feat(selfhost): host-companion redeploy trigger via MCP admin tool#8209
Conversation
Closes #7723. A new admin-category MCP tool, loopover_admin_trigger_redeploy, lets an operator trigger a real self-redeploy (pull the published image, restart, wait for health) through an MCP client -- without ever mounting /var/run/docker.sock into an app-facing container. Design decision (proxy-mediated vs. host-companion, per #7720's own framing): host companion. Investigated extending the existing docker-proxy sidecar first -- tecnativa/docker-socket-proxy's ACL model has no way to scope a restart to one named container (only "any container reachable through the socket"), and has no concept of "pull a new image" at all, since the real desired behavior is docker-compose-level orchestration, not a single Docker Engine API call a raw proxy could cleanly allowlist. The companion (scripts/redeploy-companion.ts) runs entirely outside Docker as a systemd service, listens on a Unix domain socket (never a TCP port), authenticates with its own separate REDEPLOY_COMPANION_TOKEN (distinct from LOOPOVER_MCP_ADMIN_TOKEN -- two independent credentials, not one), and shells out to the existing, already-tested scripts/deploy-selfhost-image.sh rather than reimplementing its pull+recreate+health-wait sequence. Entirely opt-in: no companion installed means the tool reports configured: false, every other admin tool is unaffected.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | 96c1eef | Commit Preview URL Branch Preview URL |
Jul 23 2026, 12:55 PM |
Bundle ReportChanges will increase total bundle size by 10.13kB (0.14%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8209 +/- ##
=======================================
Coverage 92.10% 92.11%
=======================================
Files 776 778 +2
Lines 78328 78394 +66
Branches 23667 23686 +19
=======================================
+ Hits 72147 72209 +62
Misses 5062 5062
- Partials 1119 1123 +4
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…ploy image override
Security scanner flagged isSafeImageOverride for not rejecting backticks,
which can trigger bash command substitution. Verified empirically (not just
by inspection) that this isn't currently exploitable: redeploy-companion.ts's
spawn() never sets shell: true, so the image string always reaches
deploy-selfhost-image.sh as a single literal argv element, and that script
only ever references it through properly quoted expansions ("$1", "$IMAGE") --
confirmed with a standalone repro that a backtick payload passed as real argv
data stays inert literal text through resolve_image, the generated compose
override YAML, and would reach docker compose unexecuted.
Fixed anyway as defense-in-depth: no legitimate Docker image reference ever
needs backticks, semicolons, pipes, ampersands, or angle brackets, so
rejecting them costs nothing, and it removes a fragile invariant (the
"nothing here is ever unquoted" guarantee) that a future change to either
side of this call could silently break. Applied consistently across all
three places that share this character class: deploy-selfhost-image.sh's
authoritative validate_inputs, redeploy-companion.ts's own pre-check, and the
MCP tool's zod schema.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Caution 🛑 LoopOver review result - fixes requiredReview updated: 2026-07-23 13:42:45 UTC
Review summary Nits — 6 non-blocking
CI checks failing
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
test/unit/docker-compose-env-example-parity.test.ts failed CI: docker-compose.yml interpolates this var twice but it was never documented in .env.example (REDEPLOY_COMPANION_TOKEN_FILE was already covered via secrets/README.md).


Summary
Closes #7723 (sub-issue of #7720, sibling to #7721's config admin tools). A new
admin-category MCP tool,loopover_admin_trigger_redeploy, lets an operator trigger a real self-redeploy (pull the published image, restart, wait for health) through an MCP client — without ever mounting/var/run/docker.sockinto an app-facing container.Design decision (this issue's own explicit requirement — proxy-mediated vs. host-companion): host companion. Investigated extending the existing
docker-proxysidecar first:tecnativa/docker-socket-proxy's ACL model has no way to scope a restart to one named container (only "any container reachable through the socket," broader than intended), and has no concept of "pull a new image" at all — the real desired behavior isdocker compose pull && up -d-level orchestration, not a single Docker Engine API call a raw proxy could cleanly allowlist. A host companion sidesteps both problems by calling through the exact existing, already-testedscripts/deploy-selfhost-image.shinstead of reimplementing its pull+recreate+health-wait sequence.How it works:
scripts/redeploy-companion.tsruns entirely outside Docker as a systemd service (systemd/loopover-redeploy-companion.service.example), listens on a Unix domain socket (never a TCP port, never the Docker socket), and authenticates with its own separateREDEPLOY_COMPANION_TOKEN— a distinct credential fromLOOPOVER_MCP_ADMIN_TOKEN, so a single leaked token can't trigger a redeploy on its own. Theloopovercontainer reaches it via a narrow, purpose-built socket bind-mount.Entirely opt-in: no companion installed means the tool reports
configured: false; every other admin tool (#7721) is completely unaffected. Docs include a full "why this needs a whole extra moving part" security-tradeoffs section (self-hosting-configuration.mdx), matching this issue's own explicit deliverable.Test plan
npm run typecheckclean (repo-wide)redeploy-companion.ts'shandleConnection/runDeploy(13), the socket client against a real Unix socket server (12), the Workers-safe registry (3), the MCP tool's registration-gating/auth-boundary/not-configured/success/failure/protocol-failure paths (11) — plus the pre-existing admin-tools/tool-categories/tool-rename-alias suites (48) confirmed unaffectednpm run docs:drift-check,selfhost:env-reference:check,test:miner-deployment-docs-audit,manifest:drift-check,branding-drift:checkall cleandocker compose configvalidates the compose changesnpm run ui:lint/npm run ui:typecheckcleansrc/**files; the couple of remaining gaps are proven-exercised-by-passing-tests defensive races (a timer callback firing afterclearTimeout, a socket error after an already-settled promise) that a real-timer/coverage-instrumentation interaction doesn't attribute correctly — documented inline