Skip to content

Implement Approach B: socket-watching controller (Docker-event source enrollment) #46

Description

@pofallon

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/<id>│                              │ (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/<sourceId> (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/<sourceId>, derive api_url from the project network name, network connect notifier→project net (reuse PR Notifier: wildcard egress grants, convention-derived source connector, Option-A net wiring #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/<sourceId>, 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

Still required regardless

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions