ac8c7ba696
End-to-end provisioning parity with the docker backend. After this
chunk a smolmachines bottle has a working trust store, git-gate
gitconfig, and supervise MCP registration — same shape as docker,
dispatched via `smolvm machine cp` / `smolvm machine exec` instead
of `docker cp` / `docker exec`.
Adds three new provision modules:
- ca.py: select egress vs pipelock CA (same logic as
docker), machine cp + update-ca-certificates,
log sha256 fingerprint.
- git.py: copy host .git when --cwd was passed; render
~/.gitconfig with insteadOf URLs. URL prefix is
`git://<bundle_ip>:9418/...` (no DNS in the
TSI-allowlisted guest) vs docker's
`git://git-gate/...`.
- supervise.py: `claude mcp add` via machine_exec; URL is
`http://<bundle_ip>:9100/`. Failure is logged but
non-fatal (matches docker).
Shared render: `render_git_gate_gitconfig` moves out of
backend/docker/provision/git.py into the platform-neutral
claude_bottle/git_gate.py (renamed to git_gate_render_gitconfig
for consistency with the existing git_gate_render_* helpers),
parameterized on a `gate_host` argument so both backends use the
same logic with different addresses.
Path/user fixups for the post-chunk-4c agent image (real
claude-bottle image, USER node, $HOME=/home/node):
- prompt.py default path moves from /root/... to
/home/node/.claude-bottle-prompt.txt; chown + chmod after
machine cp.
- skills.py default skills dir moves from /root/.claude/skills to
/home/node/.claude/skills; chown -R per skill.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
61 lines
2.1 KiB
Python
61 lines
2.1 KiB
Python
"""Supervise sidecar provisioning inside a running smolmachines
|
|
bottle (PRD 0023 chunk 4d; PRD 0013 supervise plane).
|
|
|
|
Registers the per-bottle supervise sidecar as an HTTP MCP server
|
|
in the agent's claude-code config so the agent discovers the
|
|
stuck-recovery MCP tools (pipelock-block, capability-block) at
|
|
startup.
|
|
|
|
Mirrors `backend.docker.provision.supervise` — same `claude mcp
|
|
add` call, just dispatched via `smolvm machine exec` instead of
|
|
`docker exec`, and against `<bundle_ip>:<port>` instead of the
|
|
short `supervise` alias (no DNS in the TSI-allowlisted guest)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ....log import info, warn
|
|
from ....supervise import SUPERVISE_PORT
|
|
from .. import smolvm as _smolvm
|
|
from ..bottle_plan import SmolmachinesBottlePlan
|
|
|
|
|
|
_SUPERVISE_MCP_NAME = "supervise"
|
|
|
|
|
|
def supervise_mcp_url(bundle_ip: str) -> str:
|
|
return f"http://{bundle_ip}:{SUPERVISE_PORT}/"
|
|
|
|
|
|
def provision_supervise(plan: SmolmachinesBottlePlan, target: str) -> None:
|
|
"""Run `claude mcp add` inside the guest to register the
|
|
supervise sidecar in claude-code's user config. No-op when
|
|
bottle.supervise is False.
|
|
|
|
Failure is logged but not fatal: the bottle still works (you
|
|
just can't call supervise tools from the agent until the entry
|
|
is added manually). The operator sees the warning at launch."""
|
|
if plan.supervise_plan is None:
|
|
return
|
|
url = supervise_mcp_url(plan.bundle_ip)
|
|
info(f"registering supervise MCP server in agent claude config → {url}")
|
|
r = _smolvm.machine_exec(
|
|
target,
|
|
[
|
|
"claude", "mcp", "add",
|
|
"--scope", "user",
|
|
"--transport", "http",
|
|
_SUPERVISE_MCP_NAME,
|
|
url,
|
|
],
|
|
)
|
|
if r.returncode != 0:
|
|
warn(
|
|
f"`claude mcp add supervise` failed (exit {r.returncode}): "
|
|
f"{(r.stderr or r.stdout or '').strip()}. Inside the bottle, "
|
|
f"register manually with: "
|
|
f"claude mcp add --scope user --transport http supervise {url}"
|
|
)
|
|
|
|
|
|
__all__ = ["provision_supervise", "supervise_mcp_url"]
|