Home

01 overview and goals

§7d.1 Overview & design goals

Problem

BigFred already relies on sibling processes isolated from the main server binary — most notably scripts-executor (§3a.7). The current architecture sketches a hand-rolled supervisor in pkgs/bigfred/server/executor/supervisor.go (spawn, exponential backoff, health flag, graceful shutdown). That approach works for one child but does not scale cleanly when more managed processes appear (per-layout command-station workers, dedicated pollers, optional sidecars).

The user requirement is to manage many processes with confidence they are running, using supervisord as the battle-tested process manager, while the Go backend remains the single source of truth for what should run.

Goals

  1. Declarative process groups — callers register programs as (command, autostart, autorestart) tuples grouped under a logical name (e.g. loco, command-stations).
  2. Config as code — supervisord INI is never edited by hand at runtime; it is rendered from Go structs via text/template and written atomically.
  3. Hot reload on change — after a config change, apply it with supervisord's built-in supervisorctl reread followed by supervisorctl update, which adds/removes/restarts individual programs without restarting the daemon. A full supervisord restart is used only when global sections ([supervisord], socket, …) change — a rare case with stable hub paths.
  4. Non-root only — no /etc/supervisor, no system-wide /var/run for this instance, no privileged ports, no user=root. Config, socket, pidfile and logs live under /data/… on the hub RW partition (or the same paths when developing against a mounted /data tree).
  5. Observable — the service exposes program/group status so higher layers (ScriptService, WS system.status, admin UI) can report health without re-implementing process tracking.

Non-goals (this milestone)

High-level placement

┌─────────────────────────────────────────────────────────────┐
│  loco server (Go, non-root)                                 │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  SupervisordService                                   │  │
│  │    · desired state (groups + programs)                │  │
│  │    · render supervisord.conf (Go template)            │  │
│  │    · spawn supervisord daemon; reread/update on change│  │
│  │    · supervisorctl status / shutdown                 │  │
│  └───────────────────────────┬───────────────────────────┘  │
│                              │ owns (child process)         │
│                              ▼                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  supervisord daemon                                   │  │
│  │    group:loco                                         │  │
│  │      ├─ program:scripts-executor   autostart=true    │  │
│  │      └─ program:…                  autorestart=…    │  │
│  │    group:dcc-bus (added by §7e M4.5)                  │  │
│  │      ├─ program:dcc-bus-1-2        autostart=true    │  │
│  │      └─ program:dcc-bus-1-3        autostart=true    │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

server remains the DCC/throttle authority. Managed processes stay siblings — they never import pkgs/bigfred/server/http or pkgs/bigfred/server/ws.

Why supervisord instead of extending the hand-rolled supervisor

Aspect Hand-rolled executor/supervisor.go SupervisordService
Multiple processes one-off spawn loop per child one daemon, many [program:…] sections
Crash restart custom backoff in Go autorestart= handled by supervisord
Group operations manual supervisorctl restart loco:*
Log capture custom pipes built-in stdout/stderr log files
Operational familiarity project-specific widely documented tooling

The Go layer keeps policy (which programs exist, when config changes) and delegates mechanism (signal handling, restart timing, log rotation basics) to supervisord.

Path layout (hub)

All paths are fixed constants in supervisord.DefaultPaths() (see pkgs/bigfred/server/supervisord/paths.go). On the hub image they sit on the RW partition mounted at /data; SupervisordService.Start creates missing directories with mode 0700.

The scripts-executor Unix socket remains at $XDG_RUNTIME_DIR/loco/exec.sock (§3a.7) — only supervisord's own config, control socket, pidfile and logs use the /data tree:

Path Purpose Mode
/data/etc/supervisord/ config dir; directory= for managed programs 0700
/data/etc/supervisord/supervisord.conf rendered config 0600
/data/run/supervisord.sock [unix_http_server] socket 0700
/data/run/supervisord.pid supervisord pidfile 0600
/data/log/ supervisord main log + per-program logs 0700

The template also sets user={{ .RunAsUser }} in [supervisord] so programs cannot accidentally inherit a different identity if the config is reused elsewhere.

External dependency

The host (or dev container) must provide:

Both are shipped by the Python supervisor package (pip install supervisor or distro package supervisor). The service accepts optional SupervisordBin / SupervisorctlBin overrides for non-PATH installs. Missing binaries fail fast at SupervisordService.Start with a clear error — no silent fallback to the old hand-rolled supervisor.