Install under procd, so OpenWrt counts as a host too

The binary was already static and musl-clean; the only thing standing between
Ham Lab and an OpenWrt box was that setup.go knew systemd and OpenRC and
nothing else, so an install there ended at "nothing starts it at boot."

Adds an embedded procd init script, detected on /sbin/procd + /etc/rc.common
ahead of the OpenRC branch. Both backends write /etc/init.d/hamlab, so
uninstall tells them apart by USE_PROCD=1 rather than by path.

Verified on the new router: procd supervises the instance, S95/K10 symlinks
land, and the hub drives its Docker and stacks like any other node. The
Updates tab needed nothing — apk-tools 3 answers the same query Alpine's does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-26 17:39:04 -07:00
co-authored by Claude Opus 5
parent 4e3c817b2a
commit 4450f1285d
3 changed files with 65 additions and 9 deletions
+10 -3
View File
@@ -437,9 +437,16 @@ something long in one, switch away and back, refresh — it's all still there.
replace, exec in place. Config default moved to `~/.config/hamlab` (existing replace, exec in place. Config default moved to `~/.config/hamlab` (existing
`~/apps/hamlab` still found by the installer and the CLI). Tested in `~/apps/hamlab` still found by the installer and the CLI). Tested in
containers: Debian 12, Ubuntu 22.04, Fedora 40, Rocky 8, openSUSE Leap 15.5, Alpine containers: Debian 12, Ubuntu 22.04, Fedora 40, Rocky 8, openSUSE Leap 15.5, Alpine
3.18/3.20 (OpenRC + doas, non-root), plus the four lab hosts. Not done: an OpenRC 3.18/3.20 (OpenRC + doas, non-root), plus the four lab hosts. **procd (OpenWrt) added
backend for the Services tab; runit/s6/sysvinit service files; public hosting for 2026-09-26** (`internal/setup/hamlab.procd`): detected on `/sbin/procd` + `/etc/rc.common`
`install.sh` (the Gitea repo is private, so the one-liner needs `HAMLAB_TOKEN`). ahead of the OpenRC branch, installed to the same `/etc/init.d/hamlab` path and told apart
from OpenRC at uninstall by `USE_PROCD=1` in the file. Verified on the new router
(`gate`, OpenWrt 25.12.5, running as root): `S95`/`K10` symlinks, procd supervising the
instance, Docker tab and stacks working through the hub. The Updates tab needed no change
— apk-tools 3 answers `apk version -l '<'` the same way Alpine's does. Not done: an
OpenRC or procd backend for the Services tab (it is systemd-only and says so cleanly);
runit/s6/sysvinit service files; public hosting for `install.sh` (the Gitea repo is
private, so the one-liner needs `HAMLAB_TOKEN`).
- **Users, sessions and settings that follow the account** — **shipped 2026-09-22** - **Users, sessions and settings that follow the account** — **shipped 2026-09-22**
(`internal/api/users.go`, `config.User`, `store.Session`/`UserState`, `Login.tsx`, (`internal/api/users.go`, `config.User`, `store.Session`/`UserState`, `Login.tsx`,
`Users.tsx` — a hub-only **Users** tab in the rail, added the same day at Mitchell's `Users.tsx` — a hub-only **Users** tab in the rail, added the same day at Mitchell's
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh /etc/rc.common
# Ham Lab web console (written by hamlab install)
USE_PROCD=1
# after network and firewall, before the tail of the boot
START=95
STOP=10
start_service() {
procd_open_instance hamlab
procd_set_param command "@BIN@" serve
procd_set_param env HAMLAB_CONFIG="@CONFIG@" HOME="@HOME@" USER="@USER@"
procd_set_param user "@USER@"
procd_set_param group "@GROUP@"
procd_set_param pidfile /var/run/hamlab.pid
# respawn: settle 3s, 5 retries inside that window, then give up counting
procd_set_param respawn 3600 3 0
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger network
}
+25 -1
View File
@@ -37,9 +37,14 @@ var systemdUnit string
//go:embed hamlab.openrc //go:embed hamlab.openrc
var openrcScript string var openrcScript string
//go:embed hamlab.procd
var procdScript string
const ( const (
unitPath = "/etc/systemd/system/hamlab.service" unitPath = "/etc/systemd/system/hamlab.service"
openrcPath = "/etc/init.d/hamlab" openrcPath = "/etc/init.d/hamlab"
// procd (OpenWrt) uses the same path as OpenRC; the two are told apart by content.
procdPath = "/etc/init.d/hamlab"
sudoersPath = "/etc/sudoers.d/hamlab-quiet" sudoersPath = "/etc/sudoers.d/hamlab-quiet"
) )
@@ -228,6 +233,19 @@ func Install(args []string, version string) error {
return err return err
} }
} }
case exists("/sbin/procd") && exists("/etc/rc.common"):
initSys = "procd"
if err := os.WriteFile(procdPath, []byte(fill(procdScript, vars)), 0o755); err != nil {
return err
}
if err := run(procdPath, "enable"); err != nil {
say("warning: %s enable: %v", procdPath, err)
}
if !o.noStart {
if err := run(procdPath, "restart"); err != nil {
return err
}
}
case exists("/sbin/openrc-run") || exists("/usr/sbin/openrc-run") || have("rc-service"): case exists("/sbin/openrc-run") || exists("/usr/sbin/openrc-run") || have("rc-service"):
initSys = "OpenRC" initSys = "OpenRC"
if err := os.WriteFile(openrcPath, []byte(fill(openrcScript, vars)), 0o755); err != nil { if err := os.WriteFile(openrcPath, []byte(fill(openrcScript, vars)), 0o755); err != nil {
@@ -258,7 +276,7 @@ func Install(args []string, version string) error {
url := scheme + "://" + browseAddr(cfg.Listen) url := scheme + "://" + browseAddr(cfg.Listen)
switch { switch {
case initSys == "": case initSys == "":
say("\nNo systemd or OpenRC here, so nothing starts it at boot.\nRun it under your own supervisor, as %s:\n HAMLAB_CONFIG=%s %s serve", acct.name, cfgPath, dest) say("\nNo systemd, procd or OpenRC here, so nothing starts it at boot.\nRun it under your own supervisor, as %s:\n HAMLAB_CONFIG=%s %s serve", acct.name, cfgPath, dest)
case o.noStart: case o.noStart:
case healthy(cfg.Listen, cfg.TLS == "auto"): case healthy(cfg.Listen, cfg.TLS == "auto"):
say("\nHam Lab is up: %s", url) say("\nHam Lab is up: %s", url)
@@ -294,9 +312,15 @@ func Uninstall(args []string) error {
_ = run("systemctl", "daemon-reload") _ = run("systemctl", "daemon-reload")
} }
if b, err := os.ReadFile(openrcPath); err == nil { if b, err := os.ReadFile(openrcPath); err == nil {
if strings.Contains(string(b), "USE_PROCD=1") {
dataDir = configDirFrom(string(b), "HAMLAB_CONFIG=")
_ = run(procdPath, "stop")
_ = run(procdPath, "disable")
} else {
dataDir = configDirFrom(string(b), "export HAMLAB_CONFIG=") dataDir = configDirFrom(string(b), "export HAMLAB_CONFIG=")
_ = run("rc-service", "hamlab", "stop") _ = run("rc-service", "hamlab", "stop")
_ = run("rc-update", "del", "hamlab", "default") _ = run("rc-update", "del", "hamlab", "default")
}
os.Remove(openrcPath) os.Remove(openrcPath)
} }
os.Remove(sudoersPath) os.Remove(sudoersPath)