Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
363ff02bda | ||
|
|
b5454c1022 |
@@ -2,7 +2,6 @@
|
||||
VERSION ?= $(shell git describe --tags --always 2>/dev/null || echo dev)
|
||||
GO ?= go
|
||||
PREFIX ?= /usr/local/bin
|
||||
PUBLISH ?= /home/core/apps/hamlab-demo/site
|
||||
|
||||
# every architecture a release carries; arm is ARMv6 hard-float, so it covers every Raspberry Pi
|
||||
ARCHES ?= amd64 arm64 arm 386 riscv64
|
||||
@@ -10,7 +9,7 @@ LDFLAGS = -s -w -X main.version=$(VERSION)
|
||||
# static, no cgo: the same file runs on glibc, musl (Alpine) and anything else with a Linux kernel
|
||||
GOBUILD = CGO_ENABLED=0 GOOS=linux GOARM=6 $(GO) build -trimpath -ldflags "$(LDFLAGS)"
|
||||
|
||||
.PHONY: all web build go dev cross release publish gitea-release deploy push-nodes push-nodes-force test clean
|
||||
.PHONY: all web build go dev cross release gitea-release deploy push-nodes push-nodes-force test clean
|
||||
|
||||
all: build
|
||||
|
||||
@@ -61,9 +60,7 @@ gitea-release: release
|
||||
deploy: build cross
|
||||
./bin/hamlab install --prefix $(PREFIX)
|
||||
@sleep 1; $(PREFIX)/hamlab push-nodes || true
|
||||
@# The public installer must never lag the fleet: it would offer everyone an older build, and
|
||||
@# "upgrade" a current machine backwards. Publish only where this hub serves the download site.
|
||||
@if [ -d $(PUBLISH) ]; then $(MAKE) --no-print-directory publish; fi
|
||||
@echo "deployed $(VERSION); 'make gitea-release' publishes it for everyone else"
|
||||
|
||||
# push the installed binary to every managed machine (what deploy does last)
|
||||
push-nodes:
|
||||
@@ -75,13 +72,3 @@ push-nodes-force:
|
||||
|
||||
clean:
|
||||
rm -rf bin dist internal/web/dist web/dist
|
||||
|
||||
# Put the release where `curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh` finds it:
|
||||
# the static site beside the demo page (nginx, public through NPM). Binaries first, VERSION
|
||||
# last, so nobody is ever pointed at files that aren't there yet.
|
||||
publish: release
|
||||
mkdir -p $(PUBLISH)/dl
|
||||
cp dist/hamlab-linux-* dist/SHA256SUMS $(PUBLISH)/dl/
|
||||
cp dist/install.sh $(PUBLISH)/dl/install.sh && cp dist/install.sh $(PUBLISH)/install.sh
|
||||
cp dist/VERSION $(PUBLISH)/dl/VERSION
|
||||
@echo "published $(VERSION): curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh"
|
||||
|
||||
@@ -186,7 +186,12 @@ something long in one, switch away and back, refresh — it's all still there.
|
||||
a URL), status sensors (services, updates, ZFS, SMART), a widget library, custom widgets
|
||||
on the Dashboard tab (briefly renamed "Monitors" 2026-09-19, back to "Dashboard"
|
||||
2026-09-20 at Mitchell's request; the view id and URL were always `dashboard`), and a
|
||||
frameable `/embed/widget` for Glance and friends. The stock Dashboard modules and the Disks
|
||||
frameable `/embed/widget` for Glance and friends. (An external demo page was deployed at
|
||||
`hamlab-demo.bigham.dev` and linked from the Widgets tab 2026-09-19; **both removed
|
||||
2026-09-23** at Mitchell's request — the Widgets tab shows the same thing against the
|
||||
machine you are signed in to, so the second copy earned nothing and cost a public
|
||||
read-only token, which was revoked with it. `examples/external-demo/` stays as an example
|
||||
to read.) The stock Dashboard modules and the Disks
|
||||
tab read the sensor API too (`sampleAdapter.ts`); `/api/metrics` is retired.
|
||||
Also per-service and per-container sensors (memory and CPU from cgroups), tokens limited
|
||||
to ID patterns, and `/api/fleet/sensors/query` for the whole fleet in one request.
|
||||
@@ -398,6 +403,16 @@ something long in one, switch away and back, refresh — it's all still there.
|
||||
**not** part of `make deploy`, because deploy pushes this build to the machines this hub
|
||||
owns while publishing offers it to every install anywhere, and one tag per deploy would
|
||||
be noise. Re-publishing a version needs `--force`.
|
||||
**Public since 2026-09-23**, at Mitchell's instruction and after the trade was put to him
|
||||
twice: Gitea's instance-wide `REQUIRE_SIGNIN_VIEW` is off and `mitchell/hamlab` is marked
|
||||
public, so anyone can browse it, clone it and download a release with no account. The
|
||||
other five repos there are each marked private and are invisible — an anonymous request
|
||||
for one is a 404, not a login prompt — registration is still disabled, and new repos
|
||||
still default to private. No token is needed by anything any more (the read-only one was
|
||||
revoked; only `hamlab-publish`, which creates releases, remains). Gitea resolves
|
||||
`releases/download/latest/<asset>`, so `install.sh` and `HAMLAB_URL` now point straight
|
||||
at the newest release and never need changing. What is public is the source, the history,
|
||||
`BUGS.md` and this file — which describes the lab and names the console's hostname.
|
||||
- **Auth hardening** — WebAuthn passkeys (phone Face ID), TOTP fallback, optional NPM
|
||||
access list restricting to WireGuard/LAN.
|
||||
- **Files tab** — **shipped 2026-09-18** (`internal/files`, `Files.tsx`): browse from
|
||||
@@ -426,28 +441,6 @@ something long in one, switch away and back, refresh — it's all still there.
|
||||
|
||||
## To do
|
||||
|
||||
- **Somewhere public to download from** — asked for 2026-09-22, half done. The **updater**
|
||||
shipped 2026-09-23 (above) and reads releases from Gitea, but `git.bigham.dev` runs with
|
||||
`REQUIRE_SIGNIN_VIEW=true`, so those releases are reachable only with a token. That is
|
||||
fine for this fleet — each machine carries a read-only `read:repository` token — and no
|
||||
use at all to a stranger running the `install.sh` one-liner, who still gets the rolling
|
||||
copy on `hamlab-demo.bigham.dev`. What is left is deciding where a public download
|
||||
actually lives:
|
||||
- **Lower the wall on Gitea.** `REQUIRE_SIGNIN_VIEW=false` and per-repo visibility does
|
||||
the rest: every repo there is private and stays hidden, and only a repo marked public
|
||||
is visible. That is how Gitea is meant to be configured, and it is the one change that
|
||||
makes "releases on my Gitea" literally true. It is an instance-wide posture change on
|
||||
a public hostname, so it is Mitchell's call — the same trade was declined once before
|
||||
(`docs/docker.md`, the demo site's `install.sh` is hosted outside Gitea for exactly
|
||||
this reason).
|
||||
- **Or keep the wall and keep the demo site** as the public face: `make publish` already
|
||||
writes the rolling `dl/` directory there, and a generated index page (version, built
|
||||
when, a row per architecture with size and checksum, the one-line install, and that
|
||||
the binaries are unsigned and served from his own box) would finish the "page" half
|
||||
without touching Gitea at all.
|
||||
- Either way the release notes and version history live on Gitea, and the updater does
|
||||
not care which of the two it is pointed at.
|
||||
|
||||
- **Deprecate copyparty** — asked for 2026-09-23, not started. `copyparty` on `grid`
|
||||
(`copyparty.service`, `:3923`, `/etc/copyparty.conf`, serving all of `/tank` as user
|
||||
`core` with one account behind NPM) overlaps the **Files** tab almost entirely. The aim
|
||||
|
||||
@@ -20,15 +20,16 @@ sudo ./hamlab-linux-amd64 install # that's the whole install
|
||||
or, from wherever `make release` output is served:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh # asks: user, address, HTTPS, firewall, password
|
||||
curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh -s -- --yes # asks nothing, all defaults
|
||||
curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh -s -- --listen 0.0.0.0:8100 --tls auto # flags go to `hamlab install`
|
||||
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.
|
||||
`make publish` puts a release where that URL serves it (`HAMLAB_URL` points it elsewhere); run it beside the `hamlab-linux-*` files and it installs offline.
|
||||
`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;
|
||||
|
||||
@@ -12,10 +12,10 @@ window.HAMLAB = { base: 'http://10.0.0.234:8100', token: '<read-only sensor toke
|
||||
```
|
||||
|
||||
Anyone who can load the page can read the token. If the page is public, limit the token to
|
||||
patterns that name nothing you'd mind the world reading (the `hamlab-demo` one shows
|
||||
hardware numbers and counts, not container or service names). Deployed on `core` as the `hamlab-demo` stack —
|
||||
see Hamlet `docs/docker.md`. Update it with
|
||||
`cp examples/external-demo/index.html /home/core/apps/hamlab-demo/site/`.
|
||||
patterns that name nothing you'd mind the world reading — hardware numbers and counts,
|
||||
not container or service names.
|
||||
|
||||
To link it from the Widgets tab, set `"demo_url": "http://…"` in the hub's `config.json`
|
||||
and restart Ham Lab.
|
||||
This is an example to read and copy, not something that runs anywhere. It was deployed on
|
||||
`core` as the `hamlab-demo` stack and linked from the Widgets tab until 2026-09-23, when
|
||||
both were removed: the Widgets tab shows the same widgets against the machine you are
|
||||
already signed in to, so a second public copy earned nothing and cost a public token.
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Ham Lab installer.
|
||||
#
|
||||
# curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh
|
||||
# curl -fsSL https://git.bigham.dev/mitchell/hamlab/releases/download/latest/install.sh | sh
|
||||
#
|
||||
# With a terminal it walks you through it: who the console runs as, where it listens,
|
||||
# HTTPS, the firewall, the password — then downloads the build for this machine, checks
|
||||
@@ -15,6 +15,8 @@
|
||||
# ./install.sh # beside the hamlab-linux-* files: installs offline
|
||||
#
|
||||
# HAMLAB_URL where hamlab-linux-<arch>, SHA256SUMS and VERSION are served from —
|
||||
# by default the newest release on Gitea; "latest" is an alias Gitea
|
||||
# resolves to the most recent release, so the URL never needs changing —
|
||||
# any static file host, or a Gitea/Forgejo release's download URL
|
||||
# HAMLAB_TOKEN access token, when that place is a private Gitea/Forgejo repo
|
||||
# NO_COLOR plain output
|
||||
@@ -22,7 +24,7 @@
|
||||
# POSIX sh: runs under dash, busybox ash and bash alike.
|
||||
set -eu
|
||||
|
||||
HAMLAB_URL="${HAMLAB_URL:-https://hamlab-demo.bigham.dev/dl}"
|
||||
HAMLAB_URL="${HAMLAB_URL:-https://git.bigham.dev/mitchell/hamlab/releases/download/latest}"
|
||||
BUILT_FOR="@VERSION@" # filled in by `make release`
|
||||
|
||||
# ─────────────────────────────────────────────────────────────── looks ──
|
||||
|
||||
@@ -1013,9 +1013,8 @@ func (s *Server) me(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
_, rebootErr := os.Stat("/run/reboot-required")
|
||||
updateVersion, _ := s.behind()
|
||||
var demoURL, addr string
|
||||
var addr string
|
||||
s.cfg.View(func(c *config.Config) {
|
||||
demoURL = c.DemoURL
|
||||
if h, _, err := net.SplitHostPort(c.Listen); err == nil && h != "" && h != "0.0.0.0" && h != "::" {
|
||||
addr = h // the address this console listens on, for the machine menu
|
||||
}
|
||||
@@ -1023,7 +1022,7 @@ func (s *Server) me(w http.ResponseWriter, r *http.Request) {
|
||||
jsonOK(w, 200, map[string]any{
|
||||
"host": s.hostName(), "user": s.user, "account": actorOf(r).User, "version": s.version,
|
||||
"load": load, "uptime": uptime, "service_started": s.started,
|
||||
"reboot_needed": rebootErr == nil, "fingerprint": s.fingerprint, "arch": runtime.GOARCH, "demo_url": demoURL, "addr": addr,
|
||||
"reboot_needed": rebootErr == nil, "fingerprint": s.fingerprint, "arch": runtime.GOARCH, "addr": addr,
|
||||
"update_available": updateVersion,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,9 +70,6 @@ type Config struct {
|
||||
TLS string `json:"tls,omitempty"` // "auto" → self-signed cert in the data dir; "" → plain HTTP
|
||||
Tokens []Token `json:"tokens,omitempty"`
|
||||
Nodes []Node `json:"nodes,omitempty"`
|
||||
// DemoURL, when set, is linked from the Widgets tab: a page outside Ham Lab that
|
||||
// shows the sensor API being used (examples/external-demo).
|
||||
DemoURL string `json:"demo_url,omitempty"`
|
||||
// Updates: where a newer build comes from. See UpdateSource.
|
||||
Updates UpdateSource `json:"update,omitempty"`
|
||||
|
||||
|
||||
+1
-2
@@ -31,7 +31,7 @@ function Tile({ spec, data, note }: { spec: WidgetSpec; data: WidgetData; note:
|
||||
)
|
||||
}
|
||||
|
||||
export default function Gallery({ demoUrl }: { demoUrl?: string }) {
|
||||
export default function Gallery() {
|
||||
const [cat, setCat] = useState<Sensor[] | null>(null)
|
||||
const [err, setErr] = useState('')
|
||||
const rate = useRefreshMs()
|
||||
@@ -61,7 +61,6 @@ export default function Gallery({ demoUrl }: { demoUrl?: string }) {
|
||||
<span className="hint">{needle ? `${shown.length} of ${sensors.length}` : sensors.length} sensors on this machine · live · tap a tile to copy its ID</span>
|
||||
<input className="gallery-find" value={find} onChange={(e) => setFind(e.target.value)} placeholder="find — gitea, temp, enp3s0…" spellCheck={false} autoCapitalize="off" />
|
||||
<span className="spacer" />
|
||||
{demoUrl && <a className="btn" href={demoUrl} target="_blank" rel="noreferrer" title="a page outside Ham Lab, built on the same sensor API">External demo ↗</a>}
|
||||
</div>
|
||||
|
||||
{!needle && <h2 className="section">Widget types</h2>}
|
||||
|
||||
+1
-1
@@ -269,7 +269,7 @@ export default function Shell({ me: hubMe, onLoggedOut }: { me: Me; onLoggedOut:
|
||||
{view === 'space' && <Space key={machine ?? ''} />}
|
||||
{view === 'machines' && <Machines hub={hubMe} onChanged={loadMachines} onOpen={(n) => { switchMachine(n); setView('dashboard') }} />}
|
||||
{view === 'dashboard' && <Dashboard />}
|
||||
{view === 'widgets' && <Gallery demoUrl={hubMe.demo_url} />}
|
||||
{view === 'widgets' && <Gallery />}
|
||||
{view === 'docker' && <Docker />}
|
||||
{view === 'alerts' && <Alerts machines={machines.map((m) => m.name)} titleOf={(n) => titleOf(n === hubMe.host ? null : n, machines, hubMe.host)} />}
|
||||
{view === 'top' && <Top />}
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ async function req<T>(method: string, path: string, body?: unknown, local = fals
|
||||
}
|
||||
|
||||
export interface UpdateStatus { current: string; latest: string; arch: string; behind: boolean; checking: boolean; installing: boolean; enabled: boolean; auto: boolean; url: string; notes?: string; published?: string; checked?: string; error?: string }
|
||||
export interface Me { demo_url?: string; addr?: string; host: string; user: string; account?: string; update_available?: string; version: string; load: string; uptime: number; reboot_needed?: boolean; fingerprint?: string; arch?: string }
|
||||
export interface Me { addr?: string; host: string; user: string; account?: string; update_available?: string; version: string; load: string; uptime: number; reboot_needed?: boolean; fingerprint?: string; arch?: string }
|
||||
export interface PushResult { name: string; ok: boolean; version?: string; error?: string }
|
||||
// What a machine is called on screen: the hostname it reports, which follows a rename within
|
||||
// a poll. The name it was added under stays its identity — the URL, its accent colour, and
|
||||
|
||||
Reference in New Issue
Block a user