Commit Graph

27 Commits

Author SHA1 Message Date
tommy 0be9ae426b homepage: read HA token from env var; stop committing it in plaintext
services.yaml now references key: "{{HOMEPAGE_VAR_HASS_TOKEN}}" instead of
the inline long-lived HA JWT (which was exposed in this public-fronted repo
and has been rotated + revoked). Secret now lives only in observability/.env
(untracked, mode 600). Add .gitignore for .env/secrets so this can't recur.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:01:27 -05:00
tommy 50e3107e38 ha: add universal media-partition low-space guard for Frigate
Adds a command_line sensor (HAOS Media Partition Free, df of /media =
sda8) and an automation that stops the Frigate add-on when free space
drops below 50 GB for 2 min. Protects HAOS Core from the VAULT1 CIFS
fallthrough fill scenario (RB-001). Fires regardless of CIFS mount
state — the mount sensor can be stale during the crisis. Stop-only +
max-priority ntfy alert; no auto-remount (needs admin token).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:33:28 -05:00
tommy 16489cabe5 observability: fix container memory alerts to use working set (C-02)
ContainerMemoryNearLimit and ContainerMemoryAtLimit divided
container_memory_usage_bytes (which includes reclaimable inactive_file
page cache) by the container limit. On prometheus this read ~99% and
fired AtLimit critical ("OOM kill expected shortly") despite anon
working set being only ~365 MiB and memory.events oom_kill = 0.

Both rules now use container_memory_working_set_bytes, the OOM-relevant
figure (usage minus reclaimable cache). Validated with promtool and
reloaded via SIGHUP; both rules read inactive (~33%).

This versions a fix that was previously live-only at
/home/tommy/observability/prometheus/ and would silently revert on the
next repo-based redeploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:56:07 -05:00
tommy 8a734c638a runbooks: RB-002 total Traefik 403 — CrowdSec SQLite journal lock + WAL fix
All services behind Traefik on docker-node01 (.186) returned HTTP 403 for ~7
minutes (22:02–22:11 CDT 2026-05-23) due to CrowdSec SQLite database lock.

Root cause: SQLite delete-journal mode serializes readers under write lock.
After 27.5 h of operation (8.2 MB fragmented DB), CrowdSec hourly maintenance
tasks (metrics flush, orphan event purge, allowlist flush) fired simultaneously,
holding the exclusive write lock long enough to time out all concurrent bouncer
reads. Traefik bouncer plugin fails-closed → HTTP 403 on every proxied service.

Fix: stopped CrowdSec, switched DB to WAL mode (PRAGMA journal_mode=WAL) +
VACUUM (8.2 MB → 3.5 MB), restarted. WAL mode persists in DB file header.

Runbook covers: Cloudflare passthrough mislead, --resolve bypass diagnosis,
LAPI health check, journal file detection, step-by-step recovery, trigger
investigation methodology, and blast-radius note on entrypoint-level middleware.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:23:16 -05:00
Tommy e61fa8fe60 docs(runbook): HAOS NUT addon installed and verified
- NUT addon a0d7b954_nut installed as netclient secondary on HAOS
- Monitors cyberpower1@192.168.99.200:3493 (upsuser/Sparky$100)
- HAOS confirmed in Beast's connected client list (upsc -c)
- Replaces "Pending" NUT gap entry in RB-001 Prevention section
- Updated root cause paragraph: NUT gap resolved 2026-05-23

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:00:46 -05:00
Tommy af09358149 ha: VAULT1 CIFS mount monitor; runbooks: supervisor root-cause
HA: add binary_sensor.vault1_cifs_mounted (grep /proc/mounts every 5 min),
rest_command.vault1_push_heartbeat, and two automations — heartbeat push to
Uptime Kuma push monitor (id=45, 10-min timeout) and direct ntfy alert if
CIFS drops for >3 min. Catches the sda8-fallthrough scenario in <8 min.

