Portable install: the binary installs itself; static builds on five architectures; root via sudo, doas or uid 0; every package manager; older systemd; no lsblk

- hamlab install / uninstall (internal/setup): systemd or OpenRC, elevates through
  sudo or doas, runs as the invoking user, sets or generates the password, names only
  journal groups that exist, waits for /healthz, reports an active firewalld/ufw.
  --purge and the data chown never touch a directory that isn't Ham Lab's alone.
- install.sh: curl-able bootstrap (arch detection, SHA256SUMS, offline beside the
  binaries, noexec /tmp fallback). make release builds dist/ for amd64, arm64, arm
  (ARMv6), 386, riscv64. Every build is CGO_ENABLED=0; the native one used to link glibc.
- internal/priv replaces every literal `sudo -n`: uid 0, sudo or doas.
- Updates acts on dnf, zypper, pacman and apk as well as apt; dnf shows installed versions.
- Services falls back to the plain systemctl table before systemd 246 and says so on
  hosts without systemd. Disks falls back to /sys/block without lsblk.
- Listener binds with IP_FREEBIND, so a LAN address that isn't up at boot is no failure.
- Self-update: the new build must pass `check` against the live config, is put in
  place by atomic rename, and the daemon re-execs itself (same under any supervisor).
- State and config writes are fsynced; a corrupt state.json is set aside, not fatal.
- Terminal shutdown no longer empties the saved list, so names come back after a
  restart by design rather than by winning a race.
- Config default is ~/.config/hamlab; an existing ~/apps/hamlab is still found.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-18 23:00:20 -07:00
co-authored by Claude Fable 5.1
parent 91b7f4f009
commit 488d929733
26 changed files with 1617 additions and 178 deletions
+51 -16
View File
@@ -1,6 +1,6 @@
# Ham Lab
Web-based Linux management console for bare-metal Debian, in the style of the Proxmox
Web-based Linux management console for bare-metal Linux, in the style of the Proxmox
web UI. Named after Ham the cat (see also HamTV).
Status: **v0.1** — persistent web terminals and a live Dashboard tab. Scope and roadmap in
@@ -8,27 +8,62 @@ Status: **v0.1** — persistent web terminals and a live Dashboard tab. Scope an
## Runs on any Linux box
One static Go binary with the UI embedded; no cgo, no Node at runtime, no agents. The
Dashboard reads `/proc` and `/sys` directly (CPU, memory, hwmon or thermal-zone temps,
fans, NICs, block devices, mounts) so it works the same on x86 and ARM, bare metal or VM,
and just leaves a section empty when the hardware has nothing to report. UPS status is
optional and picked up from whichever of `pwrstat`, NUT (`upsc`) or `apcupsd` is
installed.
One static Go binary with the UI embedded: no cgo, no libc, no Node at runtime, no agents.
The same file runs on Debian, Ubuntu, Fedora, RHEL/Rocky/Alma, openSUSE, Arch and Alpine
(musl), on bare metal, VMs and containers. Builds: `amd64`, `arm64`, `arm` (ARMv6
hard-float and up, so every Raspberry Pi), `386`, `riscv64`.
```sh
make build # web + Go, or: make build-arm64 for a cross-compile
./deploy/install.sh # installs the binary + a systemd unit for the invoking user
hamlab passwd # set the login password (config in ~/apps/hamlab/)
sudo systemctl start hamlab # listens on 127.0.0.1:8100 by default; set "listen" in config.json
sudo ./hamlab-linux-amd64 install # that's the whole install
```
or, from wherever `make release` output is served:
```sh
curl -fsSL "$HAMLAB_URL/install.sh" | sh
curl -fsSL "$HAMLAB_URL/install.sh" | sh -s -- --listen 0.0.0.0:8100 --tls auto
```
`install.sh` only picks the build for `uname -m`, checks it against `SHA256SUMS` and runs
`hamlab install`; run it beside the `hamlab-linux-*` files and it installs offline.
`hamlab install`:
- copies the binary to `/usr/local/bin` (`--prefix`), atomically;
- gets root through `sudo` or `doas` if you aren't root already, and runs the console as
**you** (`--user`), so terminals are your login shell with your rights;
- writes the config to `~/.config/hamlab/` (`--data`; an existing `~/apps/hamlab/` is kept)
and asks for the login password, or generates one and prints it once
(`--password-stdin` for scripts);
- registers a boot-time service with **systemd** or **OpenRC**, naming only groups that
exist on the host, and waits for `/healthz`; with neither init it prints the one line
to put under your own supervisor;
- says so when firewalld or ufw will block the port (`--open-firewall` opens it).
Re-running it upgrades in place and keeps config, user and password. `hamlab uninstall
[--purge]` reverses it.
What adapts to the host, rather than assuming Debian:
| | |
|---|---|
| Root | already root, else passwordless `sudo`, else passwordless `doas`; without any, the console is read-only where root is needed and says so |
| Updates tab | apt, dnf, zypper, pacman, apk: list, refresh, upgrade, install (pacman: all or nothing) |
| Services / timers | systemd of any age (JSON output where it exists, the plain table before v246); a clear message on hosts without systemd |
| Disks | `lsblk`, or `/sys/block` when it's missing (busybox) or too old; SMART and ZFS only when their tools are there |
| Dashboard | `/proc` and `/sys` only: hwmon or thermal-zone temps, fans, NICs, disks, mounts; empty sections where the hardware has nothing to report. UPS from whichever of `pwrstat`, NUT (`upsc`) or `apcupsd` is installed |
| Listen address | bound with `IP_FREEBIND`, so a LAN address that isn't up yet at boot doesn't fail the start |
| Self-update | the new build must run and load this host's config before it replaces the old one; atomic rename; the daemon then re-execs itself, so it restarts the same way under systemd, OpenRC or nothing; `hamlab.prev` kept |
Building: `make build` (web + Go), `make release` (every architecture, `install.sh` and
`SHA256SUMS` in `dist/`), `make test`.
Terminals run as the user the service runs as, with that user's login shell.
## Managing several machines
Install Ham Lab on each host. On the ones the hub should reach, add `"tls": "auto"` and a
LAN `listen` address to `config.json` (a self-signed certificate is generated on first
start). Then on the hub, open **Machines**, enter a name, the address
Install Ham Lab on each host; on the ones the hub should reach:
`hamlab install --listen <lan-ip>:8100 --tls auto` (a self-signed certificate is generated
on first start). Then on the hub, open **Machines**, enter a name, the address
(`https://host:8100`) and that machine's Ham Lab password once. The hub pins the
certificate, keeps an access token, and every tab works against the chosen machine
through the switcher at the top of the rail.
@@ -36,8 +71,8 @@ through the switcher at the top of the rail.
Updating: `make deploy` on the hub installs the new build there and then pushes it to
every managed machine (`hamlab push-nodes`), which install it and restart. The Machines
page flags machines running an older version and offers the same push per machine.
Mixed architectures are fine: `make deploy` also cross-compiles arm64, and the hub sends
each machine the build for its own architecture.
Mixed architectures are fine: `make deploy` also cross-compiles every other architecture,
and the hub sends each machine the build for its own.
- On `core` it is public at `https://hamlab.bigham.dev` behind Nginx Proxy Manager.
- Installable as a PWA.