Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ source of truth** for *what* should run.
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, …)
is used only when global sections (`[supervisord]`, HTTP listener, …)
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).
for this instance, no privileged ports, no `user=root`. Config, HTTP
listener, 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.
Expand Down Expand Up @@ -100,14 +100,14 @@ 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:
(§3a.7) — only supervisord's own config, HTTP listener, pidfile and logs
use the `/data` tree (listener binds loopback; address is not a path):

| Path | Purpose | Mode |
| Path / address | 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` |
| `127.0.0.1:9001` | `[inet_http_server]` HTTP listener (`InetHTTPAddr`) | — |
| `/data/run/supervisord.pid` | supervisord pidfile | `0600` |
| `/data/log/` | supervisord main log + per-program logs | `0700` |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pkgs/bigfred/server/supervisord/
│ └── supervisord.conf.tmpl # embedded via //go:embed
├── config.go # ProgramSpec, GroupSpec, RenderConfig
├── render.go # text/template execution + atomic write
├── ctl.go # supervisorctl wrapper (unix socket)
├── ctl.go # supervisorctl wrapper (inet HTTP)
└── daemon.go # spawn / wait / stop supervisord process

pkgs/bigfred/server/service/supervisord.go
Expand All @@ -28,7 +28,7 @@ awareness.
// ProgramSpec is one supervisord [program:NAME] entry.
type ProgramSpec struct {
Name string // unique across the entire supervisord instance
Command string // passed to /bin/bash -c (see template); may contain pipes, env, etc.
Command string // wrapped as `<shell> -c '…'` (see template / DefaultShell); may contain pipes, env, etc.
Autostart bool
Autorestart bool
// Optional overrides (zero = template default)
Expand Down Expand Up @@ -68,11 +68,11 @@ type SupervisordConfig struct {
SupervisorctlBin string

// Paths — default to hub locations under /data/etc/supervisord/, /data/run/, /data/log/
ConfigDir string
ConfigPath string
SocketPath string
PIDFile string
LogDir string
ConfigDir string
ConfigPath string
InetHTTPAddr string // default 127.0.0.1:9001
PIDFile string
LogDir string

// Initial desired state (may be empty; callers add before Start)
InitialState supervisord.DesiredState
Expand Down Expand Up @@ -136,21 +136,21 @@ The template is embedded and executed with a single root struct:
// pkgs/bigfred/server/supervisord/render.go

type templateData struct {
RunAsUser string
SocketPath string
PIDFile string
LogDir string
Groups []GroupSpec
RunAsUser string
InetHTTPAddr string
PIDFile string
LogDir string
Shell string // empty → DefaultShell() (/bin/bash|/bin/sh|/system/bin/sh)
Groups []GroupSpec
}
```

Example template (`supervisord.conf.tmpl`):

```ini
; Generated by BigFred — do not edit manually.
[unix_http_server]
file={{ .SocketPath }}
chmod=0700
[inet_http_server]
port={{ .InetHTTPAddr }}

[supervisord]
logfile={{ .LogDir }}/supervisord.log
Expand All @@ -163,7 +163,7 @@ user={{ .RunAsUser }}
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix://{{ .SocketPath }}
serverurl=http://{{ .InetHTTPAddr }}

{{- range .Groups }}

Expand All @@ -172,7 +172,7 @@ programs={{ joinProgramNames .Programs }}

{{- range .Programs }}
[program:{{ .Name }}]
command=/bin/bash -c {{ shellQuote .Command }}
command={{ $.Shell }} -c {{ shellQuote .Command }}
directory={{ $.ConfigDir }}
autostart={{ .Autostart }}
autorestart={{ .Autorestart }}
Expand All @@ -189,11 +189,13 @@ startsecs={{ or .StartSecs 1 }}
Template helpers (registered in `render.go`):

- `joinProgramNames` — comma-separated program list for `[group:…]`.
- `shellQuote` — single-quote escaping for safe `/bin/bash -c '…'` embedding.
- `shellQuote` — single-quote escaping for safe `<shell> -c '…'` embedding.
- `or` — default integers when struct field is zero.

Callers pass the **inner** shell command only; the template always wraps it
as `/bin/bash -c '…'`. Example: `Command: "/usr/bin/loco scripts-executor --executor-socket /run/…"`.
Callers pass the **inner** shell command only; the template wraps it as
`{{ $.Shell }} -c '…'`. `Shell` defaults via `DefaultShell()` to the first
existing of `/bin/bash`, `/bin/sh`, `/system/bin/sh` (Android). Example:
`Command: "/usr/bin/loco scripts-executor --executor-socket /run/…"`.

`autostart` and `autorestart` render as lowercase `true` / `false` per
supervisord INI conventions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ server main
└─ SupervisordService.Start(ctx)
├─ MkdirAll ConfigDir, LogDir, socket parent dir (0700)
├─ MkdirAll ConfigDir, LogDir, pidfile parent dir (0700)
├─ verify supervisord + supervisorctl binaries exist
├─ render → supervisord.conf (atomic write)
Expand Down Expand Up @@ -62,8 +62,9 @@ program, change `command` / `autostart` / `autorestart`) touch only
via `reread` + `update` while other programs keep running.

**Full daemon restart** is the fallback when global sections change
(socket path, logfile, pidfile, `user=`, …). With stable hub paths set
at service construction this should be rare (essentially first boot only).
(HTTP listener address, logfile, pidfile, `user=`, …). With stable hub
paths set at service construction this should be rare (essentially first
boot only).

##### Full daemon restart (fallback)

Expand Down Expand Up @@ -130,7 +131,7 @@ Subscribers:

##### Daemon respawn (supervisord crash)

If the health loop cannot reach the unix socket but `loco server` is
If the health loop cannot reach the HTTP listener but `loco server` is
still running:

1. Log `supervisord daemon unreachable`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,4 @@ To be copied into `14-acceptance-criteria/` when the milestone is scheduled:
`scripts-executor` remains (`ps` check).
7. Template unit tests cover at least: empty groups, single group / single
program, multiple groups, `autostart=false`, `autorestart=false`,
`shellQuote` escaping for `/bin/bash -c`.
`shellQuote` escaping for shell `-c` wrappers.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ Wiring in `cli/root.go`, `scripts-executor` migration, `system.status` events.
| Config & runtime paths | Hub paths under `/data/etc/supervisord/`, `/data/run/`, `/data/log/` |
| Config authoring | embedded `text/template` → atomic write to `supervisord.conf` |
| Apply config changes | regenerate file, then **`supervisorctl reread` + `update`** (built-in hot reload); full daemon restart only when global sections change |
| Process declaration | `(command, autostart, autorestart)` inside a named **process group**; `command` is wrapped as `/bin/bash -c '…'` |
| Process declaration | `(command, autostart, autorestart)` inside a named **process group**; `command` is wrapped as `<shell> -c '…'` (`DefaultShell`: `/bin/bash` → `/bin/sh` → `/system/bin/sh`) |
| Single instance | one `loco server` per machine — no multi-instance ownership checks |
| External dependency | `supervisord` + `supervisorctl` binaries on `PATH` (Python `supervisor` package) |