Runbooks: root cause of credential file loss is NOT unclean shutdown but
the HAOS 17.3 supervisor (2026.05.x) deleting the cred file during mount
reload cycles. Exact moment: 2026-05-22 00:53:31 UTC, 13 min after the
HAOS 17.3 first-boot successful mount. NUT gap documented (HAOS not a NUT
slave); recommended to add Community NUT addon targeting cyberpower1@.200.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:46:44 -05:00
Tommy 7e8ea78cee runbooks: fix VAULT1 CIFS creds + add credential recovery procedure
VAULT1 supervisor mount had missing credential file since at least
2026-05-01 (lost after unclean shutdown). 22 days of Frigate recordings
accumulated on sda8 (299 GB) instead of the 4 TB USB drive. Fixed via
PUT /mounts/VAULT1 with hassio credentials; mount now shows 3.6T/915G.
Orphan dirs documented; RB-001 updated with credential recovery steps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:27:14 -05:00
Tommy 50e8dfccc7 runbooks: add INC-007 Frigate/VAULT1 USB outage runbook (RB-001)
Symptom→Diagnosis→Recovery for the 2026-05-22 7.5h Frigate blackout.
Key findings: JMicron JMS56x enclosure (2-5) is self-powered and directly
connected; VAULT1 CIFS credential-file errors are a separate pre-existing
config drift, not the root cause. Also adds VAULT1 CIFS drift entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:09:54 -05:00
Tommy ce2c169e3a ha: add ntfy rest_command and Core update alert automation
- Add rest_command.ntfy_grafana_alerts in configuration.yaml
  (POST to https://ntfy.goattw.net/grafana-alerts)
- Add ha_core_version_update_alert automation in automations.yaml
  Triggers on update.home_assistant_core_update installed_version change
  Sends high-priority ntfy alert with old→new version and browser
  hard-refresh reminder (Ctrl+Shift+R / Cmd+Shift+R)
2026-05-22 18:21:04 -05:00
tommy 4372080a3b docs: add INC-006 (PBS upsmon DEADTIME), update INC-001 pool state
INC-006: PBS upsmon DEADTIME=15s caused autonomous shutdown when Beast
NUT server was briefly unreachable during reboot. Fixed 2026-05-16 by
setting DEADTIME 300 on PBS.

INC-001: usb1-zfs and usb2-zfs manually imported 2026-05-16 09:14 CDT
after May 14 reboot left both MISSING. Both ONLINE, no errors. Scrubs
initiated. Cachefile updated at /etc/zfs/zpool.cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:49:30 -05:00
tommy 2ee7c474f5 docs: add P5-12 to phase5-incident-log, update INC-001 and P5-05 states
P5-12 (weekly-health-digest): documented scope, deployed location, cron
schedule, ntfy topic, break-test result. Removes duplicate
monitoring/PHASE5-incidents.md — P5-12 belongs in the canonical
runbooks/phase5-incident-log.md alongside all other phase items.

INC-001 state updated: PBS rebooted May 14; both usb1-zfs and usb2-zfs
now MISSING (usb2-zfs was previously ONLINE on May 5 — new degradation).
zfs-health-check alerting every 5 min to ntfy/zfs-health.

P5-05 marked RESOLVED: all certs renewed to Jul 22 2026, confirmed via
live openssl check on 2026-05-16.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 08:58:16 -05:00
tommy a4726a4da6 monitoring: add weekly-health-digest.sh and Phase 5 incident log (P5-12)
Lightweight weekly drift-catcher that posts a concise digest to the
dedicated ntfy/health-digest topic every Monday at 07:00. Covers:
PVE reachability, Prometheus target health, firing alerts, PBS backup
status, PBS ZFS pool presence, filesystem >85%, ZFS pool state, Docker
unhealthy containers, and TLS cert expiry <21d.

Does NOT replace the quarterly full health check — documented in
PHASE5-incidents.md alongside open P5-07 and P5-10 items.

Deployed: /etc/cron.d/weekly-health-digest on media-server.
Break-tested 2026-05-16: correctly flagged PBS ZFS pools MISSING and
Grafana firing alerts; suppressed known-persistent issues.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 08:56:46 -05:00
tommy 640cd908df docs: P5-11 — compute5 nvme1 PCIe quirk verified, no action needed
platform quirk 'simple suspend' is applied by PVE kernel automatically
for i7-13700T platform (both nvme0 and nvme1). Not a cmdline parameter;
/etc/kernel/cmdline absent. Persists across kernel updates by default.
Verified: dmesg confirms quirk active on both drives at current boot.
P5-11 status: monitor only, no user action required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:29:16 -05:00
tommy 909fe3dc12 docs: Phase 5 — incident log, maintenance runbook, NUT config archive
beast/nut/upssched.conf: fixed earlyshutdown timer (30s→300s) and added
  cancel rule. Archived post-fix from Beast.
beast/nut/upssched-cmd: archived for reference.

runbooks/phase5-incident-log.md:
  INC-005: NUT upssched earlyshutdown bug — cause, fix, break-test findings.
  INC-001 through INC-004: PBS USB hub/data/re-diagnosis/sdj end-of-life.
  P5-01 through P5-11: pending work queue including Pi4 node-exporter,
  compute5 PCIe PM, PBS recovery gate, qnetd migration, Authelia metrics,
  cert renewal verification, ansible-control disk, Phase 4A resume.

runbooks/maintenance-runbook.md: PBS recovery steps, UPS test procedure,
  cert renewal schedule (May 11/18), Phase 4A per-node procedure, qnetd
  migration, Authelia metrics setup, recurring checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:25:49 -05:00
tommy 545d3563b8 docs: Phase 4C — root cause for W11 (xtables) and W12 (qdevice)
W11 (compute6 pve-firewall xtables conflicts): race condition between
pve-firewall's iptables_restore (no -w flag) and Docker's iptables
backend. Self-resolves within 10-120s; no firewall gap; no action taken.
Conflicts align to :58 minute mark = Docker daemon start time at last
compute6 boot (Apr 19 12:58).

W12 (compute2 corosync-qdevice boot failures): qnetd on PBS (.153);
PBS has crash-rebooted 34 times since Apr 21, each correlated with USB
ZFS import failure (same as C1, May 5 health check). Each PBS reboot
drops qnetd for ~60s; cluster quorum unaffected (6 node votes, threshold
3). No action this phase. Phase 5 items: fix PBS USB ZFS instability;
optionally migrate qnetd from PBS to Pi4 (.227).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:11:47 -05:00
tommy 38fa22d444 monitoring: add quarterly UPS self-test script (Phase 4B)
ups-quarterly-test.sh:
  - Runs test.battery.start.quick on cyberpower1 then cyberpower2
  - 120s wait between tests (allow recharge)
  - Logs pass/fail to syslog via logger -t ups-quarterly-test
  - Password stored in single-quoted variable to prevent shell expansion
  - Deployed on beast (/usr/local/bin/), cron: first Sunday of Jan/Apr/Jul/Oct 02:00

Manual run 2026-05-06:
  cyberpower1: Done and passed (charge 97% post-test, recharged normally)
  cyberpower2: Done and passed (charge 100%)

Grafana alerts (in grafana.db):
  cfl8lrs1mxnnka  UPS Battery Charge Low (<80%) — break-tested pending ✓
  afl8lrs4mbaioa  UPS On Battery (power outage) — break-tested fired ✓

Note: nut_battery_test_result not exposed by nut-exporter v1.2.1.
Pass/fail tracked via syslog only for now. Adding to Phase 5 if exporter
gains test-result metric support.
2026-05-06 06:05:50 -05:00
tommy e3ee020d53 monitoring: add postfix queue + cert expiry scripts, Phase 4D alerts
postfix-queue-check.sh:
  - Reads mailq queue depth, writes postfix_queue_size{host=X} textfile metric
  - Deployed on compute3 (systemd node_exporter) and compute5 (Docker)
  - Cron: */5 * * * * as root on each host
  - Prometheus alert: postfix_queue_size > 10 (uid: efl8kjns461a8f)

node-exporter-compute5-compose.yml:
  - Added textfile volume mount /var/lib/node_exporter/textfile:/textfile:ro
  - Added --collector.textfile.directory=/textfile flag

cert-expiry-check.sh:
  - Also stored here for monitoring/ grouping

Phase 4D Grafana alert rules (all in Infrastructure Alerts folder):
  cfl8jqdlhu680d  TLS Cert Expiry Warning (30d)        — break-tested ✓
  afl8jqdoepwqod  TLS Cert ACME Renewal Failure (14d)  — no real certs in window
  ffl8k2ry0nu2od  Alertmanager Down                     — break-tested, fired ✓
  efl8kjns461a8f  Postfix Queue Backing Up              — metric confirmed, 5m window
  dfl8k2s0xjklcf  Authelia Restart Loop                 — cadvisor-based proxy metric

Rules stored in grafana.db only — not yaml-provisioned (Phase 5 candidate)
2026-05-06 05:46:07 -05:00
tommy 10b60761ff traefik: add cert-expiry-check.sh with Prometheus textfile output
Reads acme.json hourly on docker-node01, writes:
  traefik_cert_expiry_days{domain=X} N
  traefik_cert_check_last_run_seconds EPOCH

Two Grafana alert thresholds:
  Warning  < 30d: auto-renewal window opened, ntfy high priority
  Critical < 14d: ACME renewal failed, ntfy urgent

Textfile at /var/lib/node_exporter/textfile/cert_expiry.prom
Scraped by existing node-exporter job on 192.168.99.186:9100
Grafana rules: cfl8jqdlhu680d (warning), afl8jqdoepwqod (critical)
Break-tested: 35d threshold fired for vault/pdf/scrutiny/gitea correctly.

Cron: 0 * * * * sudo /usr/local/bin/cert-expiry-check.sh
2026-05-06 05:34:31 -05:00
tommy 7fac4fc9c7 traefik: sync dynamic_conf.yml and add drift-check cron
node02 was missing two blocks from node01 (canonical):
- strip-trailing-dot-speedtest middleware (regex redirect for speedtest.goattw.net. URLs)
- speedtest-trailing-dot router (catches trailing-dot Host header variant)

crowdsecLapiHost intentionally differs: node01 uses Docker service name
(crowdsec:8080, container on same host); node02 points to node01 IP
(192.168.99.186:8081, node02 has no local CrowdSec instance).

Added traefik-drift-check.sh — runs daily at 06:00 on ansible-control,
diffs both configs (excluding known crowdsecLapiHost difference),
posts to ntfy homelab-alerts on unexpected divergence.

Traefik hot-reloaded on node02 via SIGHUP — no restart required.
2026-05-05 20:17:17 -05:00
tommy 23194ed22a pbs: rewrite zfs-health-check.sh, enable textfile collector
- Fix silent failure: script now posts to dedicated zfs-health ntfy topic
  instead of grafana-alerts catch-all (pools were offline 12+ hours
  undetected because alerts were buried in Grafana noise)
- Three explicit states: ONLINE (silent), DEGRADED (high priority),
  MISSING (urgent priority) — empty zpool list output is now a MISSING
  alert, not silently ignored
- Textfile metrics written atomically after loop completes only:
  zfs_pool_present{pool=X} 0|1 and zfs_health_last_run_seconds
- Added trap cleanup so mid-script crash leaves previous .prom intact
- Logs each pool state to syslog via logger -t zfs-health-check
- Remove duplicate cron entry running as tommy (was firing twice per tick)
- Enable node_exporter textfile collector for Prometheus scraping

Incident: usb1-zfs and usb2-zfs offline since PBS boot (missing cachefile).
Imported and cachefile regenerated in this session. No data errors.

Refs: 2026-05-05 health check CRITICAL C1/C4
2026-05-05 19:44:36 -05:00
tommy 331820b8de Fix PBS icon and datastore to Synology-Remote 2026-03-12 21:23:15 -05:00
tommy 5d5d123e7a Add Frigate to Monitoring group 2026-03-12 21:14:29 -05:00
tommy 2c9df030b9 Add Frigate config files 2026-03-12 21:07:53 -05:00
tommy 4cc0a4c8f2 Add Authelia config 2026-03-12 21:01:04 -05:00
tommy ea4db49b46 Add Homepage config files 2026-03-12 20:57:47 -05:00
root 22be212642 Add Compute3 PVE storage config 2026-03-11 22:25:08 -05:00
root bc7f1933ac Initial commit - PVE storage and backup job configs 2026-03-11 22:10:04 -05:00