fix(supervise): docker gateway shares the one host DB (Step 2d)
The docker gateway container ran the supervise daemon but bind-mounted no DB and set no SUPERVISE_DB_PATH — so the daemon wrote proposals to a container-local, ephemeral SQLite file, disconnected from the host DB the orchestrator (and now the operator, over HTTP) uses. Same split-DB bug firecracker had; Dockerfile.gateway even documents the mount (`/run/supervise/bot-bottle.db bind-mounted at run time`) that ensure_running never provided. Bind-mount the host DB dir into the gateway at /run/supervise and set SUPERVISE_DB_PATH, so the daemon queues into the same file the orchestrator container opens (its BOT_BOTTLE_ROOT bind-mount) and the operator reaches over the control plane. One DB per host, shared by bind-mounts — docker now on the exact same supervise path as firecracker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -23,6 +23,13 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..docker_cmd import run_docker
|
from ..docker_cmd import run_docker
|
||||||
|
from ..paths import host_db_path
|
||||||
|
from ..supervise import DB_PATH_IN_CONTAINER
|
||||||
|
|
||||||
|
# The host DB dir is bind-mounted here so the gateway's supervise daemon
|
||||||
|
# writes its queued proposals into the ONE host DB (the same file the
|
||||||
|
# orchestrator container opens and the operator reaches over HTTP).
|
||||||
|
_SUPERVISE_DB_DIR_IN_CONTAINER = os.path.dirname(DB_PATH_IN_CONTAINER)
|
||||||
|
|
||||||
# The gateway's mitmproxy writes its CA a beat after the container starts, so
|
# The gateway's mitmproxy writes its CA a beat after the container starts, so
|
||||||
# reads poll for it rather than assuming it's there on a fresh launch.
|
# reads poll for it rather than assuming it's there on a fresh launch.
|
||||||
@@ -54,6 +61,14 @@ GATEWAY_DOCKERFILE = "Dockerfile.gateway"
|
|||||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def _host_db_dir() -> str:
|
||||||
|
"""The host DB directory (created if missing), for the gateway's
|
||||||
|
supervise-DB bind-mount."""
|
||||||
|
db_dir = host_db_path().parent
|
||||||
|
db_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return str(db_dir)
|
||||||
|
|
||||||
|
|
||||||
class GatewayError(Exception):
|
class GatewayError(Exception):
|
||||||
"""The shared gateway failed to build/start/stop (non-zero `docker` exit)."""
|
"""The shared gateway failed to build/start/stop (non-zero `docker` exit)."""
|
||||||
|
|
||||||
@@ -192,6 +207,11 @@ class DockerGateway(Gateway):
|
|||||||
# Persist the self-generated CA so it survives restarts (agents
|
# Persist the self-generated CA so it survives restarts (agents
|
||||||
# trust it) — see GATEWAY_CA_VOLUME.
|
# trust it) — see GATEWAY_CA_VOLUME.
|
||||||
"--volume", f"{GATEWAY_CA_VOLUME}:{MITMPROXY_HOME}",
|
"--volume", f"{GATEWAY_CA_VOLUME}:{MITMPROXY_HOME}",
|
||||||
|
# Share the one host DB: the supervise daemon queues proposals
|
||||||
|
# into the same file the orchestrator (and the operator, over
|
||||||
|
# HTTP) reads — no second, disconnected DB in the container.
|
||||||
|
"--volume", f"{_host_db_dir()}:{_SUPERVISE_DB_DIR_IN_CONTAINER}",
|
||||||
|
"--env", f"SUPERVISE_DB_PATH={DB_PATH_IN_CONTAINER}",
|
||||||
]
|
]
|
||||||
for port in self._host_port_bindings:
|
for port in self._host_port_bindings:
|
||||||
argv += ["--publish", f"0.0.0.0:{port}:{port}"]
|
argv += ["--publish", f"0.0.0.0:{port}:{port}"]
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ class TestDockerGateway(unittest.TestCase):
|
|||||||
self.assertEqual(self.sc.network, runs[0][runs[0].index("--network") + 1])
|
self.assertEqual(self.sc.network, runs[0][runs[0].index("--network") + 1])
|
||||||
# Persists its CA on a named volume so agents keep trusting it.
|
# Persists its CA on a named volume so agents keep trusting it.
|
||||||
self.assertTrue(any("mitmproxy" in a for a in runs[0]))
|
self.assertTrue(any("mitmproxy" in a for a in runs[0]))
|
||||||
|
# Shares the ONE host DB: the supervise daemon queues into the same
|
||||||
|
# file the orchestrator + operator (over HTTP) use.
|
||||||
|
self.assertTrue(any(
|
||||||
|
a.startswith("SUPERVISE_DB_PATH=") and a.endswith("/run/supervise/bot-bottle.db")
|
||||||
|
for a in runs[0]))
|
||||||
|
self.assertTrue(any(
|
||||||
|
a.endswith(":/run/supervise") for a in runs[0]))
|
||||||
|
|
||||||
def test_ensure_running_creates_network_when_missing(self) -> None:
|
def test_ensure_running_creates_network_when_missing(self) -> None:
|
||||||
calls: list[list[str]] = []
|
calls: list[list[str]] = []
|
||||||
|
|||||||
Reference in New Issue
Block a user