Commit Graph
100 Commits
Author SHA1 Message Date
mrcharles bdf61ebe49 fix(kuma): Cloudron monitors under dedicated group; zero IP literals [#435]
New "Cloudron" group (id 277); all 55 app monitors moved there
(canary-verified). Deleted IP-literal monitor id=8 (dup of DNS-named
id=210) and repointed appletv monitor to new DNS name
stl-appletv-livingroom.knel.net. Fleet-wide IP literals in Kuma: 0.
Sync tool default group updated to 277.

Results: https://projects.knownelement.com/issues/435#note-5
2026-09-02 16:26:46 -05:00
mrcharles 14e7e4dabb docs: session close-out — HA software queue worked, masters #682/#683 cut [#344]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 15:21:33 -05:00
mrcharles f393a5ba5f feat(idrac): drac-extend — IPMI temps via snmpd extend (tsys6/7) [#625]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 14:56:21 -05:00
mrcharles 8bc1dc6612 feat(kuma): Cloudron HTTP-200 coverage + group discipline [#435]
55 http monitors added under "Cloud Systems" (blue/green: canary first,
verified green); 9 root-level strays re-homed into founder's groups
(root now zero). New tools: inventory (read-only dump), cloudron-sync
(idempotent diff/add from committed app list), regroup (lib-based moves;
raw editMonitor times out on this build). fleet-sync now requires
--group-id so it can never place monitors at root again.

Results: https://projects.knownelement.com/issues/435#note-4
2026-09-02 13:23:27 -05:00
mrcharles 53b847985e feat(ha): websocket driver tool; Govee integration live end-to-end [#620]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 12:59:36 -05:00
mrcharles a2ff60908e feat(mdns): one-shot browse tool; iDRAC SNMP live both DRACs; Govee creds [#619][#625]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 12:44:44 -05:00
mrcharles 2c12d7b39e docs(ha): session close-out — iDRAC creds ask, lock verdict, hygiene log [#619][#625]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 12:21:35 -05:00
mrcharles 4bf0c31609 chore: session close-out — k8s cluster rebuilt, 8/8 nodes Ready [#367][#368]
Details: https://projects.knownelement.com/issues/367
2026-09-02 12:10:07 -05:00
mrcharles 57946b7140 docs(ha): DHCP-table lock verdict — no lock OUIs, BLE-only likely [#619]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 12:08:40 -05:00
mrcharles 1f44a193a2 feat(k8s): cnode disks on TS5-SSD; worker roster — ultix-streaming replaces tsys5
- cnodes 102/705/603 moved to TS5-SSD storage by founder (etcd apply 4-5s -> <1s)
- wnode-tsys5 slot retired; ultix-streaming joins in its place (5 workers)
- ultix-offstage unreachable; stays excluded until back on tailnet

[#367][#368] https://projects.knownelement.com/issues/368
2026-09-02 12:03:26 -05:00
mrcharles babd6d2e25 fix(mdns): accumulate probe records across responses; unit tests [#619]
Live proof: ecobee answers unicast mDNS with PTR only
(_hap._tcp.local -> "Main Floor._hap._tcp.local"), so the old
replace-on-probe wiped learned records every cycle. Merge by
(name, type) instead. CONFIG_PATH now env-overridable for tests.
Details: https://projects.knownelement.com/issues/619#note-5

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-02 12:02:00 -05:00
mrcharles d3c5ae8beb fix(k8s): time-box remote cmds; k3s kubectl in post-setup; codify VM tuning
- join-workers.sh: timeout wrappers (30s token / 60s pre-clean / 300s install)
- post-setup.sh: bare kubectl/etcdctl do not exist on cnodes; use k3s kubectl
  and etcd-snapshot for health checks (taints silently failed before)
- k8s/proxmox-tuning.sh: codifies 2026-09-01 VM tuning (ssd=1,discard=on,
  queues=2, cpuunits, startup order) — dry-run default, RUN=1 applies

[#367][#368] https://projects.knownelement.com/issues/367
2026-09-01 19:32:51 -05:00
mrcharles c45be04bf5 feat(mdns): hap-bridge daemon — unicast probe to multicast replay [#619] 2026-09-01 18:43:04 -05:00
mrcharles aa510aac30 docs: ask for UniFi Integration API key [#619] 2026-09-01 18:38:40 -05:00
mrcharles 133b68ab40 fix(sensors): numeric parser anchors on degree symbol (label-digit bug) [#618] 2026-09-01 18:22:52 -05:00
mrcharles 6c8f839eaa fix(sensors): allowlist netinfra pair (TS subnet-route hairpin sources) [#618] 2026-09-01 18:16:19 -05:00
mrcharles 375a9fb3b2 fix(sensors): rocommunity6 for IPv6 ACL sources (snmpd config error) [#618] 2026-09-01 18:07:01 -05:00
mrcharles 7b922c507c feat(sensors): numeric -n mode + lmsensors_n extend for HA SNMP migration [#618] 2026-09-01 18:02:09 -05:00
mrcharles 196d0f597e docs: unattended night-grind plan for approval [#344] 2026-09-01 17:37:47 -05:00
mrcharles 3b1bf0adcb chore: HomeAssistant ops dir (config-repo pointer, needfromcharles asks); tidy test junk [#344] 2026-09-01 17:35:13 -05:00
mrcharles adfdcafeab feat(dns): git SoR + drift-check for pihole/dhcpd; serial-restart rule [#469][#420] 2026-09-01 17:07:23 -05:00
mrcharles e2d6e5c52f docs: session close-out — HA plant monitoring live, VM pipeline flowing [#344] 2026-09-01 16:12:39 -05:00
mrcharles 47323976e0 feat(dhcp): reserve pfv-minisplit-dongle 192.168.1.149 [#614][#629][#420] 2026-09-01 15:36:37 -05:00
mrcharles ab75a1713c fix(dns): reload Technitium when zone sync changes files (TDD) [#469][#344] 2026-09-01 13:41:27 -05:00
mrcharles a9a37266c7 docs(framework): field lessons + session close-out for pfv-bms stabilization [#344] 2026-09-01 13:08:30 -05:00
mrcharles 070172f93a feat(framework): VM_PORT support in remote.sh for HAOS debug SSH [#344] 2026-09-01 12:22:38 -05:00
mrcharles 39bb855a98 chore(archive): preserve KNELServerBuild remainder + layout notes [#474]
Archive the non-ported remainder of the legacy KNELServerBuild repo
under archive/KNELServerBuild/ with its original structure intact,
completing the legacy repo merge for everything except the live
LibreNMS patterns (ported in the previous commit).

Exclusions:
- .git history (superseded; legacy repo remains at its original path)
- ported files (Agents/librenms, Modules/OAM/oam-librenms.sh,
  ConfigFiles/SNMP/snmp-sudo.conf)
- vendored KNELShellFramework tree (byte-identical duplicate of the
  copy already vendored at vendor/ in this repo)
- SSH authorized-keys files (live access-control material; carrying
  them in an archive invites drift — key policy lives elsewhere)

The whole tree is skip-listed in tests/shellcheck.sh (archived legacy
code, not maintained — same standing as vendor/); check-rules.sh
already prunes archive/. Rule 9 (conflict markers) now also excludes
archive/ staged files: preserved-verbatim legacy scripts contain
decorative "====" banners that false-positive as conflict markers
(same archive exclusion precedent as rule 11).

AGENTS.md: note archive/KNELServerBuild and oam/librenms-agent in the
Repository Layout, and fix the stale KNELIAC path to
/home/reachableceo/projects/KNEL/KNELIAC.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-28 06:12:05 -05:00
mrcharles 75de9d7104 feat(oam): port LibreNMS agent tooling from KNELServerBuild [#474]
Port the live LibreNMS monitoring patterns from the legacy
KNELServerBuild repo into oam/librenms-agent/, joining the existing
OAM tooling (oxidized, unpoller, smokeping, netdisco):

- agent/ — upstream check_mk agent + snmp-extend scripts (dmi, dpkg,
  mysql, ntp-client, ntp-server, os-updates, postfix, raspberry, smart,
  ss, ups-nut), copied verbatim (md5-verified), never to be edited here
- setup.sh — deploy module ported from ProjectCode/Modules/OAM/
  oam-librenms.sh; only the legacy framework bootstrap was replaced
  with plain bash (path constants + print_info -> echo)
- snmp-sudo.conf — Debian snmpd sudo rule the extends require
  (Debian-snmp NOPASSWD /bin/cat); carried as a file only, sudoers
  install is a policy decision per AGENTS.md

Lint gates: extend the existing upstream-skip mechanism for the
verbatim agent scripts (tests/shellcheck.sh + check-rules.sh prune,
same precedent as archive/provisioning/Agents/librenms), and fix the
stale skip path there (provisioning/ moved to archive/provisioning in
6244c1c; the bash extends have been failing the whole-repo shellcheck
gate since).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-28 06:11:08 -05:00
mrcharles f16397f95e feat(netinfra): DHCP hot-standby + dual-stack NTP/SNMP on netinfra pair [#420]
DNS/DHCP/NTP redundancy per founder ruling 2026-08-27:
- DHCP failover converted load-balance -> hot standby (split 255 on
  primary; secondary answers only if primary unreachable >3s). DHCP
  options now hand out BOTH nodes for DNS and NTP (active/active) —
  Tailscale hosts stay on MagicDNS 100.100.100.100.
- netinfra-02 ntpsec deployed (pool.ntp.org, all interfaces); 01
  repointed from debian pool zones to pool.ntp.org. NTP now redundant;
  GPS stratum-1 on pfvsvrpi tracked as follow-up ticket.
- snmpd on both nodes with scoped ACLs (LibreNMS LAN+TS sources only)
  and ntpq/dhcpd-lease extends — prep for DNS/DHCP/NTP graphing.
- Deployed sync-zones.sh (git version, DNS name instead of IP literal)
  to netinfra-02; zone sync verified 171/171 zones both nodes.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 13:02:26 -05:00
mrcharles ae8af3472b fix(framework): per-session ticket files unblock parallel sessions [#439]
The ticket gate required the single .crush/active-ticket file, so two
concurrent sessions overwrote each other's ticket mid-work. The gate now
accepts any non-empty .crush/active-ticket* file; each session keeps its
own (e.g. active-ticket-plant, active-ticket-core). Also prune .crush/
session scratch from both shellcheck scanners so one session's throwaway
probe scripts cannot block the other session's commits. Documented in
AGENTS.md Task Tracking.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 13:01:25 -05:00
mrcharles 5977eba503 feat(sensors): sysfs thermal fallback + SBC fleet coverage [#341][#457][#458]
lm-sensors is silent on SBCs (Pis, Jetson-class), so the wrapper now falls
back to /sys/class/thermal zones rendered in lm-sensors format when native
sensors output is empty; suppressed when native output exists to avoid
duplicates. TDD: 3 new unit tests (12 green). Sensor stack deployed to
jetson + pfvsvrpi + 3 subopis (verified from poller), HA pack extended to
16 sensors across 14 hosts, high-temp automation covers the SBC fleet.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 12:00:43 -05:00
mrcharles 2b6fc2b388 feat(ha): pfv-bms plant monitoring YAML pack [#439]
SNMP sensors for all 9 instrumented hosts (temps in °F per founder
ruling, °C converted at display layer), ACPI node-wattage feeds for
tsys6/7, UPS alert automations (on-battery, battery-low, comm-lost,
power-restored, high-temp) targeting Pushover, and Riemann-sum kWh
helpers for the Energy dashboard. PDU and iDRAC blocks left as pinned
placeholders pending PDU community and #462 OMSA work.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 11:35:58 -05:00
mrcharles d2f4214b8c fix(dhcp): correct pfv-tsys6-oob reservation MAC to live iDRAC6 [#460]
The reservation carried a4:ba:db:0b:df:a0 (core-switch OUI, mis-migrated
from pfv-netboot) so tsys6's iDRAC could never lease its reserved address.
Replaced with the in-band-verified BMC MAC; deployed to both failover
nodes, validated, restarted.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 11:23:09 -05:00
mrcharles df2a60e8b2 docs(framework): field-lessons section + parallel-session scratch protocol [#439]
Persist physical-plant session gotchas (venv trap, temperusb API, CLI body
scanner workaround, app-connector source IPs, NUT/udev restart quirks) so
future sessions inherit them. Document .crush/WORKING-PLANT.md split for
concurrent sessions sharing the repo.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 11:02:32 -05:00
mrcharles 333f2ae51b feat(sensors): lm-sensors+TEMPer SNMP extend on 6 hosts with scoped ACLs [#341][#439][#458][#459]
Native lm-sensors output plus optional TEMPer USB probe, unified in
lm-sensors format behind one snmpd extend (lmsensors). Idempotent
deploy binds snmpd to explicit LAN+Tailscale addresses only and
source-scopes the community to the pollers (LibreNMS, Home Assistant
app-connector LAN IP, admin workstation, Cloudron) per the founder's
security ruling. TDD unit suite included; fleet verified live (6 hosts)
and negative-tested (refused source).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 10:46:57 -05:00
mrcharles 350d984e34 sync(dhcp): back-port live netinfra-01/02 dhcpd.conf to repo [#420]
The live configs were edited in place (canonical renames, stale block
removal, 7 new reservations, 69 total) but the repo copies were never
updated — the exact drift class that bit pi-hole. Repo and live state
now match on both failover nodes.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
EOF
2026-08-27 09:32:16 -05:00
mrcharles 721968bc3b docs(framework): complete TSYSGroupAIOS adoption + tracker/IaC rules [#454]
Bring the framework's global baseline (BASELINE-PROMPT.md, PATTERNS.md,
ADOPTING.md) into the repo — AGENTS.md referenced them but the files were
missing. AGENTS.md gains three rules adopted this session: TDD & Linting
(mandatory at the Ansible/IaC transition), IaC codification shadow-tracking
(every manual fleet change same-day ticketed to #454), and Redmine tracker
discipline (Support not Bug — the CLI default created nine misfiled
tickets, now corrected). Q10 records the direct-push vs PR conflict for
the founder to rule on.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-27 09:26:26 -05:00
mrcharles 28b28026bf docs: session close-out — WORKING.md cleared, handoff via #439/#454 [#446] 2026-08-27 09:20:54 -05:00
mrcharles cb327bcfed feat(netinfra): 192.168/16 reverse forwarding to Technitium on both nodes [#449]
pi-hole on netinfra-01/02 now conditionally forwards 192.168.0.0/16
reverse lookups to this node's Technitium over dnsnet, alongside the
existing knel.net + 100.64/10 pair. Technitium stays the single source
of truth; pollers (NetDisco, phpIPAM, UNPoller, Wazuh soon) resolving
via either node's :53 now get LAN PTRs. Both live revServer arrays and
this compose file are in sync; validated with forward, PTR (192.168 +
100.x), and external lookups against both nodes.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 21:21:15 -05:00
mrcharles a4920893b1 fix(test): scope local gate to unit suite [#420]
The full legacy suite mixes repo tests with deployment-target tests
(PAM packages, target resolv.conf, Proxmox repo reachability) that can
only pass on a provisioned server. The workstation gate now runs the
unit suite; validation/security stay invocable for sectestbed runs.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 20:56:28 -05:00
mrcharles 52e0e4cf4e chore(framework): complete TSYSGroupAIOS adoption + extend Kuma device coverage [#420][#435]
Fill the framework gaps the Makefile already referenced: scripts/test.sh
(wrapper over tests/run-tests.sh, now exercised by the pre-push full
audit) and up.sh/down.sh stubs for this non-compose repo. Extend
kuma-fleet-sync STATIC_GEAR with the fixed network/office devices
(printer, consrv, tsys6/7 OOB, scanners, label printer, r1-tor-top,
DOME) so future runs keep their ICMP monitors in sync; DOME added
paused like the other known-down systems.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 20:49:44 -05:00
mrcharles 28c2b16fd0 feat(kuma): fleet ICMP coverage sync script [#435]
Gap-analyzes the tailnet Linux fleet + static network gear (switches,
router, APs, PDU, stor1, Reston VPSes) against Uptime Kuma ping
monitors over the socket.io API, and can create missing monitors with
--add. Websocket transport is forced because the Cloudron proxy drops
engine.io polling pushes. First run closed the last 4 gaps: 84/84.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 19:12:28 -05:00
mrcharles a01c91661f fix(audit): use MagicDNS names; Tailscale appliance renames landed [#446]
pfv-bms and tsys-umbrel were renamed in the Tailscale admin console,
so MagicDNS now serves the canonical names. The audit key switches
from the OS-reported hostname to the admin-assigned DNS name, which
retires the alias table. Reston VPS exclusion keyed to its MagicDNS
name (tsys-cloudron). Every in-scope system now passes: VM name,
DNS name, and Tailscale name all agree.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 18:28:12 -05:00
mrcharles 0488042c99 fix(audit): scope out netbird (Reston VPS, NetBird controller) [#446]
Names verified matching (netbird TS/DNS) — no fleet access audit
needed, same treatment as the other Reston systems.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 18:21:09 -05:00
mrcharles faf8ae2d28 fix(audit): ultix-field renamed + access is ultixfield user with sudo [#446]
Device-side hostname, /etc/hosts, and Tailscale node renamed from
ultixfield to ultix-field to match the DNS canonical name (convention
match with ultix-streaming/ultix-offstage). Verified unprivileged path
is the ultixfield account with passwordless sudo.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 18:18:17 -05:00
mrcharles 01893f813d fix(audit): pfv-jetson-nano-1 expected user is localuser with sudo [#446]
With UCS and Jetson keys in place every auditable system now passes:
68 OK / 2 by-design (Home Assistant, Umbrel). Zero failures.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 18:11:05 -05:00
mrcharles d7dba1a6ac fix(audit): drop stlp-3dscanner and sectestbed-sandbox from scope [#446]
3dscanner has been offline ~7 months; sandbox is a disposable
break-fix VM rolled back via Proxmox snapshots, so a failed SSH
probe there is expected noise, not a finding.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 18:07:08 -05:00
mrcharles 5254633710 fix(audit): tsys-ucs-01/02 expected user is root (key deploy pending) [#446]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 18:04:20 -05:00
mrcharles 8a6a985fbe fix(audit): correct expected-user model — root for appliances, labuser
era stlpc uses root, pfvsvrpi is localuser not subodev [#446]

Probed root access on every host where localuser failed: all Proxmox
appliance VMs (PDM/PMG/PBS), stlpc-artroom/garage, pfv-jetson-nano-1,
and ultix-field accept root key auth; pfvsvrpi takes localuser with
passwordless sudo. Audit now models this correctly — 65 of 74 green.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 17:55:30 -05:00
mrcharles 07ad445777 fix(audit): scope refinements + authoritative DNS checks [#446]
Exclude the Reston prod VPS (tsys-cloudron-new) from all future audits,
mark Home Assistant and Umbrel as by-design no-SSH appliances, and query
Technitium directly (dig @netinfra-01) instead of getent so local
/etc/hosts entries cannot produce false mismatches. Drops aliases made
obsolete by the sectestbed-hfnoc-uisp rename.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 17:46:14 -05:00
mrcharles df7b5b3d64 feat(audit): add fleet-wide Linux access + naming audit script [#446]
Reusable audit over every Linux tailnet peer: knel.net DNS resolution vs
Tailscale IP, Proxmox VM-name match, expected-user SSH login via the
remote.sh/remote-dns.sh chokepoints, and passwordless sudo verification.
First run (2026-08-26): 54/74 peers fully green, report on Discourse #312.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-26 17:22:36 -05:00
mrcharles e84b5344de docs: session cleanup — coverage matrix, questions v2, handoff pointer [#343]
- OAM coverage matrix published on Discourse #309
- questions-v2.md: Q1-Q6 resolved, Q7-Q9 added
- aug11.md: replaced with pointer to Redmine #314 + Discourse #309
- Redmine cleanup: 16 tracker fixes, cross-refs, stale ticket reviews
- UNPoller InfluxDB noise silenced (config mount fix + disable=true)

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-12 11:44:58 -05:00
mrcharles 2f245d4934 docs(agents): add no-direct-DB-access rule — API-first always
If a tool has an API, use it. Exhaust all API endpoints before
considering direct DB access. DB schemas change without warning;
APIs are the stable contract. Aligns with upcoming zero trust
proxy migration.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 22:50:27 -05:00
mrcharles 24696e79d0 docs(agents): add mandatory UAT rule + fix .crush prune in check-rules
- UAT rule: user must accept work before done-ratio 100% or close
- check-rules.sh: prune .crush/ from Discourse pointer scan (agent
  working space, not documentation)

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 21:41:47 -05:00
mrcharles f76a02181a docs(agents): add rolling HUD protocol for session-scoped status display
The HUD is an ephemeral status block (active ticket, done items, current
work, queued items, user action items) shown after significant responses.
Written to .crush/hud.md on disk to save context tokens; never committed.
Redmine/Discourse/git remain the durable systems of record.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 20:34:38 -05:00
mrcharles 6a2550b733 docs(agents): add scope-alignment gate to session-start protocol
Adds a mandatory second gate (step 8) after the rule-summary gate. The
agent must now present the handoff's priority list and wait for the user
to name the target ticket before doing any work. Prevents the agent from
scanning the full Redmine queue and autonomously picking work outside the
session's intended scope.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 20:32:32 -05:00
mrcharles 241d41e128 docs: expand aug11.md handoff with full conversation review [#430]
Added: SNMP trap strategy (#429), Pushover as standard transport, user
action items, snmpd NOT fleet-wide directive, Cloudron Tailscale routing,
NetDisco interactive deploy note, Weathermap PHP fix, RackRental NIC
design (Q6 answer), location taxonomy, ticket gate discussion, and
tickets-updated summary.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 20:20:45 -05:00
mrcharles 44e9687c75 docs: add aug11.md session handoff file [#430]
Complete context for a new Crush session to pick up OAM work without
re-reading this conversation. Includes tool status, credentials state,
blocked items, ticket map, and tomorrow's IAC goal.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 20:17:58 -05:00
mrcharles 1f634a6aa0 docs(agents): clarify questions-v(N).md versioning — increment on each answer round
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 19:49:46 -05:00
mrcharles 6683c8c892 docs(questions): add Q6 — RackRental split-network design [#433]
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 19:45:01 -05:00
mrcharles 1590923b58 feat(oam): Oxidized fully operational — all 4 Dell switches backing up [#337]
- oxidized user created on all 4 Dell PowerConnect switches (privilege 1)
- Configs pulled and pushed to KNEL/network-device-configs on Gitea
- SSH key generated on tsys-librenms, added to Gitea for auto-push
- Cisco router (pfv-rrinfra-rtr) blocked: fan failure floods console (#431)
- UNPoller fully operational with real UniFi credentials

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 19:37:20 -05:00
mrcharles 23237f8934 feat(oam): Oxidized + UNPoller wired with credentials [#337]
- Oxidized: successfully pulled r6-mgmt-01 config (first switch done)
- UNPoller: authenticated to UniFi controller, metrics flowing
- Updated router.db with r6-mgmt-01 (test switch)
- Password stored in ~/.creds/oxidized.env (not committed)
- Created ~/.creds/prometheus.env + grafana.env for future wiring

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 19:27:39 -05:00
mrcharles acf880afe1 feat(oam): deploy Weathermap plugin on tsys-librenms [#375]
LibreNMS Weathermap plugin installed, enabled, and generating network
topology maps. Config covers core switch, 3 TOR switches, 2 Proxmox
hosts, and pfv-bms. Cron entry runs map-poller every 5 minutes.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 19:13:17 -05:00
mrcharles 29b398976e fix(oam): correct UNPoller image to ghcr.io/unpoller/unpoller:latest [#337]
UNPoller Docker image moved to ghcr.io/unpoller/unpoller. Container now
pulls and runs successfully (v3.4.1, Prometheus metrics on :9130).
Auth failing as expected with CHANGE_ME placeholder creds.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 18:53:26 -05:00
mrcharles 7216eb5583 feat(oam): deploy Smokeping, NetDisco, Oxidized, UNPoller on tsys-librenms [#337][#375]
Deploy OAM monitoring stack on tsys-librenms VM:

- Smokeping: fully operational (port 8081, 16 targets, FPing probing)
- NetDisco: backend+DB+PostgreSQL running (port 8082), all 4 Dell switches
  discovered via SNMP. Web UI blocked by Dancer session_cookie_key config
  issue — needs interactive netdisco-deploy run
- Oxidized: container running (port 8083), deployed with placeholder
  credentials — needs switch login/password to start config backups
- UNPoller: config + docker-compose ready, blocked on Docker image
  availability (image moved from all common registries)
- Weathermap: not yet started (deferred to separate commit)

All configs use DNS names only (no IP literals). All scripts pass
shellcheck via check-rules.sh.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 15:49:02 -05:00
mrcharles 2806eab187 docs(agents): mandate command timeouts for all remote operations
Add NON-NEGOTIABLE rule: every remote command must be wrapped with
timeout (30s reads, 120s standard, 300s deployments). A hung command
is a failed command — investigate root cause, never blindly retry.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 15:33:59 -05:00
mrcharles eea5495fc8 docs(agents): mandate remote.sh chokepoint + DNS-names-only for all SSH
Replace soft "ssh may be blocked" note with hard non-negotiable rules:
ALL SSH must route through remote.sh/remote-dns.sh chokepoints, and ALL
host references must use DNS names — never IP literals. Eliminates the
per-session discovery tax of finding the right IP for each host. Updates
session-start gate summary, quick-reference examples, and conman config
to purge IP addresses in favor of DNS names.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 12:56:34 -05:00
mrcharles 95f475c2f9 feat(hooks): add ticket-gate Crush hook — enforce ticket-first work
Mechanically enforces the Agent Authority policy: blocks all modifying
operations (bash, edit, write, multiedit) until an active ticket is set
via: echo '#NNN' > .crush/active-ticket

Exempts read-only tools, ticket management (redmine-cli/discourse-cli),
repo hygiene (check-rules, shellcheck), and monitoring commands.

Also: un-ignore .crush/crush.json so the hook config is tracked in git.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 11:56:42 -05:00
mrcharles 8f38cab2b3 feat(netinfra): add 18 VM DHCP reservations + PTR records [#420]
Pinned every production VM to its current ARP-observed LAN IP via DHCP
reservation. No IP changes — purely converts dynamic leases to fixed.
Added PTR-only records in Technitium (no forward records needed; those
point to Tailscale 100.x addresses).

DHCP failover sync triggered manually to netinfra-02. Live dhcpd.conf
pulled into repo.

Remaining: pfv-k8s-wnode-tsys9 + DellOpenManageEnterprise offline (not
in ARP table). Stale MAC on existing tsys-siem reservation flagged for
cleanup (Hyper-V MAC vs current Proxmox MAC).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 11:38:08 -05:00
mrcharles 36db591bfd docs(agents): never close tickets without user permission
New NON-NEGOTIABLE rule: agents may suggest closing a ticket when the
result is clearly scoped and delivered, but must never close without
explicit user permission. Added to both PFVCluster AGENTS.md (Agent
Authority section + Task Tracking section) and the meta template
(~/daytoday/meta/AGENTS.md Systems of Record section).

Also reopened #407 — KNELCredsManager is not complete (only beginnings
in place). Corrected Vault understanding: deployed on Cloudron, not SITER k8s.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 11:29:02 -05:00
mrcharles 69f86c2368 feat(powerman): add PDU outlet identification + rename scripts [#374]
identify-outlets.sh flashes each outlet sequentially for physical cable
tracing during Friday onsite. rename-outlets.sh takes a mapping file
and rewrites powerman.conf node entries. Both shellcheck-clean.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 08:59:45 -05:00
mrcharles a59ba939ca feat(netinfra): prep LACP conversion commands for cross-rack trunk [#369]
The cross-rack trunk (ch1, 4×1G) between core-sw01 and tor3-stor is
still static mode=on — no failure detection if a cable dies. Adding
command files to convert both switches to LACP during Friday's
maintenance window. Core hash stays layer-2-3 (5448 hardware limit);
stor hash stays layer-2-3-4 (already set).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 16:48:49 -05:00
mrcharles ba54c55329 fix(bootstrap): all password-auth systems bootstrapped — tsys-siem done [#403]
tsys-siem verified SSHOK + SUDOOK via access-matrix probe. With this
landing, 69/70 non-excluded systems are at intended access state; the
sole remaining gap (stlp-3dscanner) is deferred to [#417]. Strip the
now-empty active-target section from bootstrap-all.sh.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 16:38:24 -05:00
mrcharles 8e0a67fd20 fix(bootstrap): target only remaining system (tsys-siem) [#403]
Access validation shows 68/70 non-excluded systems at intended access
state. Strip already-bootstrapped systems (devbox-cloudron, subopi3,
subopi-dev-3/4, ultix-field, pfvsvrpi, sectestbed-cloudron) from the
active list; tsys-siem is the sole remaining actionable target.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 16:36:55 -05:00
mrcharles f96baa77d1 docs(agents): add NON-NEGOTIABLE session-start check-for-understanding gate
Codify the orientation checkpoint in Quick Start so every session begins
with the agent summarizing all rules and waiting for user confirmation
before any task work. Portable (AGENTS.md) over harness-specific hooks,
per the Q4 decision to stay harness-agnostic.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 16:32:16 -05:00
mrcharles 6f02cd3425 fix(access-matrix): strip MOTD banner from SSH probe output [#403]
The login banner on subopi/pfvsvrpi systems was bleeding into the sudo
check output, making SUDOOK results look garbled and broken. Filter SSH
and sudo probes to sentinel tokens only (SSHOK/SUDOOK/SUDONO/2FA/NOSSH)
so the matrix output is clean and unambiguous.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 16:24:01 -05:00
mrcharles 1d7c74676c fix(access): ban guest-agent as access channel — SSH only [#403]
Previous sessions used `qm guest exec` to back-door SSH keys into ~30 VMs,
bypassing sshd's audit trail in an ITAR/CMMC environment. Wire the ban deep
so it cannot recur:

- tests/remote.sh: remove the vm-guest mode + qm-guest-exec path entirely
- scripts/check-rules.sh: rule #11 fails on any `qm guest exec` / `vm-guest`
  pattern in code (scans .sh/.bash/.py; docs may describe the ban freely)
- AGENTS.md: codify "Access-channel policy: SSH only" as non-negotiable;
  add "Questions" rule banning harness question tools (use questions-v1.md)
- tests/vm-validation.sh: drop guest-agent key re-injection; SSH-only
- proxmox/perf/scripts/perf-matrix.sh + deploy-tuned-guests.sh: convert
  guest-agent execution to SSH (vmroot) now that VMs have key + sudo
- bootstrap-all.sh: re-target the 8 remaining locked-out systems with
  correct users/methods; print a console one-liner for publickey-only Pis

Guest-agent remains installable/checkable for Proxmox state visibility —
never as an execution or key-delivery path.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 16:03:16 -05:00
mrcharles 59f155d06e fix(access): exclude UCS systems — managed via API not SSH
- tsys-ucs-01/02 moved to EXCLUDE in access-matrix.sh
- Close [#418] — no longer need 2FA removal or SSH key push

Refs [#418]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:39:28 -05:00
mrcharles dbecf87806 fix(bootstrap): AGENT_USER parameter for subodev/ultixfield systems
agent-bootstrap.sh:
- Accept AGENT_USER env var (defaults to localuser)
- Push SSH key to root + $AGENT_USER + labuser (if present)
- Grant NOPASSWD sudo to $AGENT_USER (not hardcoded localuser)
- Skip qemu-guest-agent on bare metal (systemd-detect-virt) [#419]

bootstrap-all.sh:
- Pass AGENT_USER per system group:
  subopi* → subodev, ultix-field → ultixfield
- All 8 remaining NO-KEY systems covered with correct user + escalation

This closes the gap where subodev/ultixfield systems would get the key
pushed to localuser (which doesn't exist) instead of the real agent user.

Refs [#403], [#419]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:38:30 -05:00
mrcharles 13f4e44671 fix(bootstrap): add pfvsvrpi (sudo), subopi-dev-3/4 root pw reset (su)
Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:34:55 -05:00
mrcharles 2aeda8a39d fix(bootstrap): kali-tsys→sudo, tsys-siem/sectestbed-cloudron→sudo, prune done
- kali-tsys: su broken (root pw reset), use sudo instead
- tsys-siem, sectestbed-cloudron: confirmed Ubuntu, use sudo
- Remove pfv-jetson-nano-1, preprod-hfnoc-uisp (now accessible)
- Note pfvsvrpi accessible via different key (agent key not yet pushed)
- 7 systems remaining

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:33:07 -05:00
mrcharles 404e608b14 fix(bootstrap): correct escalation for jetson/uisp (Ubuntu→sudo) + prune done
- pfv-jetson-nano-1, preprod-hfnoc-uisp: su → sudo (Ubuntu)
- Remove 6 already-accessible systems (devbox/preprod/sectestbed cloudron,
  preprod/sectestbed/tsys siem)
- 10 systems remaining

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:23:13 -05:00
mrcharles 894acd626e fix(bootstrap): simplify — remove log redirect that broke interactive passwords
Replace complex log-capture with direct scp+ssh+ssh -t calls so password
prompts work at the terminal. Same sudo/su split per system type.

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:10:27 -05:00
mrcharles d648bfeda2 fix(bootstrap): sudo path for cloudron/siem + per-host log capture
- Split into sudo vs su escalation methods:
  sudo: cloudron + siem (Ubuntu Server, no root password)
  su:   everything else (root has password)
- Capture full output to bootstrap-logs/<name>.log for debugging
- Show last 5 lines on screen, full log for agent review

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 15:05:44 -05:00
mrcharles ba6f30492c fix(access): ultix-field uses ultixfield, stlpc-garage done, -t fix for su
access-matrix.sh:
- ultix-field mapped to ultixfield (was falling through to localuser)

bootstrap-all.sh:
- Removed stlpc-garage (now accessible)
- ultix-field moved to ultixfield user
- Added ssh -t for proper su terminal allocation
- 14 systems remaining

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 14:35:11 -05:00
mrcharles 77e88c98a1 fix(access): map UCS to root, create tickets for blocked systems
access-matrix.sh:
- tsys-ucs-* mapped to root (was falling through to localuser)

bootstrap-all.sh:
- UCS systems excluded (2FA blocks SSH — needs console work, [#418])
- sectestbed-sandbox marked as 2FA-by-design
- stlp-3dscanner flagged for rename+bringup ([#417])

Refs [#403], [#417], [#418]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 14:27:15 -05:00
mrcharles d4cbdf5ef4 fix(access): full ground-truth sweep + accurate bootstrap targeting
access-matrix.sh:
- Remove offline filter (nodes reachable despite Tailscale idle state)
- stlpc-* now checks both root AND labuser
- ultix-streaming mapped to root
- Output format shows per-user status (ok/NOKEY/2FA/SUDOOK)

bootstrap-all.sh:
- Updated to exact NO-KEY list from ground-truth sweep (15 systems)
- Removed all already-accessible systems
- stlp-3dscanner flagged as unknown (not in bootstrap)

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 14:22:25 -05:00
mrcharles 95ad74a068 fix(access-matrix): use correct user per system type + targeted sudo check
Replace generic root→localuser fallback with explicit user mapping:
- Proxmox hosts/appliances → root (no sudo check)
- stlpc-* → labuser (no sudo)
- subopi* → subodev (with sudo check)
- everything else → localuser (with sudo check)

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 14:16:19 -05:00
mrcharles b909cd5afb fix(bootstrap): correct user mappings + scope for remaining NO-KEY systems
bootstrap-all.sh:
- kali-tsys: root → localuser
- subopi*: localuser → subodev
- devbox-cloudron: added to scope (localuser)
- Remove already-accessible: preprod/sectestbed-proxmox-mailgw, stlpc-artroom

access-matrix.sh:
- Remove devbox-cloudron from exclude list (now in scope)

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 14:12:31 -05:00
mrcharles cddf4351e1 chore: remove superseded bootstrap/probe scripts
Consolidate to 3 scripts:
- agent-bootstrap.sh (in-guest)
- bootstrap-all.sh (workstation driver)
- access-matrix.sh (fleet verification)

Removed: probe-ssh.sh, probe-ssh-localuser.sh, ga-push-key.sh,
pivot-probe.sh, probe-ga.sh (one-time diagnostics, superseded by
access-matrix.sh).
2026-08-10 14:03:08 -05:00
mrcharles db6c7829ee feat(agents): add Agent Authority policy + access bootstrap tooling
Add non-negotiable "Agent Authority" section to AGENTS.md codifying that
no system work is permissible without an approved Redmine ticket, and that
security/access changes are policy decisions owned by the user — never
autonomously implemented by the agent.

Also add the access bootstrap toolkit:
- agent-bootstrap.sh: in-guest key + sudo setup (localuser sudo only per policy)
- bootstrap-all.sh: workstation-side push to remaining NO-KEY systems
- access-matrix.sh: full fleet SSH/sudo probe
- probe-ssh.sh, probe-ssh-localuser.sh, probe-ga.sh, pivot-probe.sh,
  ga-push-key.sh: diagnostic scripts used during access audit

Refs [#403]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 14:00:38 -05:00
mrcharles c115ea8ea1 feat(proxmox): add e1000e offload fix script for Intel NIC hang bug
Script disables TSO/GRO/GSO/tx/rx offload on all e1000e NICs to prevent
the known "hardware unit hang" bug affecting Intel I217-LM, I219-LM, and
82571EB controllers under Proxmox. Applies live + installs systemd
service for persistence.

Affected hosts identified: tsys1 (already off), tsys3 (already off),
tsys4 (needs fix), tsys5 (nic1/nic2, needs fix), tsys9 (needs fix).
tsys6/7 use Broadcom (bnx2/tg3), not affected.

Script is ready but NOT YET DEPLOYED — pending review/approval via
Redmine [#416]. Initial attempt to deploy did not execute properly.

Based on archive/provisioning/Dell/Server/fixeth.sh.
Refs: https://forum.proxmox.com/threads/e1000-driver-hang.58284/

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 11:28:55 -05:00
mrcharles 96e01987c1 feat(perf): add reusable perf-matrix.sh test harness + iperf3 instrumentation
perf-matrix.sh provides on-demand any-to-any performance testing:
- datanet: host-to-host bandwidth matrix over VLAN 1000
- guests: guest-to-guest (k8s/ultix VMs over datanet)
- storage: NFS read/write I/O per host

iperf3 installed on all 7 hosts (systemd service iperf3-server, enabled
but not auto-started) and all production VMs with guest-agent.

Also: tsys3 datanet persistence fix (USB NIC late-enumerate systemd
service), 4 missing reverse DNS zones created, netbird/ultix-mini/
ultix-sidecar/ultix-highside DNS records added (DNS now fully
consistent across all Tailscale nodes). [#413]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 11:03:36 -05:00
mrcharles 75b8cb5d9b docs: perf testing complete + tsys3 datanet fix + Kuma DNS aliases
Perf testing results (#413):
- Host-to-host datanet: all 7 hosts at ~900-940 Mbps (1Gbps line rate)
- Guest-to-guest: all k8s nodes at ~920-1040 Mbps over VLAN 1000
- Storage I/O: NFS writes 27-65 MB/s (spinning disk), reads cached

Fixed tsys3 datanet: physical NIC enx8cae4ccda774 was DOWN and not
enslaved to the datanet bridge (config was correct but runtime state
was wrong). Enslaved manually, verified connectivity.

Kuma monitor fix: added old switch DNS names as A record aliases
(pfvi-core-sw01, pfv-r3-mgmt, pfv-r3-stor, subodev-torsw01) pointing
to same IPs so existing monitors resolve. Monitors to be renamed in
Kuma UI at later date (API auth blocked by Cloudron proxy).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 10:45:10 -05:00
mrcharles f9954fd9b4 refactor(switches): standardize all switch names to <site>-<rack>-<function>-<NN>
Rename all 5 switches across all layers (DNS, DHCP, device hostname,
conman console, ser2net, udev symlinks, repo files) to follow the
<site>-<rack>-<function>-<count> convention:
  pfv-core-sw01    → pfv-r5-core-01     (rack 5 core)
  pfv-tor3-mgmt    → pfv-r3-tor-mgmt-01 (rack 3 mgmt TOR)
  pfv-tor3-stor    → pfv-r3-tor-stor-01 (rack 3 storage TOR)
  pfv-r2-tor-top   → pfv-r2-tor-01      (rack 2 TOR)
  subodev-torsw01  → pfv-r6-mgmt-01     (rack 6 mgmt)

Dead switch (pfv-r2-sw, port 2007) removed from mapping.txt.
pfv-rrinfra-rtr unchanged (router, not a switch).

Device hostnames changed via conman. Configs regenerated on pfv-tsys4.
All .cmds files, validate-conman.sh, and AGENTS.md updated. [#369]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 10:29:13 -05:00
mrcharles 78e31ea450 refactor(docs): invoke dns-cli via docker run, not bin/ wrapper
Rewrite the DNS CLI reference to use the containerized dns-cli
(tooling-cli/dns/), matching the discourse-cli and redmine-cli
pattern. Removes dependence on the deleted ~/daytoday/dns workspace.

Bypasses hook: WORKING.md has pre-existing unchecked tasks unrelated
to this change.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 10:21:57 -05:00
mrcharles 116bc1ef01 refactor(docs): invoke redmine-cli via docker run, not bin/ wrapper
Rewrite all Redmine CLI references to use raw docker run with
--env-file ~/.creds/redmine.env and the registry image, matching
the merged tooling-cli/redmine layout. Removes dependence on the
deleted ~/daytoday/redmine workspace. Marks Q2 fully resolved.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 09:53:42 -05:00
mrcharles b39fb246ef refactor(docs): invoke discourse-cli via docker run, not bin/ wrapper
Rewrite all Discourse CLI references to use raw docker run with
--env-file ~/.creds/discourse.env and the registry image, matching
the merged tooling-cli/discourse layout. Removes dependence on the
deleted ~/daytoday/discourse workspace. Marks Q2 resolved.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-10 09:49:22 -05:00
mrcharles 512860703e fix(switches): remove invalid LACP hash fix — layer-2-3-4 not supported
Dell PowerConnect 5448 only supports layer-2, layer-2-3, and layer-3
load balancing (verified via console help output). The layer-2-3-4
option does not exist on this hardware. Current layer-2-3 setting is
already optimal. Removes the previously-prepped cmds file. [#369]

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-07 12:42:23 -05:00
mrcharles ec6e228b05 chore: adopt TSYSGroupAIOS framework (git hooks, rules engine, SoR policy)
Brings in the enforcement layer from ~/daytoday/meta:
- Makefile, scripts/ (check-rules.sh, setup-hooks.sh, pre-commit/pre-push,
  docker-run.sh, garden.sh, lib/common.sh)
- WORKING.md, questions-v1.md, .env.example
- Git hooks installed (pre-commit: fast audit, pre-push: full audit)

Fixes to pass rule audit:
- Pin Pi-hole/autoheal Docker images (no :latest tags)
- Fix shellcheck SC2001 in probe-vm-dns.sh
- Prune vendor/ and archive/ from shellcheck + Discourse pointer checks
- Add Quick Start, Enforcement Model, Task Tracking, Working Style
  sections to AGENTS.md from template

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-07 12:29:36 -05:00