Commit Graph
1359 Commits
Author SHA1 Message Date
vh 7f066a4b79 feat(coder-seat): Qwen2.5-Coder-1.5B copy on nh3-ml1 (not yet in the gateway)
Same model revision (df3ce67, blob sha matches fv-ml1), vLLM v0.24.0 and flags;
0.33 of the Ada (~5.4 GB, fv-ml1's absolute budget). Healthy, KV 79,808 tokens.

Parity (teacher-forced true code, 40 FIM prompts, 2,215 tokens, cache-salted):
each host is bit-exact with itself; cross-host |dlogprob| median 0.049, top-1
agreement 0.966 (Blackwell vs Ada + fp8 KV); against ground truth no quality
difference (mean logprob diff +0.008 +/- 0.019 SE). Speed on-box: ~5x slower
(64-tok FIM p50 ~1.0 s vs ~0.2 s; 63 vs 338 tok/s). Gateway coder-fast left on
fv-ml1 pending Prime's call.
2026-09-25 23:56:34 -07:00
vh c923727cf2 memory: AMT follow-ups parked until the MS-03s arrive; OOB path through nh3-scale noted 2026-09-25 23:30:03 -07:00
vh 0ad7799956 feat(nh3-dev): zellij-fleet@.service — fleet zellij server in its own cgroup (for SVOS seat_up); installed, not enabled 2026-09-25 23:14:48 -07:00
vh 50f113cd7d feat(nh3-pve): AMT static on nh3-mgmt with KVM enabled and opt-in none; WS-Man helper
- AMT re-IP'd over WS-Man to static 10.100.250.61/24 (gw/DNS .1): AMT keeps
  its old DHCP lease across a VLAN move, so it was unreachable until changed
  (reached via a temporary on-link /32 on vmbr0 + ssh tunnel, since removed).
- KVM redirection enabled, redirection listener on, IPS_OptInService
  OptInRequired=0; all read back. Password vaulted as nh3-pve/amt-admin.
- scripts/amt-wsman.py: stdlib WS-Man get/put/invoke client (digest auth,
  AMT 16 legacy-renegotiation TLS), password from $AMT_PW.
2026-09-25 23:08:53 -07:00
vh b3b75c16f4 feat(nh3-pve): move AMT to nh3-mgmt (UDM port 6 native VLAN 250) + Homepage link
- PFI-UDMSE port 6 override: native nh3-mgmt, tagged VLANs blocked (was
  forward all / native default). Reservation nh3-pve-amt -> 10.100.250.61.
- nh3-pve: arp_ignore=8 / arp_announce=2 on enp88s0 (now on vmbr0's untagged
  L2) so the host never answers ARP for 10.100.250.60 with the AMT port's MAC.
- DNS nh3-pve-amt.nh3.internal -> 10.100.250.61.
- Homepage: NH3-PVE-AMT card under Infra - NH3 (no siteMonitor/ping: AMT drops
  ICMP and its legacy-renegotiation TLS fails Homepage's fetch).
- AMT keeps its old 10.100.0.151 lease until rebind/expiry (~1920-2224 PT
  2026-09-26); it does not re-DHCP on a VLAN change or link drop (measured).
2026-09-25 22:59:42 -07:00
vh 2c248200b5 docs(nh3-pve): AMT cable is on UDM port 6, not nh3-sw1 (first pass read a stale port table) 2026-09-25 22:34:54 -07:00
vh 0f032fac2c fix(nh3-pve): AMT reachable — hold the vPro port up; DHCP reservation + DNS
AMT had no IP because Linux had enp88s0 admin-down (it left vmbr0 on 09-25 and
had no auto stanza); igc powers off the I226-LM PHY when down, so AMT had no
link. With the port up (no IP, unbridged) AMT took a lease within minutes.

- nh3-pve: `auto enp88s0` (manual, unbridged) and sysctl.d disable_ipv6 on it;
  backup /root/interfaces.bak-20260925-amt; runtime already matched, no reload.
- UDM: reservation nh3-pve-amt -> 10.100.0.151 (untagged nh3-default; the
  cable is on an nh3-sw1 trunk, not the UDM).
- DNS nh3-pve-amt.nh3.internal. AMT 16.1.25 answers TLS :16993 and :664.
2026-09-25 22:27:44 -07:00
vh 6d901c0e8d fix(beszel): GPU-LXC temperature alerts watch the GPU, not the hypervisor CPU
nh3-ml1's first Temperature alert (2026-09-25 2105, 88.8 C) was nh3-pve's CPU
package during the nightly vzdump; the GPU sat at 47 C. hwmon is not namespaced,
so an LXC's agent reports its host's coretemp sensors under the LXC's name.

- hosts/{nh3,esh}-ml1.yaml: SENSORS=-coretemp_*,acpitz (blacklist); GPU and
  NVMe temperatures verified still reported.
- Hub: Temperature >95 C / 5 m on nh3-pve and esh-pve for the CPUs (i9-13900H
  TjMax 100 C; nh3-pve 2-hour averages reach 88 C on busy days).
- nh3-pve README: runs warmer than esh-pve; workload confounds it, check
  airflow on the next visit.
2026-09-25 21:13:08 -07:00
vh 5960526c3f feat(nh3-ml1): second TEI embed/rerank backend live on nh3-pve; parity-verified vs esh-ml1
NH3 site visit done: Secure Boot off, iGPU restored as boot VGA, AMT port cabled.

- nh3-pve: NVIDIA 580.178.04 (DKMS, open modules) via pve-nvidia-host.yaml.
- nh3-ml1 = CT 109 @ 10.100.50.80 via gpu-lxc.yaml; embed-rerank (TEI 1.9.4)
  deployed with HOST_NAME/HOST_IP labels.
- Parity vs esh-ml1 (1,126 texts, 2 runs/host, controls): embed cosine min
  0.999993 = own noise floor; overlap@10 1.000 vs MRL-256 positive control
  0.684; rerank top-1 1.00, max diff 0.0014 vs floor 0.0020. On-box speed
  identical within rep spread.
- gpu-lxc.yaml: first step upgrades lxc-pve to >= 6.0.0-2 (Proxmox fix #7006).
  With 6.0.0-1 every docker run in a nesting CT failed on runc 1.5's sysctl
  reopen; applied on nh3-pve (one package).
- pve-nvidia-host.yaml: document that the headers meta drags in the newest
  kernel (nh3-pve went 6.8.12-11 -> -43 at the next reboot).
- Monitoring: Beszel NVIDIA agent + 5 alerts, Kuma #29/#30, Homepage
  nh3-ml1-docker, Dozzle agent (hub 8 clients). DNS nh3-ml1.nh3.internal.
- nh3-pve README: SB/IGFX/driver/kernel state, btmtk oops on -4x kernels,
  AMT cabled but unreachable on the network.

Gateway routing to nh3-ml1 is not changed.
2026-09-25 15:55:33 -07:00
vh 6fa8213c20 memory: snapshot — nh3-pve shutting down for the NH3 site visit (post-boot checklist), nh3-ml1 blocked on Secure Boot, gx10 AC-restore validated 2026-09-25 14:41:08 -07:00
vh 7ddd116e29 fix(pve-nvidia-host): refuse early on Secure Boot without an enrolled DKMS key; record nh3-pve SB=on 2026-09-25 14:22:47 -07:00
vh 59f5474bc7 memory: nh3-ml1 build in progress, blocked on host driver install 2026-09-25 14:17:40 -07:00
vh bc278d4ba8 refactor(playbooks): host-generic GPU host + GPU LXC playbooks for nh3-ml1
- esh-pve-nvidia-host -> pve-nvidia-host: headers/dkms/build-essential step,
  nouveau blacklist + guarded unload (refuses if nouveau bound a device)
- esh-ml1-lxc -> gpu-lxc: host vars have no defaults (elway aborts on undefined),
  rootfs storage/startup order parameterized, CT kept out of all-guests vzdump jobs
- embed-rerank: Homepage labels take HOST_NAME/HOST_IP, defaults = esh-ml1
2026-09-25 14:17:31 -07:00
vh cdd7605e89 docs(nh3-pve): BIOS-access paths for the IGFX fix — HDMI-only NanoKVM vs mini-DP RTX; esh-pve as known-good reference 2026-09-25 14:06:37 -07:00
vh 2b49be4972 docs(pfi-gx10,nh3-pve): AC-restore patch validated; nh3-pve OOB held blind until next NH3 visit (NanoKVM → gx10, MS-01 → own vPro) 2026-09-25 13:34:17 -07:00
vh 2bf418c510 docs(nh3-pve): record GPU-install post-boot state — RTX 2000E at 01:00.0, iGPU hidden by BIOS, uplink moved to enp2s0f1np1 2026-09-25 11:22:00 -07:00
vh 58390a4785 memory: correct the unpushed-commit note 2026-09-25 11:01:32 -07:00
vh 6b9ce5b14c memory: snapshot — nh3-pve GPU install pending (post-boot checklist), TEI fleet embed/rerank + reward on esh-ml1, 34 entries archived 2026-09-25 11:01:23 -07:00
vh 2118449881 feat(nh3-pve): prepare for GPU install — pin NIC names by MAC, pull AMT port from vmbr0
nh3-pve and esh-pve are the same Minisforum MS-01 (BIOS AHWSA.1.17). With a
card in the x16 slot its root port takes bus 01 and every NIC moves down a
bus (measured on esh-pve), so predictable names change (enp2s0f0np0 ->
enp3s0f0np0 etc.) and vmbr0 would boot with no uplink. systemd .link files
now pin all NICs by MAC, baked into every initramfs and synced to the ESP;
udev confirms the files apply. The AMT-capable I226-LM (enp88s0) leaves
vmbr0's bridge-ports in the file (next boot), so cabling it for AMT cannot
loop the STP-less bridge.

Also: documented the NanoKVM (https://10.100.250.171) as nh3-pve's console
OOB and that AMT is not wired; nh3-dev's Beszel agent no longer binds NAS
shares (it died on the last NH3 cold start); post-boot checklist in
persistent-memory.
2026-09-25 10:56:17 -07:00
vh 20039bcda2 memory: hermes-gateway Homepage rename resolved 2026-09-25 09:20:42 -07:00
vh 422a27cc1d chore(homepage): commit the live 'hermes-gateway seat' card rename (operator handle split, 2026-09-24) 2026-09-25 09:20:36 -07:00
vh 65dc586497 feat(esh-ml1): wire telemetry and monitoring; fix Dozzle's stale agent list
- Beszel: NVIDIA agent (stacks/beszel hosts/esh-ml1.yaml), hub system
  registered with Status/Disk/CPU/Memory/Temperature alerts; GPU samples
  verified (RTX 2000E Ada util, VRAM, power, temp).
- Uptime Kuma: health monitors for the TEI embed (:8001) and rerank (:8013)
  services — the sole backends behind the gateway, so the one exception to
  "seats are out of Kuma's lane". Reward seat excluded (no consumer).
- Homepage: dockerd on tcp/2375 bound to 10.0.50.80 (playbook step);
  esh-ml1-docker added to docker.yaml.
- Dozzle: agent v10.4.1 on esh-ml1. The hub's DOZZLE_REMOTE_AGENT still
  named ana-ml2's 10.250.50.54 and irv-ml1's retired 10.100.79.3; repointed
  to 10.251.50.54 / 10.6.110.50 (fv-ml1 and irv-ml1 logs were missing).
- Documented nh3-dev's Beszel agent failing on NH3 cold start (bind of an
  automounted NAS share); revived by hand, fix still open.
2026-09-25 09:20:36 -07:00
vh 52612cbe96 feat(reward-seat): move Skywork reward seat from fv-ml1 to esh-ml1; audit finds nothing superseding it
- Audit: Skywork-Reward-V2-Llama-3.1-8B is still #1 of 188 on AllenAI's
  RewardBench 2 per-sample results; no Skywork V3; the -40M sibling is
  vendor-marked experimental. Our AWQ W4A16 quant: 0.847 vs published bf16
  0.860 on a 150-prompt sample (within +/-2.9 pt SE), 96.2% pairwise
  agreement. Double BOS from vLLM on pre-templated text costs a further
  ~2.7 pts; callers must send add_special_tokens=false.
- No working consumer: 0 requests since 2026-09-13; Worldtree Domari points
  at a dead IP with a non-vLLM schema (reported to worldtree-dev).
- Move: sha256-identical model copy; vLLM v0.24.0 on esh-ml1 :8003 at 0.55
  util (KV 1.30x of a 16k request). Parity vs fv-ml1: 149/150 verdicts,
  99.8% pairwise signs, raw |delta| median 0.049.
- Gateway /scalar-judge passthrough -> 10.0.50.80:8003; fv-ml1 vllm-reward
  removed (~10.2 GB freed on GPU 1). stacks/vllm now holds only vllm-coder.
2026-09-25 09:09:48 -07:00
vh 7bdac80878 feat(embed-rerank): TEI is the fleet embed/rerank engine; esh-ml1 sole backend; retire fv-ml1 seats
Prime, 2026-09-25: TEI serves embedding + reranking for esh-ml1 and the fleet
from now on; fv-ml1 retires both once esh-ml1 is up.

- stacks/embed-rerank: vLLM -> TEI 1.9.4 (89- Ada build), same ports
  8001/8013, fail-closed truncation (--auto-truncate false; embed
  --max-batch-tokens 32768).
- litellm: qwen3-embedding -> esh-ml1 only (hosted_vllm/, unchanged address);
  reranker -> huggingface/ provider at :8013 (hosted_vllm/ 422s on TEI's
  `texts` body). DB alias reranker-a3-bge-v2-m3 patched to the same target.
- Verified via the gateway against the retiring fv-ml1 seats: embed cosine
  median 0.999927 (n=203); rerank top-1/top-3 29/30.
- stacks/vllm: vllm-embed and vllm-rerank-a3 removed (containers retired on
  fv-ml1, GPU 1 freed ~6.1 GB); reward + coder unchanged.
- Bake-off record moved to docs/pfi/embed-rerank-tei-vs-vllm-bakeoff.md;
  CLAUDE.md gains the TEI convention.
2026-09-25 08:30:53 -07:00
vh 49b4bf0177 fix(tei-bakeoff): fail-closed truncation; record Dvalin review and nevermore threshold check 2026-09-25 08:17:40 -07:00
vh 582b150131 feat(tei-bakeoff): TEI 1.9.4 vs vLLM on esh-ml1 — parity holds, not faster, much lighter 2026-09-25 08:05:17 -07:00
vh a1885718e9 docs(esh-ml1): whole-novel embedding bench — gateway-bound, esh-ml1 ~2.2x slower only on the direct path 2026-09-25 07:17:37 -07:00
vh 3e73374c6e docs(esh-ml1): speed A/B vs fv-ml1 — single queries a wash, bulk 3-10x slower; stay failover-only 2026-09-25 06:40:12 -07:00
vh e128f5da63 docs(esh-docker-vm): homepage wedge cleared by a VM reboot; record that it needs a manual start afterwards 2026-09-25 06:20:25 -07:00
vh b3bb5512e6 memory: High Seat report proven in nh3-dev restic snapshot cd113bef; daily check retired 2026-09-25 01:12:28 -07:00
vh b1fcb9f815 memory: auto-memory index trimmed to 21.7 KB (under the 24.4 KB load limit); drop the done item 2026-09-24 22:44:20 -07:00
vh 5402568b76 feat(esh-ml1): RTX 2000E Ada on esh-pve serves embed + rerank as a LiteLLM failover
esh-pve: NVIDIA 580.178.04 (open modules, DKMS) installed on the host from
NVIDIA's .run and loaded live, no reboot. nvidia-persistenced unit creates the
device nodes before pve-guests; the T400's vfio-pci ids and `blacklist nvidia`
retired. playbooks/esh-pve-nvidia-host.yaml.

esh-ml1: CT 110, unprivileged Debian 12, 10.0.50.80, GPU nodes via devN,
NVIDIA userspace from the same .run (--no-kernel-modules), docker-ce +
nvidia-container-toolkit (no-cgroups). playbooks/esh-ml1-lxc.yaml. Not in the
vzdump job on purpose. DNS esh-ml1.esh.internal.

stacks/embed-rerank: Qwen3-Embedding-0.6B :8001 + bge-reranker-v2-m3 :8013 on
the same vLLM v0.24.0 digest and flags as fv-ml1. Measured parity: embed
cosine FV-vs-ESH median 0.999908 (min 0.999772), inside both self-noise
floors; rerank max |delta| 0.000145 vs floor 0.000181, identical ranking.

litellm: qwen3-embedding and reranker gain an esh-ml1 deployment at order 2
behind fv-ml1 (order 1). Order fallback proven with throwaway groups:
refused primary +0.15 s, host-down primary ~18.7 s per call, dead-only 500.

Also: repaired the DB-only alias reranker-a3-bge-v2-m3, dead since the
fv-ml1 relocation (still named 10.250.50.54); documented the third
unkillable homepage wedge on esh-docker-vm.
2026-09-24 22:38:55 -07:00
vh eb46973051 memory: snapshot — NH3 outage recovered, VM 102 retired + RTX 2000 Ada installed (LXC decision), gx10 AC-restore unvalidated, 40 entries archived 2026-09-24 21:52:48 -07:00
vh 7cbd4c3781 docs(esh-pve): GPU swap done — T400 out, RTX 2000 Ada in; VM 102 leftovers cleared after the reboot 2026-09-24 21:38:36 -07:00
vh 719e3fa324 docs(esh-pve): VM 102 retired; record the hung D-state kvm from the 09-10 vzdump and tonight's reboot checklist 2026-09-24 16:24:36 -07:00
vh e5197a3688 docs(pfi-gx10): AC power-restore patch applied, pending a real power-cut test; keep the original UEFI bytes
After the 2026-09-24 NH3 outage pfi-gx10 stayed off. It has no BMC, and the
ASUS firmware defaults to staying off after AC loss. With Prime's approval,
bytes 3-4 of the UEFI Setup variable were changed from 0x1d,0x00 to
0x1c,0x01, a community recipe that matches factory power-on units. The
readback shows exactly those two bytes changed. The original 85-byte raw
variable (attr header included) is committed here and also kept on the box.

The README records that an OS shutdown is not a test of this setting. It
stays off by design, and two such "tests" were invalid. The only valid test
is pulling and restoring AC, which Prime will do on 2026-09-25. It also
records the revert procedure.
2026-09-24 15:40:06 -07:00
vh 1cbde502f4 fix(nh3): NFS clients mount nh3-nas shares on first access; record guest power-loss expectations
After the 2026-09-24 NH3 power outage every NFS line from nh3-nas failed at
boot: nh3-docker's /mnt/compose and /mnt/backup, and nh3-dev's /mnt/backup.
The NAS is the slowest box to serve NFS, and the plain fstab mounts stayed
down until remounted by hand. nh3-dev's /mnt/smithy already used
x-systemd.automount, and the same boot shows it self-healing on its next
access. playbooks/nh3-nfs-automount.yaml brings the other lines to that
shape (_netdev,nofail,x-systemd.automount,x-systemd.mount-timeout=30).
Hard-mount semantics are unchanged; only WHEN the mount happens moves.

Applied and read back on nh3-docker and nh3-dev: every nh3-nas line carries
automount, each automount unit is active, and each share is NFS-mounted after
access. A second run on nh3-docker is a no-op. The fstab check judges only
errors the rewrite ADDS, because nh3-dev's installer cdrom line has a
pre-existing verify error. The first run caught that and aborted without
writing.

servers/nh3-pve/README.md now lists each guest's expected state after a
power loss: nh3-laser is on-demand and stays off (Prime), pbs-nh3 now has
onboot=1 (it had none and stayed down), and pfi-gx10 is bare metal that
does not auto-power-on.
2026-09-24 13:16:18 -07:00
vh ad2b4d9df8 docs(fleettools): carry the operator's military-time convention into the Codex/Grok bootstrap
Operator directive 2026-09-24: clock times are written in 24-hour local
(US Pacific) format, four digits, no colon. Claude seats get it from the
global CLAUDE.md (dotfiles fe63a67). Codex (~/.codex/AGENTS.md) and Grok
(~/.grok/rules/00-fleettools.md) read this file through symlinks, so the
same rule goes under Non-negotiables here.
2026-09-24 10:02:34 -07:00
vh e6da607767 chore(task-board): mothball it; superseded by the High Seat and ledger
Operator ruling 2026-09-24. On ana-docker the stack is `docker compose
down`: the container is removed and port 7878 is closed. Kept for revival:
- the data dir /opt/docker/conf/task-board/data (tasks.db, last written
  2026-09-11)
- the task-board:local image
- stacks/task-board/ and the host's compose + .env

The Uptime Kuma monitor (id 3) was deleted before the stop so it could
not page, and its row is removed from monitors.yaml. Homepage drops the
card on its own, since it reads the container's labels.

Hooks: the container log showed no hook POSTs in 30 days. The only
traffic was open browser tabs holding /events, and the plugin was already
uninstalled on nh3-dev. Removed the paragraph that told sessions to call
task_* tools (CLAUDE.md, and the fork-fleet.sh template that seeds new
repos), the vestigial TASK_BOARD_SESSION env in .claude/settings.json, and
the listings in README and FLEETTOOLS.
2026-09-24 09:22:50 -07:00
vh bcf33427e8 docs(claude): align the Miranda standing order with the global send protocol
Global CLAUDE.md now defines how to reach Miranda: every message becomes a
summary on Prime's phone, URGENT means she phones him, and no noise. The
project section had prefixed every message URGENT and said 'when unsure,
send it', which contradicted that. It now points to the global section for
mechanics and keeps only what is infra-ops-specific: the standing order
covers matters already fixed, the consequence test, and the channel's
dependency on svos.service and hermes-gateway (a routine svos restart is
about 100s of deafness).
2026-09-24 09:05:48 -07:00
vh 62817a24b0 docs(claude): infra-ops may refer to the operator as Prime / papa romeo mike when writing to Miranda 2026-09-24 08:43:05 -07:00
vh 617b759c1d docs(claude): PRiMe is the operator's callsign with Miranda — a name, not an authenticator 2026-09-24 08:42:35 -07:00
vh 4b294928c0 docs(claude): standing order — high-urgency matters go to Miranda
Operator standing order 2026-09-24: infra-ops informs Miranda of every
high-urgency matter, fixed or needing the operator. Records the send form
(URGENT — subject, what/state/operator-action body), the consequence-based
urgency test, and the channel's dependency on svos.service and
hermes-gateway, with a fallback route for outages that take those down.
Round trip verified: test thread 01M3A0RP4Q8T0KNGH8TMFSNDA6, ack in ~1 min.
2026-09-24 08:38:10 -07:00
vh eea9eb2ae5 docs(fleet-conventions): git checkouts under root:docker need safe.directory for the deploy user
The 2026-09-14 normalization (826a63b) moved /opt/docker/compose/<stack>
dirs to root:docker 2775. git refuses a worktree whose top-level dir has a
different owner, so yt-voice-clipper's webhook auto-deploy on irv-ml1
(git fetch/reset as lkraven) failed with 'dubious ownership' from then on.
Found 2026-09-24 when v0.3.13 did not land.

The fix keeps the convention: one safe.directory entry per repo for the
deploy user. Applied on irv-ml1 and the deploy re-run: main 2af9b3a,
/diagnostics reports 0.3.13. A sweep of the other normalized hosts found no
other checkout deployed by a non-root user.
2026-09-24 02:13:38 -07:00
vh fedd4b6d95 fix(headscale-ddns): retry Cloudflare, validate every response, never write blind
Three of the four failures in the week to 2026-09-23 (09-19, 09-21, 09-23)
had one signature. The Cloudflare zone lookup returned an empty body at its
15s cap, a bare json.load crashed with tracebacks instead of a cause, and the
script carried empty IDs on to a PATCH against zones//dns_records/.
Cloudflare rejected it, so there was no DNS impact, but only by accident.

- Cloudflare calls go through cf(): 3 announced tries, and a call counts
  only when the body says success:true.
- success:true is not trusted as shape. pick() validates every body and
  prints only the fields asked for, or one line saying why not. It requires
  exactly one zone named phasefinal.com and exactly one A record named
  headscale.phasefinal.com, each with a non-empty id and content. Two A
  records are refused rather than half-updated, and an empty id can no
  longer shift the content into the id slot.
- No write without both IDs. The run ends on a confirmation that the record
  now reads the new address. The previous final line was an echo whose exit
  status was always 0, even when the parse inside it failed.
- Only a global unicast IPv4 is published (python ipaddress is_global).
  Loopback, RFC1918, link-local, CGNAT and documentation ranges are retried
  and then refused.
- curl -q as the first argument ignores ~/.curlrc, so a verbose config can
  never log the bearer token. The vault CLI path is quoted. The empty
  data-array expansion is safe under set -u on bash < 4.4.

Tests: services/headscale-ddns/test_headscale_ddns.py, 12 cases with curl,
the vault CLI and sleep stubbed. Each failure case asserts the FATAL line's
stated reason, so a run that died earlier for an unrelated cause cannot pass
it. The documentation-range fixtures (203.0.113.x) were themselves rejected
by the new public-IP guard: a free positive control.

Live: a manual run and a unit run both printed "unchanged 70.230.226.88",
Result=success. Cross-model bug-hunt (heid "Talus", Gróa + seat): all 8
findings folded.
2026-09-23 16:27:58 -07:00
vh 466f7aa4e6 fix(elway): sudo uploads land root:root, validated and staged; add fleet ownership audit
elway's sudo upload did scp-as-user then `sudo mv`, and mv keeps the owner,
so every file it installed "as root" (systemd units, /etc configs, root-run
scripts) ended up owned by the SSH user. A sudoers drop-in installed that
way would be rejected by sudo outright.

elway:
- Sudo uploads now chown to root:root by default. Playbooks can override
  with `upload.owner:` and ad-hoc runs with `--owner`. An owner is refused
  on a non-sudo upload, and `--owner` outside an ad-hoc `--upload` is an
  error rather than silently ignored.
- Ownership and mode are applied to the STAGED file, then a single mv
  publishes it, so a failed chown can no longer leave the live path owned
  by the SSH user. chown runs before chmod so setuid bits survive. A trap
  removes the staged file on every exit path.
- A directory dest is refused before anything moves. mv would otherwise
  drop the file inside the directory under a staging name.
- `mode` was spliced unquoted into the remote root shell line. It is now
  validated as octal (^[0-7]{3,4}$) and shell-quoted, on both the sudo
  and non-sudo paths.
- `mode` and `owner` must be quoted YAML strings. YAML 1.1 turns a bare
  0644 into 420, a bare 1000:0 into 60000 and a bare yes into True, and
  str() of each is a different value that looks valid. All 85 playbooks
  already quote them.
- New preflight(): every step is resolved before any remote action. An
  undefined var or a templated owner that resolves badly is now refused
  up front, not after earlier steps have already changed the host.

scripts/fleet-ownership-audit.sh (new, read-only) finds files in root's
territory owned by a normal user. Tier A lists /etc, /usr/local, /root and
cron, judging symlinks by their target. Tier X covers files that root-run
units exec from /opt, /srv, /home or /volume1, reading drop-ins and quoted
paths. Tier B only summarises /opt app trees. A host that is unprivileged,
whose find fails, or whose run dies is reported INCOMPLETE (exit 5), never
clean. Completion is marked with a per-run nonce.

Verification: 24 unit tests in scripts/test_elway.py. Live checks on
esh-docker-vm: default lands root:root; the override lands infra-ops:adm at
4755 with setuid intact; a bad group fails with dest untouched and no
staging left; a directory dest is refused; a bad mode is refused before
any copy. Audit positive controls on nh3-dev (a drop-in-only root Exec, a
quoted path containing a space, a symlink to a user-owned target) were all
flagged; esh-docker-vm negative control was 0. Probes removed. Cross-model
bug-hunt (heid, Gróa arm + seat) findings folded.
2026-09-23 09:59:15 -07:00
vh 6e8da46a28 fix(restic/esh-docker-vm): stop dumping paperless's Postgres from this host
Operator decision. paperless-ngx's database lives on esh-vm-db and is backed
up at the source by that host's fail-closed pg_dumpall. esh-docker-vm's
second copy had failed auth every night since 2026-04-24 behind a WARN. Its
`> paperless.pg_dump` redirect left a 0-byte file in every snapshot
(confirmed in snapshot 6ec9f74f), which looked like a dump but held nothing.

The block was the only consumer of /etc/restic/dbcreds.env, so the creds
loader is gone and the template dbcreds.env.example is deleted. The host
file was moved (not deleted) to /var/lib/restic/repair-20260923/.
Paperless's media volumes are still captured under /var/lib/docker/volumes.

Also fixes ownership. elway's sudo upload does scp-as-user then `sudo mv`,
so the hook deployed at 08:04 landed infra-ops:infra-ops even though root
executes it. Both esh-docker-vm playbooks now chown it back to root and
verify root:root 700.

Verified: the live hook hash matches canonical (e0d3ddcef1bddf43), and
the manual backup saved snapshot decfae71 with 3 staged dumps and no WARN
lines.
2026-09-23 09:18:03 -07:00
vh 25e41d2ab5 fix(restic/esh-docker-vm): drop the uptime-kuma hook block that aborted every backup
Uptime Kuma moved from esh-docker-vm to ana-docker on 2026-09-22. The
pre-backup hook's fallback lookup, `docker ps | grep -E "uptime.kuma"`,
then matched nothing and exited 1. Under set -euo pipefail that aborted the
hook, and resticprofile treats a failed run-before as fatal, so no snapshot
was taken from 2026-09-22 01:00 until this fix (backup-freshness: 54h stale).

The block is removed rather than guarded because there is nothing on this
host left for it to back up. The header now records the invariant the
"blocks only WARN" promise depends on: every optional-service lookup must sit
inside an `if` test or end in `|| true`. The remaining blocks were checked
and all do.

Deployed with playbooks/esh-docker-vm-restic-drop-kuma-block.yaml (the
pre-fix hook is kept in /var/lib/restic/repair-20260923/). The live hook
hash matches the canonical copy (43e6bea8b8569602). The manual backup saved
snapshot 6ec9f74f, and backup-freshness now reports all backups fresh.
2026-09-23 08:05:47 -07:00
vh c2b0a05754 docs(notify-failure): a stop that exits non-zero pages as a failure
A deliberate `systemctl --user restart hermes-gateway` paged infra-ops as a
FAILED unit (msg 3716) while the unit was already back up. Cause is a Hermes
v0.21.1 race: the planned-stop marker watcher runs the shutdown handler before
systemd's SIGTERM, consumes the marker, and the SIGTERM re-runs the handler,
which then classifies the stop as unexpected and exits 1.

Corrects the README claim that OnFailure never fires on a deliberate restart:
that holds only when the main process exits with a success status. Measured on a
throwaway unit (3/3 paged without SuccessExitStatus, 0/3 with it, and crash
restarts are unaffected), and a survey of every stop on nh3-dev since 09-15
found hermes-gateway to be the only unit that does this.

The host-side fix is drop-in hermes-gateway.service.d/
20-planned-stop-exit1-is-clean.conf (SuccessExitStatus=1). No alarm coverage
is lost: Restart=always ignores the classification, and StartLimitIntervalSec=0
means the unit can never reach `failed` from a start failure.
2026-09-23 02:15:13 -07:00
vh 6d616391fd memory: snapshot — both carried decisions approved, safe-rm fleet-wide, the misroute closed
In-flight rewritten for 2026-09-22 evening. The operator approved both carried
calls ("decide to do both pending"): the NRestarts flap sampler is to be BUILT
(163bb97) and the restic content-assertion ruling is RATIFIED as-is (ba60fda).
Recorded in Recent decisions with tracking pointers, and their modality flipped
in the handoff from do-not-touch to genuine pending work.

safe-rm: installed on nh3-dev, delegated to infra-hermes, now fleet-wide 6/6
with acceptance met. Wiring ruled to /etc/bash.bashrc above the case-$ guard
rather than /etc/environment -- an rc self-guards, smaller blast radius, covers
bash-over-ssh. ⭐ The guarded acceptance test caught infra-hermes's own silent
wiring failure in flight: a probe that cannot destroy what it tests also cannot
lie about it.

⚠ Recorded twice over because it will otherwise be trusted for the wrong class:
safe-rm is a BLACKLIST. It refuses rm -rf /home and deletes an unset-variable
path without complaint. set -u is the actual cover for the habit that prompted
the install.

D-0010/D-0011: genuine, operator-approved, misrouted to this seat by pane_find
matching a ROLLING PANE TITLE. Belayed both, nothing lost. svos-dev owns the fix
and found the sharper half -- pane_send structurally cannot deliver a harness
command, so D-0010 promised an unachievable /clear and its receipt reported
success anyway. {"sent": true} is a claim about transmission, never effect.

Live thread carried: restic/ana/esh-docker-vm has drifted 36h -> 44h against a
48h threshold while every other repo sits at 12h. Drifting, not static.
2026-09-22 21:16:12 -07:00