Infra backlog: capacity, CI verification, alerts, older loose ends #121
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Status 2026-10-13 updates. Done today: #110 verified live (and its flaw found and fixed in #123), #111 and #113 merged, #108 merged (cAdvisor trim; measure after deploy). New: the production state lock (see #116),
plan-prod.sh unlock(#124).Open threads that are not part of the secrets plan.
Found 2026-10-13 after the metrics deploy (both pre-date today's deploys)
PostfixQueueGrowingis firing and correct. 77 messages queued, all fromalerts@jfraeys.com(so Alertmanager email is not being delivered), some about 35 h old. Findings 2026-10-13 ~16:08 EDT: the relaymail.zedmail.com(144.217.242.50) refused connections at first probe (all four ports) but port 2587 then answered 6/6 consecutive probes, so the refusals are intermittent and external (same signature as the 2026-09 incident indocs/incidents.md); Postfix is holding the destination in "delivery temporarily suspended" backoff, which is why the oldconnect ... Connection refusedentries persist. 9 messages instead failed SASL535 Authentication failedfor thealerts@sender (vault keyPOSTFIX_SENDER_ALERTS_APIKEY, sender-dependent auth): check the Zedmail dashboard for that key (revoked/rotated/rate-limited after earlier failed attempts). To do: (1) decide whether 35 h-old alert mails are worth delivering (flushing sends them all) or should be dropped (postsuper -d ALL deferredinside the postfix container); (2)docker exec postfix-postfix-1 postqueue -fto retry now, then watch the log forstatus=sent; (3) if 535 persists, fix or regenerate thealerts@API key in Zedmail and redeploy--tags postfix; (4) confirm another alert channel is live while email is down (Alertmanager receiver config was not readable from the host without sudo output; verify the Discord/Slack webhook receivers).AlertNotificationsFailing), #129 (ALERTMANAGER_NTFY_TOPIC) and promotion #130 merged;./infra deploy --tags alertmanager,prometheus --limit servicesrun. Host checks:/opt/alertmanager/secrets/ntfy_url0400 nobody:nogroup; the topic is not in the world-readablealertmanager.yml; running config hasprimary-push(critical and warning route to it, info stays onprimary); Prometheusalertmanagertarget up;AlertNotificationsFailingloaded. A synthetic critical alert (NtfyDeliveryTest, auto-resolving after 5 min) was posted into Alertmanager: webhook notifications 1, failures 0, and the operator received it on the phone. Direct test message to the topic also reached the phone earlier. Not covered: Alertmanager or the whole host being down (needs an external heartbeat).alerts@sender (2026-10-13 17:10 EDT). After the Postfix container was recreated at 17:10:29, the deferred synthetic-alert email went out:status=sent (250 OK 1 message(s) queued), queue empty, Alertmanager email failures 0; the operator received the email in the inbox. Before that Zedmail answered535 Authentication failedforalerts@even though TLS and connectivity were fine. Cause confirmed by the operator: they generated a newalerts@key in Zedmail, stored it and redeployed Postfix. The oldPOSTFIX_SENDER_ALERTS_APIKEYwas a copy ofZEDMAIL_API_KEY(identical values, checked by equality) while NOREPLY/CONTACT/BOOKING are distinct; Zedmail ties one key to one From address (docs/incidents.md), so a copied general key is the likely reason it was rejected. The new key was only in the operator's local vault until PR #133 (encrypted, only that key differs from main).NOREPLY,CONTACT,BOOKING): onlyalerts@is proven. Authelia, Forgejo and Vaultwarden mail uses them; trigger one real mail from each (e.g. a password-reset email) and look forstatus=sent.535s in the Postfix log so a rejected key is noticed before the queue grows. Also check the other three sender keys (above) are distinct and bound to their own From addresses.alerts@. Send a test from the postfix container to an address of yours (printf 'Subject: postfix test\n\ntest\n' | sendmail -f alerts@jfraeys.com YOU@exampleinsidepostfix-postfix-1) and look forstatus=sent. New lifecycle alerts (#105/#107:ToolMinorBehindx5,ToolPatchBehind,ToolUpstreamArchived,SecretsExpiryUnknown) arependingand will start emailing, so a still-broken relay will refill the queue.HostDiskSpaceWarning: fixed 2026-10-13 ~18:23 EDT. The systemd journal was 2.4 GB of the 2.7 GB in/var/logand nothing capped it. Thehardeningrole'sSystemMaxUse=500Mdrop-in (#127, with the staging-check fix #134) is deployed: journal 493 MB,/73% used (6.2 GB free), alert resolved./var/lib/dockeris 8.2 GB: still to review withdocker system df.backup-restore-test"failures": investigated 2026-10-13; backups are restorable. The scheduled weekly run on 2026-10-12 01:00 EDT passed completely (report/var/log/backup-restore-test/test-20261012_010004.log):opt/vaultwarden/data,opt/traefik,opt/forgejorestored and verified, Vaultwarden SQLite integrity ok, Forgejo dump (zip) and Authelia dump (sqlite) valid, and the secondary (OVH) restore also SUCCESS. The two red units (run-u4261,run-u4287, 2026-10-13 12:00:57 and 12:05:11) were transientsystemd-rununits: they lack the service'sEnvironmentFile=/etc/infra-backup.env,HOMEandXDG_CACHE_HOME; with an empty environment the script stops atRESTIC_REPOSITORY: unbound variable(reproduced on the host). They created no report and did not touch the metric (last written 2026-10-12 01:00:45), so they died in the first lines; their output never reached the journal (probably piped to the launcher), and who started them is unknown, so the exact error was not seen. Run it by hand with the unit:sudo systemctl start backup-restore-test.servicethenjournalctl -u backup-restore-test -f. Confirmed by a fresh run 2026-10-13 18:29-18:30 EDT (sudo systemctl start backup-restore-test.service, exit 0 in 43 s, reporttest-20261013_182924.log, metric file refreshed 18:30:07): primary restore SUCCESS (snapshot01dca5d0: all three paths, Vaultwarden SQLite integrity, Forgejo dumpforgejo-dump-20261013T040007Z.zipand Authelia dumpauthelia-db-20261013T040007Z.sqlite3valid) and the OVH secondary restore SUCCESS. The stalerun-u*failed units were cleared withreset-failed;trivy-scan.serviceis now the only failed unit. Next scheduled run Mon 2026-10-19 01:00 EDT. Note for manual runs:journalctlneedssudo(theansibleuser is not inadm/systemd-journal), otherwise it shows nothing.trivy-scan.service(weekly, Sunday 03:00): 15 images scanned 2026-10-11, 9 failed, 119 CRITICAL in total. Counts (CRITICAL/HIGH) from the journal:klakegg/hugo:ext-alpine55/746,boky/postfix:5.1.036/360,vaultwarden/server:1.37.17/129,gcr.io/cadvisor/cadvisor:v0.49.15/66,containrrr/watchtower:1.7.15/51,lldap/lldap:stable4/57,authelia/authelia:4.39.23/45,crazymax/fail2ban:1.1.03/31,prom/node-exporter:v1.10.21/43; clean (0 CRITICAL): crowdsec v1.8.0, forgejo 16.0.3, headscale 0.29.2, alertmanager v0, prometheus v3, traefik v3. The saved report never contained the failing images (bug:scan_imagereturned before appending; the report held only the 6 clean ones), so the CVE details were never kept. Fix: PR #136, merged (4e70c3b) but not deployed (keeps every image's JSON, writes one file per image, prints the top CRITICAL CVEs with fixed versions into the journal; 10 tests with a fake trivy; staging run API 604 / UI #413 in progress when written, the promotion PR follows it). The scan ran BEFORE the 2026-10-13 patch updates (#103: Forgejo 16.0.6, Vaultwarden 1.37.4, Headscale 0.29.5, Authelia), so vaultwarden/authelia/forgejo/headscale may already be better../infra deploy --tags hardening --limit services, then runsudo systemctl start trivy-scan.serviceonce (about 1 min CPU; brief memory pressure on this host) and read the per-image files / journal (sudo journalctl -u trivy-scan.service -o cat), or wait for Sunday 03:00. Then triage each image: fixable by a version bump (docs/tool-versions.yml,docs/update-and-patch-policy.md) / accept (exposure) / replace.klakegg/hugo:ext-alpine(55 CRITICAL, floating tag, serves the forgejo-hugo-docs container) looks like an unmaintained image: replace with a maintained one or drop that container;containrrr/watchtoweris upstream-archived (a maintained fork exists);boky/postfix:5.1.0is the latest upstream release per the tool survey, so its CRITICALs are base-image packages (port 25 is tailnet-only: low exposure); cAdvisor 0.49.1 update to a newer release is a deferred decision;lldap:stableandcrazymax/fail2bancan be re-pulled/bumped.CI gaps found 2026-10-13 (lint job log)
37 rules found, 3 promtool test files SUCCESS, key isolation 8/8, amtool accepts both configs, no SKIP lines, and no promtool crash on native amd64 (supports the theory that theconcurrent map writescrash seen under QEMU on an Apple Silicon Mac was an emulation artifact). The lint job went from ~90 s to ~160 s.Capacity / platform
HostMemoryPressureWarningcleared. Resizing to 2 GB at the OVH move is still the real fix). #108 trims cAdvisor; measure after deploy. Resize to 2 GB (Terraform instance type,--confirm-prod, short reboot) when moving to OVH, as decided.instancelabel shows the container name (node-exporter:9100); optional relabel to showservices.CI
pytest roles/ci_ephemeral_provisioner/testsinto lint (it is not in CI today, nor is the existingtest_breaker.py).waiting.ansible-lintfailssyntax-checkonplaybooks/rotate-secret.ymlstandalone (needs-e secret_name=); givesecret_namea lint-safe default or exclude it.staging-reboot-check.ymlis nowephemeral-large(#113, merged); run it once: the reboot check is built but has never been run.Alerts and metrics (new, #105-#107)
docs/tool-versions.ymlentries flagged behind/archived.PublicPortUnexpectedis quiet (allowlistinfra_public_ports_allowed: 80/443, 3478/udp, 8443).Older loose ends
mail.jfraeys.comreturned 526: the record is removed (#90 applied 2026-10-13, plan clean); coaching-site repo still claims the apex; old PRs #15, #25, #26; PR #77 (promotion) review.app_ssh_accessregister/deregister scripts;forgejo_runner_macosdynamic token.infra-newis deleted and a restore drill has run.trivy-scan triage, 2026-10-13 (first run with the #136 fix, services, report
trivy-scan-20261013_192619)15 images scanned, 8 failed on CRITICAL. Status:
prom/node-exporterv1.10.2 -> v1.12.1,cadvisorv0.49.1 ->ghcr.io/google/cadvisor:v0.60.6(gcr.io has no newer tag),crazymax/fail2ban1.1.0 -> 1.1.1: merged in #139.trivy imageon the new tags: 0 CRITICAL. Not yet re-scanned on the host.containrrr/watchtower1.7.1 (5 CRITICAL; archived upstream) ->ghcr.io/nicholas-fedor/watchtower:1.23.1: merged in #140. 0 CRITICAL; a one-shot monitor-only run on services scanned 6 containers, failed=0.klakegg/hugo:ext-alpine(55 CRITICAL; abandoned): thehugo-docsservice removed in #141. It only ever served Hugo's placeholder (itsjfraeys/docsrepo never existed). Pending after deploy: remove the orphaned container and/opt/forgejo/hugo-entrypoint.shby hand.boky/postfix:5.1.0(36 CRITICAL, internet-facing relay): no newer upstream release (5.1.0 is latest); the CVEs are in the Debian base. Re-check upstream monthly; the tool-versions drift report already tracks it.lldap/lldap:stable(4 CRITICAL, all Go stdlib 1.18.2): floating tag, latest release v0.6.3. Check whether a pinned tag has a newer Go before accepting.vaultwarden/server:1.37.4(4 CRITICAL, libmariadb3 / mariadb-common, "no fix yet" in Debian): 1.37.4 is the latest release; we run SQLite (roles/backups snapshots it), not MariaDB, so the vulnerable client libs are unused. Accepted until Debian ships a fix.After the deploy, re-run
trivy-scan.service; expected failing set: postfix, lldap, vaultwarden.trivy-scan result after the deploy, 2026-10-13 ~20:30 EDT (services, 14 images, 3 failed; was 15 / 8)
Deployed: #139, #140, #141 (promotion #142, prod =
a97294a). Re-scan CRITICAL counts: fail2ban 1.1.1 0, cadvisor v0.60.6 0, node-exporter v1.12.1 0, watchtower (fork) 1.23.1 0; hugo removed. The hugo container and/opt/forgejo/hugo-entrypoint.share removed from services. Also verified live: all Prometheus targets up,container_last_seenstill reports postfix (soPostfixDownhas data), HostDiskSpaceWarning cleared after removing the six superseded/test images (77% used, ~21.6% free vs the 20% threshold; tight).Still failing, all accepted for now:
boky/postfix:5.1.036 CRITICAL: 5.1.0 is the latest upstream release; the CVEs are in the Debian base. Re-check upstream each month.lldap/lldap:stable4 CRITICAL (Go stdlib: CVE-2023-24538, CVE-2023-24540, CVE-2024-24790, CVE-2025-68121): checked the newest dated tag2026-09-22(and its-alpine/-debianvariants) with trivy: the same 4. No tag fixes it, so this is an upstream build issue. Not reachable from outside (admin UI is tailnet-only).vaultwarden/server:1.37.44 CRITICAL (libmariadb3 / mariadb-common, "no fix yet"): 1.37.4 is the latest release and we run SQLite, so the MariaDB client libs are unused.Next scheduled scan: Mon 2026-10-19 01:00 EDT; expect exactly these 3 to fail.
2026-10-14 additions to the backlog:
_reuse_vm_for_jobresetrunner_idto None, so an unconsumed registration from an earlier job on a reused VM was never deleted; 5 stale offlineephemeral-*registrations (514, 516, 518, 424, 106; oldest ~11 days) were found. Fixed in #148 (delete the previous registration on reuse, sweep offlineephemeral-*after a VM destroy), deployed. To verify: after the idle VM is destroyed (~12:22 EDT 2026-10-14) the five should be gone; if not, the sweep'sstatus: "offline"assumption is wrong. VMs themselves converge correctly (10-job-cap VM destroyed,tofu destroyclean).scripts/backup-terraform.shremoved (#147): state is in the S3 backend, the script backed up nonexistent local files and exited 0.docs/runbooks/node-failure.mdnow says so and shows a manualtofu state pull. Gap: the repo has no separate backup of the state object itself (no versioning or replica evidenced); worth a decision.servicesis a 960 MB node with ~200 MB available and active swap (575 MB used, 33% I/O wait seen during the forge-agent integration run); the forge-agent integration tests also run against the production Forgejo (jfraeys/forge-agent-it) despite their header saying disposable. Options: resize (runbook resize-node), cut memory, or a throwaway Forgejo for the tests.Runner registration leak: verified fixed in production (2026-10-14). #148 deployed 11:57 EDT. The idle VM hit its 3600 s lifetime cap at 12:16:47 and was destroyed (
tofu destroyclean, Linode instance gone); at 12:17:35 the new sweep deleted all five staleephemeral-*registrations (ids 518, 516, 514, 424, 106) and the log sayscycle 1791990954 converged: no VM, no registration. Thestatus: "offline"assumption in the sweep held. A new cycle then started on a fresh VM and reused it three times (12:22, 12:23, 12:24); the runner table showed exactly one registration (the current runner) throughout, so reuse no longer leaks. Closing this item; if a stale registration ever shows up again, the end-of-cycle sweep collects it.