Files
hamlab/README.md
T
Mitchell-JohnsonandClaude Opus 5 8a7167bf03 A guest login: temporary, look-only, revocable
Users -> Guest logins makes a throwaway user name and password, good for half
an hour by default, to hand to somebody who wants to see the console. It is a
normal account with `guest: true` and an expiry, so login, sessions and the
saved look need no special case, and it is left out of the account list, out of
"the last account can't be removed", and out of the one account an empty user
name means.

What a guest may do is one allow-list, checked in the auth middleware, rather
than a flag ~190 handlers have to remember. The list is matched by asking the
router which handler a request would reach, so it cannot drift from the routes
it names, and it is checked against the scoped path — /api/nodes/grid/procs is
/api/procs on grid — because the hub proxies to a node with a token that is
full control of that node. The node cannot tell a guest from its owner, so the
only place it can be stopped is here, before the request leaves.

A guest gets the dashboard, containers, stacks, disks, hardware, ZFS and usage,
on this machine and on any managed one, and nothing else: no terminal (attaching
is a GET, and it is a root shell), no files, no container logs or environments,
no stack files, no alert channels, no cron, no tokens, no settings, no power.

Expiry is enforced on every request and swept once a minute, so a console left
open on a visitor's phone stops within the minute rather than at their next
click; revoking signs them out at once. The console hides what a guest cannot
use and says how long is left where the power button would be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:23:16 -07:00

6.2 KiB

Ham Lab

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 PLAN.md.

Runs on any Linux box

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.

sudo ./hamlab-linux-amd64 install          # that's the whole install

or, from wherever make release output is served:

curl -fsSL https://git.bigham.dev/mitchell/hamlab/releases/download/latest/install.sh | sh        # asks: user, address, HTTPS, firewall, password
curl -fsSL https://git.bigham.dev/mitchell/hamlab/releases/download/latest/install.sh | sh -s -- --yes                              # asks nothing, all defaults
curl -fsSL https://git.bigham.dev/mitchell/hamlab/releases/download/latest/install.sh | sh -s -- --listen 0.0.0.0:8100 --tls auto   # flags go to `hamlab install`

install.sh is a wizard when there is a terminal (it reads answers from /dev/tty, so it works piped from curl) and silent otherwise. Either way it picks the build for uname -m, checks it against SHA256SUMS and runs hamlab install; re-running it upgrades in place. latest is an alias Gitea resolves to the newest release, so that URL never changes. make gitea-release publishes a release there (HAMLAB_URL points the installer elsewhere); run the script 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 first account's password (the account is named after the user the console runs as), 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 accounts. hamlab uninstall [--purge] reverses it.

Accounts: every person has their own user name and password, and the theme, accent colours and dashboard layout follow the account. The Users tab has it all: your own password, where you are signed in (sign out any device), and the accounts (add, remove, reset a password; your own password confirms the last two). From the shell: hamlab passwd [user] sets a password and makes the account if it is new, hamlab users lists them, hamlab user-rm <user> removes one. Machine settings — refresh rate, graph history, alert rules — stay the machine's and are shared by everyone.

Guest logins, for showing somebody the console: Users → Guest logins makes a throwaway user name and password, good for half an hour by default, and hands you the pair to paste into a message. A guest sees the dashboard, the containers, the disks, the hardware and the machines this hub manages, and can change none of it — no terminal, no files, no logs or container environments, no settings, no power. It can be revoked in one tap, and it disappears on its own when the time is up, wherever it is signed in. What a guest may reach is a list of routes in internal/api/guests.go, so a route added later is shut to guests until somebody says otherwise.

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: 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 a user and password on that machine once (the user may be left out when it has only one). 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.

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 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.