Files
felhom.eu/documentation/operations/nodes.md
T
admin 0fc54e0122
gates / gates (push) Successful in 7s
hub v0.88.0 — the WAL that never was (R-172)
store.New opened the DB with `?_journal_mode=WAL&_busy_timeout=5000`, which is
mattn/go-sqlite3 syntax. The driver is modernc.org/sqlite, whose applyQueryParams
reads only _pragma/_time_format/_time_integer_format/_txlock/_inttotime and
IGNORES anything else WITHOUT AN ERROR. So the hub ran in rollback-journal mode
with busy_timeout=0 for its entire life while its own source said otherwise.

Surfaced as a false HOST STALE banner: in rollback-journal mode a reader excludes
a writer, so rendering an operator page blocks a host report; the hub 500s, the
agent waits its full 15-minute interval without retrying, and staleness fires at
30 minutes — two collisions is a false alarm plus an operator email. 13 collisions
in one pod lifetime; the alarm fired twice on 2026-08-02 for a host that was up
two days and reconciling throughout.

The observable that proved it: a 128 MB /data/hub.db with no -wal/-shm beside it
while the DB was open.

Fix: ?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)&_txlock=immediate.
_txlock=immediate is not optional — database/sql's Begin() is DEFERRED, so a
read-then-write tx must upgrade its lock and a failed upgrade is
SQLITE_BUSY_SNAPSHOT, which busy_timeout does NOT retry; this store has 10+
db.Begin() sites and they are all write paths.

Every test asserts what the DATABASE reports, never the DSN string — a string
test would have passed for the whole life of the bug. Red-proof: restoring the
shipped DSN reproduces journal_mode="delete", the missing -wal, and the live
"database is locked (5) (SQLITE_BUSY)".

Operational consequence handled: a WAL DB cannot be copied by taking hub.db
alone — a bare `cat` opens cleanly and silently omits the newest writes. The
break-glass retrieval in operations/nodes.md used exactly that; it and the
recovery-inventory note are now WAL-aware.
2026-08-02 21:06:29 +02:00

13 KiB
Raw Blame History

Fleet node inventory — the physical demo/lab hosts

Added 2026-07-21, when the fleet stopped being one box. Two Proxmox hosts now check in to the hub. This is the operator-facing inventory: what the hardware is, how to reach it, and what is deliberately NOT enrolled on it.

The fleet

demo-felhom-8363b5 demo-hp-bb76ea
Hardware N100 mini-PC HP t740 Thin Client (s/n 8CN944035T)
CPU / RAM Intel N100 AMD Ryzen Embedded V1756B, 8 threads / 30 GiB
Firmware AMI AN3PLUS-class AMI M42 v01.10 (11/11/2020)
PVE node name demo-felhom felhom-host
Customer demo-felhom demo-hp
Agent / controller not recorded here — see the note below not recorded here
Control plane island 169.254.253.1:8443 on vmbr9 (R-50, migrated 2026-07-25) island 169.254.253.1:8443 on vmbr9 (R-50, migrated 2026-07-25)
SSH alias felhom-pve demo-hp
Tailnet 100.70.170.35 100.76.96.79
Loader used to install mkimage (unsigned, SB off — firmware workaround) shim, Secure Boot ENABLED

No component versions are recorded on this page — deliberately. Agent, controller, hub and host-install versions change several times a day, so any number written here is wrong within hours and is then read as fact. Ask the fleet instead: the hub host list (/hosts) and customer list (/configs) carry the live agent and controller versions per box; felhom-agent --version on the node and pct exec <vmid> -- docker ps in the guest are the authorities. Versions that must be pinned in writing belong in the per-repo CHANGELOG.md and the hub's Day-0 artifact manifest — not in an inventory. The fleet is not uniform: on 2026-07-30 the two boxes ran different agent and different controller versions, so a single number for "the fleet" would have been wrong regardless.

Both are at the VACATION site and travel home with the rest of the kit ~2026-08-02. While away, their LAN addresses are on that site's 192.168.0.0/24: N100 .147/.162, HP .87re-check rather than trusting these (ip -br addr show vmbr0; the N100 read .162 on 2026-07-30). The tailnet addresses are the stable ones — use those. Direct LAN literals are not reachable from DooPlex while the boxes are away (felhom-pve-lanNo route to host, 2026-07-30).

Which box is safe to break, and what may be done to each: ../runbooks/target-selection.md. This page is what the hardware is; that page is what you may do to it.

demo-hp — the HP t740, in detail

Disks

