diff --git a/PLAN.md b/PLAN.md index 0e0fab5..508df5c 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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 `~/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 - 3.18/3.20 (OpenRC + doas, non-root), plus the four lab hosts. Not done: an OpenRC - backend for the Services tab; runit/s6/sysvinit service files; public hosting for - `install.sh` (the Gitea repo is private, so the one-liner needs `HAMLAB_TOKEN`). + 3.18/3.20 (OpenRC + doas, non-root), plus the four lab hosts. **procd (OpenWrt) added + 2026-09-26** (`internal/setup/hamlab.procd`): detected on `/sbin/procd` + `/etc/rc.common` + 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** (`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 diff --git a/internal/setup/hamlab.procd b/internal/setup/hamlab.procd new file mode 100644 index 0000000..d25c5d4 --- /dev/null +++ b/internal/setup/hamlab.procd @@ -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 +} diff --git a/internal/setup/setup.go b/internal/setup/setup.go index 261601d..a456f75 100644 --- a/internal/setup/setup.go +++ b/internal/setup/setup.go @@ -37,9 +37,14 @@ var systemdUnit string //go:embed hamlab.openrc var openrcScript string +//go:embed hamlab.procd +var procdScript string + const ( - unitPath = "/etc/systemd/system/hamlab.service" - openrcPath = "/etc/init.d/hamlab" + unitPath = "/etc/systemd/system/hamlab.service" + 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" ) @@ -228,6 +233,19 @@ func Install(args []string, version string) error { 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"): initSys = "OpenRC" 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) switch { 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 healthy(cfg.Listen, cfg.TLS == "auto"): say("\nHam Lab is up: %s", url) @@ -294,9 +312,15 @@ func Uninstall(args []string) error { _ = run("systemctl", "daemon-reload") } if b, err := os.ReadFile(openrcPath); err == nil { - dataDir = configDirFrom(string(b), "export HAMLAB_CONFIG=") - _ = run("rc-service", "hamlab", "stop") - _ = run("rc-update", "del", "hamlab", "default") + 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=") + _ = run("rc-service", "hamlab", "stop") + _ = run("rc-update", "del", "hamlab", "default") + } os.Remove(openrcPath) } os.Remove(sudoersPath)