Files
hamlab/internal/setup/setup_test.go
T
Mitchell-JohnsonandClaude Fable 5.1 488d929733 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>
2026-09-18 23:00:20 -07:00

74 lines
2.4 KiB
Go

package setup
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestUnitWithoutJournalGroups(t *testing.T) {
vars := map[string]string{"@USER@": "ham", "@GROUP@": "ham", "@HOME@": "/home/ham", "@BIN@": "/usr/local/bin/hamlab",
"@CONFIG@": "/home/ham/.config/hamlab/config.json", "@GROUPS@": ""}
unit := dropLines(fill(systemdUnit, vars), "SupplementaryGroups=")
if strings.Contains(unit, "SupplementaryGroups") || strings.Contains(unit, "@") {
t.Fatalf("unit still has a groups line or an unfilled placeholder:\n%s", unit)
}
if !strings.Contains(unit, "ExecStart=/usr/local/bin/hamlab serve") {
t.Fatalf("ExecStart missing:\n%s", unit)
}
}
func TestConfigDirFrom(t *testing.T) {
for in, want := range map[string]string{
"Environment=HAMLAB_CONFIG=/home/ham/.config/hamlab/config.json": "/home/ham/.config/hamlab",
`export HAMLAB_CONFIG="/root/.config/hamlab/config.json"`: "/root/.config/hamlab",
"Environment=HAMLAB_CONFIG=/config.json": "",
"Environment=HAMLAB_CONFIG=/etc/passwd": "",
} {
key := "Environment=HAMLAB_CONFIG="
if strings.HasPrefix(in, "export") {
key = "export HAMLAB_CONFIG="
}
if got := configDirFrom(in, key); got != want {
t.Errorf("%q: got %q want %q", in, got, want)
}
}
}
// --purge must never take a shared directory with it.
func TestPurgeSparesSharedDirectory(t *testing.T) {
home := t.TempDir()
os.WriteFile(filepath.Join(home, "config.json"), []byte("{}"), 0o600)
os.WriteFile(filepath.Join(home, "thesis.txt"), []byte("irreplaceable"), 0o600)
if err := purgeData(home); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(home, "thesis.txt")); err != nil {
t.Fatal("purge deleted a file that wasn't Ham Lab's")
}
if _, err := os.Stat(filepath.Join(home, "config.json")); err == nil {
t.Fatal("purge left config.json behind")
}
own := filepath.Join(t.TempDir(), "hamlab")
os.MkdirAll(own, 0o700)
os.WriteFile(filepath.Join(own, "config.json"), []byte("{}"), 0o600)
if err := purgeData(own); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(own); err == nil {
t.Fatal("purge left Ham Lab's own directory behind")
}
}
func TestFirstMissing(t *testing.T) {
base := t.TempDir()
if got := firstMissing(filepath.Join(base, "a", "b", "c")); got != filepath.Join(base, "a") {
t.Errorf("got %q", got)
}
if got := firstMissing(base); got != "" {
t.Errorf("existing dir: got %q", got)
}
}