Last of the per-sidecar legacy names. `_per_bottle_container_names`
used to list the four pre-bundle sidecars (cred-proxy, pipelock,
git-gate, supervise) so capability-apply's teardown would force-rm
them on remediation. None of those containers exist anymore — the
four daemons run in the sidecar bundle (PRD 0024), so the list
collapses to the agent + the bundle.
Integration test follows: the fake supervise-sidecar setup, which
existed to give teardown an extra container to clean up, switches
to a fake sidecar bundle with the current name.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same line of cleanup as the supervise rename: the per-sidecar
container names (`claude-bottle-pipelock-<slug>`,
`claude-bottle-egress-<slug>`, `claude-bottle-git-gate-<slug>`)
were docker-network aliases pointing at the bundle, kept so legacy
URLs would keep resolving. Replaces them with short hostnames
(`pipelock`, `egress`, `git-gate`) matching the existing
`EGRESS_HOSTNAME` pattern, and inlines the bundle-loopback URL
(`http://127.0.0.1:8888`) for the in-bundle egress→pipelock hop —
matching what smolmachines already does.
Drops the three `*_container_name` functions, `pipelock_proxy_url`,
and `git_gate_host`. Their callers move to the new constants:
- `PIPELOCK_HOSTNAME = "pipelock"` (claude_bottle/pipelock.py)
- `GIT_GATE_HOSTNAME = "git-gate"` (claude_bottle/git_gate.py)
- `BUNDLE_LOCAL_PIPELOCK_URL` (backend/docker/pipelock.py)
The agent's HTTP_PROXY now reads `http://pipelock:8888` (vs the
old `http://claude-bottle-pipelock-<slug>:8888`); the gitconfig
insteadOf rewrites become `git://git-gate/<repo>.git`. The prepare-
time orphan probe is collapsed onto the bundle container name
(`claude-bottle-sidecars-<slug>`) instead of the four legacy
per-sidecar names that no backend creates anymore.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Supervise runs inside the sidecar bundle (PRD 0024), not in its own
container. The `claude-bottle-supervise-<slug>` per-sidecar name only
existed as a docker-network alias on the bundle so legacy code paths
that referenced the old name would still resolve. Nothing inside the
project relies on that resolution anymore — the short `supervise`
alias is the one all consumers use — so the legacy long-form is dead.
Drops the function entirely, plus its registration as a network alias
and as an orphan probe in prepare.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The four sidecar prepare-time helpers (PipelockProxy, Egress, GitGate,
Supervise) had docker-flavored subclasses that existed only as
instantiation shims for ABCs that already had no abstract methods.
PipelockProxy.prepare() reached for class-level CA path constants
that were only defined on the docker subclass — so smolmachines had
to import DockerPipelockProxy to render pipelock yaml, reaching
across the backend boundary for what's actually a platform-neutral
operation.
This moves the universal in-container CA paths
(PIPELOCK_CA_CERT_IN_CONTAINER / PIPELOCK_CA_KEY_IN_CONTAINER) to
claude_bottle/pipelock.py, drops the class-attr indirection on the
ABC, and deletes the four empty docker subclasses. Both backends
now instantiate the ABCs directly; the docker-side modules keep
the docker-flavored helpers (image pin, container naming, host CA
mint) and re-export the moved pipelock constants for compat.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bundle daemons (pipelock, egress, optionally git-gate + supervise)
now actually start with their config files bind-mounted from the
inner Plans the docker backend already produces. Chunks 2d + 3
ran with daemons_csv="" so the bundle's init supervisor idled;
chunk 4b wires up the real path: agent → pipelock → egress →
internet (when routes declared) is now functional, modulo agent-
image gaps (claude-code / TLS-trust-store / git in the guest)
that chunk 4c addresses.
bottle_plan.py — added the four inner Plan fields:
proxy_plan: PipelockProxyPlan
git_gate_plan: GitGatePlan
egress_plan: EgressPlan
supervise_plan: SupervisePlan | None
Same shape the docker backend's plan uses. Docker-network-only
fields (internal_network, egress_network) stay at dataclass
defaults — the smolmachines bundle is on a per-bottle bridge
with a pinned IP, not docker's --internal + egress topology.
prepare.py — instantiates DockerPipelockProxy / DockerEgress /
DockerGitGate / DockerSupervise and calls their .prepare()
methods to write the per-bottle config files (pipelock.yaml,
routes.yaml, git-gate entrypoint/hooks, supervise queue dir)
under the per-bottle state dir. (The "Docker" prefix on the
class names is a misnomer here — .prepare() is platform-neutral,
inherited from each sidecar's ABC. A future cleanup could factor
the prepare logic out of the docker subpackage.)
launch.py — major rewrite:
- pipelock_tls_init at launch (always); egress_tls_init only
when the bottle declares routes (otherwise the CA files
aren't bind-mounted and openssl runs would be wasted).
- Inner Plans updated in place with launch-time CA paths +
EGRESS_UPSTREAM_PROXY = http://127.0.0.1:8888 (egress's
upstream is pipelock on the bundle's own loopback; same
container's network namespace).
- BundleLaunchSpec env + volumes built from the inner Plans:
pipelock.yaml + CA + key (always); egress routes + CAs +
upstream env + token-slot bare names (when routes); git-gate
entrypoint + hooks + per-upstream identity files (when
upstreams); supervise queue dir + env (when enabled).
- daemons_csv = ["egress", "pipelock"] + ["git-gate"] (if
upstreams) + ["supervise"] (if enabled).
- Token env values resolved from host env via
`egress_resolve_token_values` and threaded into the
docker-run subprocess env (bare-name -e entries in spec
inherit from there — values never land on argv).
Tests:
- 552 unit passing (no new unit cases; fixture updated to
populate the new plan fields).
- 5 integration cases passing locally (Darwin + smolvm + docker
+ not GITEA_ACTIONS):
* test_smoke_exec_echo — still works.
* test_localhost_reach_probe — host loopback still refused.
* test_egress_port_bypass_probe — <bundle-ip>:9099 still
refused, NOW WITH EGRESS ACTUALLY RUNNING (chunk 3's
127.0.0.1 bind-address is doing its job).
* test_prompt_file_lands_in_guest — still works.
* test_pipelock_answers_on_bundle_ip — NEW. From inside the
guest, wget to <bundle-ip>:8888 gets an HTTP response
(not "connection refused") — proves pipelock is actually
listening and the bind-mount + CA generation path works.
What's left in chunk 4:
- 4c: agent-image-conversion (claude-code + git + curl +
ca-certificates in the guest). Chunk 2d's alpine placeholder
stays for now.
- 4d: provision_ca + provision_git + provision_supervise once
the agent image has the required tools.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>