device model serial role
sda SanDisk X600 M.2 2280 SATA 128GB 182195804614 system disk — PVE, LVM (pve-root 39.6G, pve-data thin pool, guest 9201's three volumes)
nvme0n1 KXG50PNV1T02 NVMe TOSHIBA 1024GB 58BS11AFT8MQ ENROLLED — mounted /mnt/nvme-1tb, the enrolled user-data drive and the felhom-backup whole-guest backup target

The NVMe joined the product on 2026-07-22, through the normal Tárhely flow, as intended. Enrolled to guest 9201 (PUBLISH TRAIN, pilot/RUNBOOK-publish-agent-0.93-2026-07-22.md); the vzdump target was moved onto it by E-2a. Verified live 2026-07-30: nvme0n1/mnt/nvme-1tb, and dir: felhom-backup / path /mnt/nvme-1tb / is_mountpoint 1 in storage.cfg.

The "PRESENT AND UNENROLLED — do not touch" fence that stood here is RETRACTED, and its reason is recorded so it is not mistaken for a live rule: the NVMe was outside everything because the install ISO's exact-serial filter pinned sda only, and it was to join through the Tárhely flow rather than the installer or by hand. That condition was satisfied; the prohibition expired with it. It stood for eight days after enrolment and contradicted the task specs that (correctly) sent drill-VM disks to /mnt/nvme-1tb.

Still true, and now the operative caution: a dir storage there must sit at the mountpoint root (a subdirectory fails the agent's exactMount check → the storage reads disconnected forever), and it shares the device with the box's own backups — so remove scratch storages when done.

NIC map — and the trap

This board has five wired interfaces, and the obvious one is the wrong one:

interface MAC driver what it is state at this site
enp1s0f0 a0:36:9f:5d:07:20 igb 4-port expansion card no carrier, no DHCP
enp1s0f1 a0:36:9f:5d:07:21 igb no carrier
enp1s0f2 a0:36:9f:5d:07:22 igb no carrier
enp1s0f3 a0:36:9f:5d:07:23 igb no carrier
enp2s0f0 7c:d3:0a:77:d9:76 r8169 onboard port — the one that works carrier up, 1000 Mb, this is vmbr0's port
wlo1 24:ee:9a:e5:05:b0 iwlwifi wifi unused

This trap cost the first install. The 4-port card got no lease, and instead of aborting the installer baked its 192.168.100.2 fallback as a static vmbr0 address and completed — a box that looked installed and could never call home. Repaired on the console by bridging vmbr0 to enp2s0f0. Filed as R-59 (must hard-abort) and R-60 (first-boot NIC sweep self-heal).

Current, post-repair: vmbr0 static 192.168.0.87/24, gw 192.168.0.1, bridge-port enp2s0f0. No trace of 192.168.100.2 remains. wg-felhom 10.77.0.3/32 up to the hub. Guest 9201 demo-hp running. Agent config shape (R-50 island): local_api on 169.254.253.1:8443/vmbr9, guest eth1 169.254.253.2/30, lan_resolver.host_ip pinned to 192.168.0.87.

Designated drill + build VM host (operator ruling, 2026-07-25)

Ruling: drill and build VMs are hosted on the t740 from now on — NOT on felhom-pve, and moving them off DooPlex (the production k3s node). This is a VM-HOSTING ruling only; the build-PIPELINE relocation to the t740 is NOT ruled or implemented here.

Current state (updated 2026-07-25 PM): the ruling is realized — the t740 now hosts the first drill appliance. QEMU VM 300 = drill-r50, a nested PVE-in-a-VM (8 GiB RAM, 4 vCPU cpu=host, 32 GiB local-lvm disk, OVMF/SB-off, one NIC on vmbr0 DHCP). Installed from felhom-pve-9.2-1-v1.25.0-nested-vm-generic-mkimage.iso through the real day-0 (self-register → operator bind to scratch customer drill-r50 → deliver → nested guest 9201 provisioned and healthy on the then-current controller + agent). Its own break-glass root is vaulted in the hub host_recovery/drill-r50-0a4f9a; reach it as root@192.168.0.176 through demo-hp (it has no key and no tailnet — it is a peer on demo-hp's LAN). It has snapshot r50pre (clean LAN-literal day-0). This VM was provisioned to unblock and run the R-50 island-bridge empirical spike (audits/SPIKE-island-bridge-2026-07-25.md) — probes P1P8 PASSED 2026-07-25, verdict GO; the drill was left in its working island configuration. It is a throwaway: destroy with qm stop 300 && qm destroy 300 --purge 1 (and delete the drill-r50 customer + appliance #9 hub-side) when no longer needed. The historical golden-bake drill.qcow2 still lives on DooPlex (/mnt/5_hdd/felhom.eu/drill/, ~18G, powered off) and is unrelated. The build-PIPELINE relocation to the t740 remains unbuilt.

Access — there is no baked SSH key

ssh demo-hp resolves to the tailnet address, but no operator public key is on this box — the HP profile deliberately left FELHOM_ROOT_SSH_KEY blank. Authentication is the G1 break-glass root password vaulted in the hub, host_recovery row demo-hp-bb76ea (set at day-0, 2026-07-21 16:24 UTC).

Retrieval (operator-side, and shred the copy — that DB holds every host's secret):

WAL-AWARE SINCE HUB v0.88.0 — copying hub.db ALONE is no longer safe. The hub runs SQLite in WAL mode (R-172), so a committed transaction may still live in hub.db-wal and not yet be in the main file. A bare cat /data/hub.db therefore yields a copy that is valid but stale — it opens cleanly and silently lacks the most recent writes, which is the worst failure shape for a credential lookup. Copy the -wal beside it and let SQLite replay it on open.

sudo kubectl -n felhom-system exec <hub-pod> -- cat /data/hub.db     > /tmp/x.db
sudo kubectl -n felhom-system exec <hub-pod> -- cat /data/hub.db-wal > /tmp/x.db-wal 2>/dev/null || true
python3 -c "import sqlite3;print(sqlite3.connect('/tmp/x.db').execute(
  \"SELECT secret FROM host_recovery WHERE host_id='demo-hp-bb76ea'\").fetchone()[0])"
shred -u /tmp/x.db /tmp/x.db-wal

The || true is deliberate: an absent -wal is legitimate (a freshly checkpointed database), and must not fail the retrieval. Shred both files — the WAL holds the same secrets as the DB.

Then sshpass -e ssh root@demo-hp (sshpass is on DooPlex, not on the nodes).

This is the lockout filed as R-61: the ISO mints a throwaway root password per build and discards the plaintext, so the console is unreachable without a working hub and network — precisely what you may be trying to fix. Slice 1 is to emit the baked password into the build report.

demo-hp-lan (192.168.0.87 via ProxyJump felhom-pve) is the fallback while the box is away.

OOB belt (H1) — both boxes, since 2026-07-23 (ISO train v1.25.0)

The dedicated OOB sshd belt (TASK H1: felhom-sshd + the static inet felhom_oob table + felhom-op) is installed and active on BOTH fleet boxes — the F9 gap (belt on neither) is closed. From v1.25.0 host-install installs it by default on every appliance install (--no-oob opts out; byo still refuses).

  • Claimed port: 8822 on both (first-free from [8822,2222,8022,62222]; persisted per box).
  • Reachability: the wg-felhom offsite tunnel ONLY — the belt admits the operator /32 (10.77.0.250) over wg-felhom to 8822 and drops everything else; :22 and every other interface are untouched. tailscale does NOT reach the belt (wrong fabric, dropped by design).
  • Operator login (from the machine holding the wg-felhom operator tunnel + the registered oob_operator_ssh_pubkey): ssh -p 8822 felhom-op@10.77.0.2 (felhom-pve) / @10.77.0.3 (demo-hp). PROVEN live 2026-07-23 on felhom-pve (felhom-op@demo-felhom).
  • Operator tunnel: the Mac/Windows operator peer dials ep0.felhom.eu:443 (WireGuard), address 10.77.0.250/32, AllowedIPs 10.77.0.0/24, server pubkey f3d1ZI7…. ep0's forward chain (persisted in its /etc/nftables.conf) allows 10.77.0.250 → 10.77.0.2/.3. If a work-network blocks UDP/443, the RheinMetall-style firewalls pass UDP/51820 — a home/hotspot network works on 443.
  • Register/rotate the operator identity hub-side: PUT /api/v1/admin/wg/operator-peer (global key) with {pubkey, assigned_ip:"10.77.0.250", ssh_pubkey}; wgsync pushes it to ep0 and the SSH key flows to both boxes' felhom-op authorized_keys within a tick.

felhom-pve (the N100) — vault parity + access

felhom-pve has operator SSH-key access (over tailscale 100.70.170.35) AND, since 2026-07-23, G1 break-glass vault parity with demo-hp: its root@pam password is freshly rotated and vaulted in the hub host_recovery row demo-felhom-8363b5 (same PUT …/recovery-credential mechanism day-0 uses; verified retrievable + authenticating over :22). Retrieval + shred-the-copy recipe is identical to demo-hp's below (swap the host_id). So a lost N100 key is recoverable the same way as the key-less HP.

Tailscale on demo-hp is an OPERATOR-LAB EXCEPTION

Read this before any product-shape audit. demo-hp is customer-shaped — it is a normal appliance install with a real customer record (demo-hp), a real guest, and a real day-0. Tailscale is not part of that shape. It was installed by hand on 2026-07-21 purely so the operator can reach a lab box that lives on someone else's LAN.

Real customer boxes never get tailscale. Their operator access is the WireGuard tunnel plus the H1 OOB path, and nothing else. If a future audit finds tailscale on demo-hp and concludes the product ships it — that conclusion is wrong, and this paragraph is the reason. The same exception already applies to felhom-pve.

Details, and the two hard rules that apply to any host node, in operations/tailscale.md.

What is NOT enrolled here (deliberately)

  • No PBS datastore, no offsite target on demo-hp; the DR tier is the N100's.
  • No second customer guest beyond 9201.

(The 1TB NVMe was on this list until 2026-07-22. It is enrolled — see the disks table above.)