Implement Approach B: socket-watching controller (Docker-event source enrollment)
- Dominant language
- Python
- Stars
- 2
- Forks
- 0
- Avg merge
- 15h 59m
- Merged PRs (30d)
- 9
Description
## Summary
Implement the **socket-watching controller** enrollment model (Approach B, decided in #42). A privileged controller watches the Docker event stream and drives source enrollment, network attach, and liveness — replacing the in-container connector + presence protocol. The proven spec-009 core (registry / per-source pollers / delivery / grants) is **reused unchanged**: the controller calls the existing `POST /v1/sources` on behalf of each discovered container.
Parent decision: #42. Key conveyance: **host-managed per-project key mount** (decided).
## Architecture
```
docker socket
│ (method+path allowlist)
┌───────▼────────┐
│ socket-proxy │ GET /events,/containers,/networks
└───────┬────────┘ POST /networks/*/connect|disconnect (nothing else)
events/inspect/ │
network connect │
┌───────▼────────┐ internal API ┌──────────────────────┐
│ controller │ ──register/drain──▶ │ notifier core (spec │
│ (privileged) │ POST /v1/sources │ 009): registry, │
└───────┬────────┘ │ pollers, delivery, │
reads key ───▶│ │ grants, channel+bot │
/run/remo/keys/│ │ (UNPRIVILEGED) │
└──────────────────────┘
```
The split is the point: only the controller touches Docker (via the proxy); the
internet-facing notifier + Telegram bot stay unprivileged.
## Key conveyance — host-managed per-project mount (decided)
- Host (Ansible/launch) generates a per-project approver key at `/run/remo/keys/` (0400).
- Mounted read-only into **that project's agentsh container** (agentsh uses it) **and** into the **controller** (reads it on discovery).
- Per-project isolation; key never in container labels/inspect; host is the trust root.
- The mount is a volume, not socket power — survives the proxy guardrail.
## Tasks
- [ ] **Spike (do first): socket-proxy allowlist.** Validate a proxy can express *GET containers/networks + events + POST network connect/disconnect, deny all else*. Coarse proxies (tecnativa) gate by section + a global POST toggle and likely **cannot** express this → expect a small bespoke method+path-allowlist proxy (Caddy/nginx/Go). Output: the chosen proxy + config.
- [ ] **Controller**: Docker `events` watch (reconnect/replay-safe); on container `start` → resolve `sourceId` (label/hostname convention), read key from `/run/remo/keys/`, derive `api_url` from the project network name, `network connect` notifier→project net (reuse PR #45 `netwire` logic), register via `/v1/sources`. On `die`/`stop` → deregister + drain + `network disconnect` (disconnect-when-last).
- [ ] **Discovery convention**: container label(s) that mark "this runs agentsh" + port (e.g. `remo.agentsh.enabled`, `remo.agentsh.port`). Containers without it are ignored.
- [ ] **Host key provisioning**: Ansible generates per-project keys, places at `/run/remo/keys/`, wires the agentsh + controller mounts.
- [ ] **Split deployment**: controller + socket-proxy as their own units/containers; notifier keeps no Docker access. Update the `remo_notifier` role.
- [ ] **Compose-aware**: a project = N containers on one project network; controller targets the workspace/agentsh container and joins the notifier to its net. Other services on the net don't trigger separate sources.
- [ ] **Tests**: controller driven by a fake event stream + fake `/v1/sources` + fake proxied docker (mirror the `netwire`/feature test style — no live daemon needed).
## Supersedes / reuses
- **Supersedes** the in-container connector + devcontainer Feature `remo-notifier-source` (Approach A). Once B lands, the Feature can be deprecated.
- **Reuses**: spec-009 registry/pollers/delivery/grants unchanged; PR #45 `netwire` logic (moves into controller) and wildcard grants (orthogonal, kept).
- PR #45's connector-derivation is now mostly moot under B, but harmless to leave; the Feature stays until B is proven.
## Still required regardless
- agentsh in the container — **#43** (agentsh-as-a-Feature).
- network-aware rendering — **#44** (after payload capture).
## Open questions
- Bespoke proxy choice + exact allowlist (spike output).
- Event replay/missed-event handling on controller restart (re-scan `containers` on startup to rebuild state).
- Exact discovery label schema.
## Links
- Parent: #42 · Related: #41, #43, #44 · Builds on: PR #45
Contributor guide
Research direction
Start with the socket-proxy allowlist spike and document the chosen proxy and configuration. Then read PR #45's netwire logic, the remo_notifier role, and the spec-009 interfaces before defining the controller boundaries. Done means event-driven enrollment and draining, host key mounts, split deployment, Compose-aware discovery, and fake-stream/proxy tests without a live Docker daemon.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ansible, docker, python
- Domain
- backend, devops, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100