diff --git a/Makefile b/Makefile index 08e0887..ebb7b33 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,11 @@ go: dev: cd web && npm run dev -deploy: build +# arm64 sibling for aarch64 machines (Mac mini); the hub pushes it to them +cross: + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -trimpath -ldflags "-s -w -X main.version=$(VERSION)" -o bin/hamlab-linux-arm64 ./cmd/hamlab + +deploy: build cross ./deploy/install.sh @sleep 1; $(PREFIX)/hamlab push-nodes || true diff --git a/PLAN.md b/PLAN.md index 2e21461..75e59d1 100644 --- a/PLAN.md +++ b/PLAN.md @@ -200,7 +200,10 @@ something long in one, switch away and back, refresh — it's all still there. it last, so one deploy on the hub updates every machine. First install on a new host is still by hand (scp binary + `install.sh`, `hamlab passwd`, `tls auto`). Still to do: Phase 2 Datacenter overview; optional firewall rule limiting a node's port to - the hub; cross-architecture builds for the Mac mini. + the hub. **Cross-architecture** (2026-09-18): `make deploy` also builds + `bin/hamlab-linux-arm64`, `install.sh` installs it beside the binary as + `hamlab-arm64`, and the hub picks the sibling matching a node's `/api/me.arch` when + pushing — the Mac mini (Fedora Asahi, aarch64, node `mac`) updates like the rest. - **Auth hardening** — WebAuthn passkeys (phone Face ID), TOTP fallback, session list + revoke, optional NPM access list restricting to WireGuard/LAN. - **Files tab** — **shipped 2026-09-18** (`internal/files`, `Files.tsx`): browse from diff --git a/README.md b/README.md index c698517..9637e55 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,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. - On `core` it is public at `https://hamlab.bigham.dev` behind Nginx Proxy Manager. - Installable as a PWA. diff --git a/deploy/install.sh b/deploy/install.sh index 639d150..d0c5ebd 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -19,6 +19,10 @@ sed -e "s|@USER@|$USER_NAME|g" -e "s|@GROUP@|$GROUP_NAME|g" -e "s|@HOME@|$HOME_D deploy/hamlab.service > "$unit" sudo install -m 0755 bin/hamlab "$PREFIX/hamlab" +# other-architecture builds sit beside the binary so the hub can push them to matching machines +for alt in bin/hamlab-linux-*; do + [ -f "$alt" ] && sudo install -m 0755 "$alt" "$PREFIX/hamlab-${alt##*-linux-}" +done sudo install -m 0644 "$unit" /etc/systemd/system/hamlab.service rm -f "$unit" sudo systemctl daemon-reload diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 6f11ba2..5bbe2cd 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -914,6 +914,12 @@ var cpuModel = func() string { } } } + // ARM boards don't put a model name in cpuinfo; the device tree names the machine. + for _, p := range []string{"/sys/firmware/devicetree/base/model", "/proc/device-tree/model", "/sys/devices/virtual/dmi/id/product_name"} { + if m := strings.TrimRight(strings.TrimSpace(readStr(p)), "\x00"); m != "" { + return m + } + } return "" }() diff --git a/internal/nodes/nodes.go b/internal/nodes/nodes.go index 3867e75..9218bef 100644 --- a/internal/nodes/nodes.go +++ b/internal/nodes/nodes.go @@ -313,8 +313,13 @@ func (m *Manager) Push(ctx context.Context, name, binPath, ourArch string) PushR _ = json.NewDecoder(io.LimitReader(resp.Body, 64<<10)).Decode(&me) resp.Body.Close() if me.Arch != "" && ourArch != "" && me.Arch != ourArch { - res.Error = fmt.Sprintf("architecture mismatch: node is %s, this binary is %s — build for it and deploy by hand", me.Arch, ourArch) - return res + // look for a sibling build: /usr/local/bin/hamlab-arm64 next to /usr/local/bin/hamlab + alt := binPath + "-" + me.Arch + if _, err := os.Stat(alt); err != nil { + res.Error = fmt.Sprintf("node is %s but only a %s build is installed here (expected %s; run make deploy, which builds it)", me.Arch, ourArch, alt) + return res + } + binPath = alt } f, err := os.Open(binPath) if err != nil {