cb2d778a8f
Sweep for vestiges of the old combined-plane model and the pre-split shared rootfs. Two are load-bearing, the rest are stale docs/comments: - Bug: macOS `enumerate_active` only excluded the gateway container from the agent list, so after the split the orchestrator container (`bot-bottle-mac-orchestrator`, also `bot-bottle-`-prefixed) was enumerated as a phantom agent. Exclude both infra containers; test covers it. - Dead code: the gateway `bootstrap.py` still carried an `orchestrator` daemon spec + `_OPT_IN_DAEMONS` + a signing-key/JWT env branch, all for the old combined container where the gateway process could also run the control plane. No backend ever requests it now — removed; the key-stripping stays as defense-in-depth. Stale-comment reframes: "the/single infra container" -> the orchestrator + gateway pair (or the specific plane); "shared rootfs / bb_role init / one published rootfs" -> the per-plane rootfs + `role_init`; the deleted Dockerfile.infra references in Dockerfile.orchestrator/.gateway; and the macOS "one infra container ... same address" docstring + its now-false share-one-address test (the planes are distinct containers with distinct addresses). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
4.1 KiB
Python
97 lines
4.1 KiB
Python
"""Stable gateway name for macOS agents, via each bottle's `/etc/hosts`.
|
|
|
|
The shared gateway's address is assigned by vmnet's DHCP and changes whenever
|
|
the gateway container is recreated — a source-hash bump, an image upgrade, a
|
|
crash. Every agent-facing URL (egress proxy, git-http, supervise) embeds that
|
|
address, and the proxy URL reaches the agent as **process environment** at
|
|
`container exec` time. A running process's `environ` cannot be rewritten from
|
|
outside, so a moved gateway used to strand every running bottle permanently:
|
|
not degraded, unreachable, until the bottle was relaunched and its session
|
|
thrown away.
|
|
|
|
So the agent never learns the address. It is given a stable *name*
|
|
(`GATEWAY_HOSTNAME`) in every URL, resolved through its own `/etc/hosts`.
|
|
Unlike `environ`, that is a file — it can be rewritten inside a container that
|
|
is already running, so a gateway that comes back at a new address is picked up
|
|
by live bottles instead of orphaning them.
|
|
|
|
Apple Container 1.0 offers no container-name DNS on a user network (the only
|
|
nameserver an agent sees is vmnet's, which does not know container names) and
|
|
`container run` has no `--add-host`, so the entry is written by exec after the
|
|
container starts.
|
|
|
|
Writing it needs root, and the agent runs as `node`: the agent therefore
|
|
cannot repoint its own gateway name, while the host (which drives `container
|
|
exec --user root`) can. That asymmetry is deliberate — keep it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ...log import warn
|
|
from . import util as container_mod
|
|
from .enumerate import CONTAINER_NAME_PREFIX, enumerate_active
|
|
|
|
# The name every agent-facing gateway URL uses. Must not collide with a real
|
|
# DNS name the agent might resolve; it is bottle-local by construction.
|
|
GATEWAY_HOSTNAME = "bot-bottle-gateway"
|
|
|
|
# Marker so the rewrite is idempotent and only ever touches our own line —
|
|
# the rest of /etc/hosts (localhost, the container's own name) is preserved.
|
|
_MARKER = "# bot-bottle gateway"
|
|
|
|
|
|
def _rewrite_script(gateway_ip: str) -> str:
|
|
"""A shell one-liner that replaces our managed line in `/etc/hosts`.
|
|
|
|
Rewrites in place via a temp file + `cat` rather than `mv`, so the file
|
|
keeps its original inode, ownership, and mode — a bind-mounted or
|
|
pre-created `/etc/hosts` must not be replaced by a root-owned 0644 copy
|
|
that the runtime then refuses to update.
|
|
"""
|
|
return (
|
|
"set -e; "
|
|
f"grep -v '{_MARKER}' /etc/hosts > /tmp/.bb-hosts || true; "
|
|
f"printf '%s %s %s\\n' '{gateway_ip}' '{GATEWAY_HOSTNAME}' "
|
|
f"'{_MARKER}' >> /tmp/.bb-hosts; "
|
|
"cat /tmp/.bb-hosts > /etc/hosts; "
|
|
"rm -f /tmp/.bb-hosts"
|
|
)
|
|
|
|
|
|
def set_gateway_host(container_name: str, gateway_ip: str) -> None:
|
|
"""Point `GATEWAY_HOSTNAME` at `gateway_ip` inside one running container.
|
|
|
|
Must run before the agent is exec'd: the agent's proxy URL names the
|
|
gateway, so the entry has to exist for its first connection. Idempotent —
|
|
re-running with the same address is a no-op in effect.
|
|
"""
|
|
container_mod.exec_container_as_root(
|
|
container_name, ["sh", "-c", _rewrite_script(gateway_ip)],
|
|
)
|
|
|
|
|
|
def refresh_gateway_host(gateway_ip: str) -> list[str]:
|
|
"""Re-point every running bottle at the current gateway address.
|
|
|
|
Called once the shared gateway is known to be up, so a bottle stranded by
|
|
an earlier gateway restart re-attaches instead of needing a relaunch.
|
|
Returns the containers updated.
|
|
|
|
Best-effort per bottle: one container that refuses the write (already
|
|
exiting, say) must not stop the others from being repaired, and must not
|
|
fail the launch that triggered the sweep.
|
|
"""
|
|
updated: list[str] = []
|
|
for agent in enumerate_active():
|
|
name = f"{CONTAINER_NAME_PREFIX}{agent.slug}"
|
|
try:
|
|
set_gateway_host(name, gateway_ip)
|
|
updated.append(name)
|
|
# One bad bottle must not stop the sweep, so this is deliberately broad.
|
|
except Exception as e: # noqa: BLE001 # pylint: disable=broad-exception-caught
|
|
warn(f"could not re-point {name} at the gateway: {e}")
|
|
return updated
|
|
|
|
|
|
__all__ = ["GATEWAY_HOSTNAME", "set_gateway_host", "refresh_gateway_host"]
|