Containerize Phase 1 bot: Docker deployment on the Spark

Add Dockerfile, docker-compose.yml, docker-entrypoint.sh, and .dockerignore
so the bot runs detached and survives reboots, replacing the foreground venv run.

The image is generic (no secrets/deployment specifics baked in): host networking
reaches both Synapse and the Mac; .env, config.toml, and the SSH key are mounted
read-only. The entrypoint is the container's environment seam (D4 analog of
launch-claude.sh) — it generates ~/.ssh/config for the mac-bridge alias from
config.toml [mac] (new hostname/user fields) so the bot's `ssh mac-bridge` stays
unchanged. SSH key mounted not baked; first connect uses accept-new host trust.

Proven live on the Spark: container connects to Synapse and real messages launched
drivable sessions on the phone across 2 rooms via the full chain.
This commit is contained in:
Keysat
2026-06-15 18:40:05 -05:00
parent 7a39fec229
commit a7529eb0b7
6 changed files with 158 additions and 12 deletions
+41 -12
View File
@@ -51,12 +51,18 @@ v1 decision surface.
- `scripts/launch-claude.sh <repo_dir> <prompt>` — the Mac wrapper (Phase 0 deliverable;
validate by hand before any bot code).
- **Bot (Phase 1), on the Spark:** `python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`,
then `.venv/bin/python src/bot.py`. Deploy by pulling `src/`, `requirements.txt`, `config.toml`,
`.env` from the Mac via `scp mac-bridge:/Users/macpro/Projects/matrix-bridge/<x> .` (no Gitea
needed). `MB_SSH_ALIAS` env var overrides the SSH target for testing.
- _TODO (Phase 1 sub-step 4):_ containerize — `docker compose up` on the Spark (host networking;
mount `.env`/`config.toml`/SSH key read-only) once the `Dockerfile` exists.
- **Bot (Phase 1), containerized on the Spark — preferred:** from `~/matrix-bridge`,
`docker compose up -d --build` (host networking, `restart: unless-stopped` so it survives
reboots; read-only mounts of `.env`/`config.toml`/SSH key). Logs: `docker compose logs -f`.
The entrypoint generates `~/.ssh/config` for the `mac-bridge` alias from `config.toml [mac]`
(`hostname`/`user`), so the alias resolves inside the container. Override the host key path with
`MB_SSH_KEY_HOST` if it isn't `/home/modelo/.ssh/id_ed25519`.
- **Bot — venv (dev/fallback):** `python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`,
then `.venv/bin/python src/bot.py` — uses modelo's host `~/.ssh/config` for the alias.
`MB_SSH_ALIAS` overrides the SSH target for testing.
- **Deploy:** pull the bot files from the Mac (no Gitea needed) —
`scp mac-bridge:/Users/macpro/Projects/matrix-bridge/{Dockerfile,docker-compose.yml,docker-entrypoint.sh,requirements.txt,config.toml,.env} .`
and `scp -r mac-bridge:/Users/macpro/Projects/matrix-bridge/src .`, then rebuild.
## Layout
@@ -72,7 +78,11 @@ v1 decision surface.
`ssh mac-bridge gui-launch.sh`; fans out for all-projects; reports failures back to the room.
- `requirements.txt` (matrix-nio) · `.env.example` (credential schema; real `.env` gitignored).
- `.claude/` — Claude wiring (dir only for now).
- _Future:_ `Dockerfile` + `docker-compose.yml` — Phase 1 sub-step 4.
- `Dockerfile` · `docker-compose.yml` · `docker-entrypoint.sh` · `.dockerignore` — the Phase 1
container (Spark). Generic image (no secrets/deployment specifics baked in); host networking;
read-only mounts of `.env`/`config.toml`/SSH key. The entrypoint generates `~/.ssh/config` for
the `mac-bridge` alias from `config.toml [mac]` — the container's environment seam (D4 analog
of `launch-claude.sh`).
## Decisions (already made — don't relitigate without new information)
@@ -195,8 +205,27 @@ once" is not done.
for `#all-projects` (each session named `<repo> - <date>`), and reports failures back (fail-loud).
Tested on the **Spark** (`~/matrix-bridge`, venv) — launches worked across several rooms (N=3).
Now 11 project rooms + all-projects; `config.toml` has a `[mac]` section (ssh_alias + launcher).
- **Next: Phase 1 sub-step 4 — containerize.** Dockerfile + `docker-compose.yml`, host networking,
mount `.env`/`config.toml`/SSH key read-only, so the bot runs detached and survives reboots
(today it's a foreground venv run). Then FF `master` `phase-1`. Work is on branch **`phase-1`**
(pushed); `master` is at Phase 0 (`b6cc829`). Longer-term backlog (incl. headless "ask" mode) in
`ROADMAP.md`.
- **Phase 1 — DONE: containerized + proven on the Spark (2026-06-15).** The bot runs as a Docker
container on the Spark (`~/matrix-bridge`, `docker compose up -d --build`): generic image
(`python:3.12-slim` + `openssh-client`), host networking, `restart: unless-stopped` (survives
reboots), read-only mounts of `.env`/`config.toml`/SSH key. `docker-entrypoint.sh` generates
`~/.ssh/config` for `mac-bridge` from `config.toml [mac]` (added `hostname`=`10.59.211.5`,
`user`=`macpro`) — the container's env seam (D4 analog of `launch-claude.sh`); SSH key mounted
not baked; first connect uses `StrictHostKeyChecking=accept-new` (private-WireGuard tradeoff, D9).
*Proven live:* container connects to Synapse (`listening as @agent… 11 rooms`) and real messages
in **2 different rooms** each launched a drivable session on the phone via the full chain
(container → `ssh mac-bridge` → `gui-launch.sh` → `claude` → phone), rc=0 — confirming the new
container→Mac SSH hop over WireGuard (mounted key + accept-new host trust). *Formal exit was N=3;
the owner accepted 2 live launches across 2 rooms + the clear repeatable pattern as done.*
Build-time checks on the Mac also passed (image builds, `ssh -G mac-bridge` resolves, entrypoint
perms 700/600).
- **Spark-side ops are owner-run.** The Mac has **no** authorized SSH key into the Spark
(`modelo@10.59.211.6` — reachable over WireGuard but not authenticated; Phase 0 only set up the
reverse, `mac-bridge`). So deploys/restarts on the Spark are run by the owner from the Spark, not
driven from the Mac — until Phase 3 wires it behind Spark Control.
- **Next (open — discuss before building):** Phase 2 (multi-room routing) is effectively already
satisfied — the bot was built multi-room (11 rooms + all-projects) and routed correctly across 2
rooms in the Phase 1 proof; only a formal confirmation pass remains. Live candidates: **Phase 3**
(Spark Control: bot status + one-click update/restart on the dashboard, the SSH-behind-buttons
pattern — also closes the owner-run-ops gap above) or the **headless "ask" mode** from
`ROADMAP.md` (a message runs `claude -p` and posts the answer back into the room).