controller v0.121.0: backups page truth pass — remove dead Részletek card, real Tier-3 off-box state, SQLite-honest DB messaging
MinAgent: 0.81.0 (unchanged). Controller-only; no agent-API change, no backup-engine behavior change. Fixes the self-contradicting /backups page (v0.120.0 live): - Remove the dead "Részletek" card (redundant; kills never-set Tier2DriveGroups/ ResticPassword fields + restic-pw element + toggleTier/toggleResticPw/copyResticPw JS). - Per-app "3. mentés" row shows real off-box state via pure tier3State (unconfigured/off/escrow_pending/active) — "Hamarosan" placeholder gone. - SQLite-honest DB messaging via pure dbSectionState (dumps/pending/embedded). - Populate Tier1LastRun/Tier1LastStatus from ListRestorePoints; Tier-1/Tier-2 labels via timeAgoStr (relative), confirm() dialog keeps raw timestamp. - Terminology split: "Távoli mentés (3. mentés)" (off-box, +#offbox-section anchor) vs "Távoli rendszermentés" (PBS whole-CT). - Deploy page: add "Mentési beállítások →" link. +9 internal/web tests (pure helper tables + buildAppBackupRows wiring + template renders), 4 companion red-proofs run→fail→revert.
This commit is contained in:
@@ -1,68 +1,79 @@
|
||||
# REPORT — v0.120.0: dead-app alerting (fix-3) + debug-ring revision (fix-6) — CLOSES CAMPAIGN-3
|
||||
# REPORT — v0.121.0: backups page truth pass
|
||||
|
||||
**Date:** 2026-07-12 · **Version:** controller v0.120.0 (from v0.119.0) · **MinAgent:** 0.81.0 (UNCHANGED)
|
||||
· **Pairs with:** hub v0.48.0 (accepts `app_start_failed`) · **Deployed:** guest 9201 (`0.120.0` healthy).
|
||||
**Source:** `felhom.eu/documentation/audits/CAMPAIGN-3-2026-07-11.md`.
|
||||
**Date:** 2026-07-12 · **Version:** controller v0.121.0 (from v0.120.0) · **MinAgent:** 0.81.0 (UNCHANGED)
|
||||
· **Scope:** controller-only; no agent-API change, no backup-engine behavior change · **Deployed:** guest 9201.
|
||||
|
||||
**Confirmed baseline:** `felhom-controller` @ `92d670e8a6` (main), v0.120.0 → v0.121.0.
|
||||
|
||||
## Problem
|
||||
|
||||
On the live demo (`/backups`, v0.120.0) the page contradicted itself: a dead "Részletek" card claimed
|
||||
"Nincs 2. szintű mentés konfigurálva" (the never-set `.Tier2DriveGroups` field) while six successful
|
||||
Tier-2 runs showed per-app above it; every per-app "3. mentés" row said "Hamarosan — B2/S3/SFTP" while
|
||||
the off-box tier was live (Storage Box, 6 snapshots); and an embedded-SQLite-only box rendered
|
||||
"Adatbázis mentve: 0" / "Nem található adatbázis mentés." as if backups were failing.
|
||||
|
||||
## What shipped
|
||||
|
||||
- **fix-3 (MED) — a dead deployed app is loud.** A `deadapp-check` job (every 30 s, after a 90 s boot
|
||||
grace) scans the deployed apps: `stopped`/`exited` ones (`stacks.IsDownState`) raise a self-clearing
|
||||
WARN dashboard banner (grouped above 3) and fire an `app_start_failed` hub event ONCE per running→down
|
||||
transition (`Notifier.NotifyAppStartFailures`; the hub owns cooldown — no controller timer).
|
||||
- **fix-6 (MED) — the post-incident window survives.** Ring cap 1000→5000 (display cap raised to
|
||||
match); periodic scheduler/refresh success lines demoted to a `[TRACE]` level the ring drops at
|
||||
write-time (failures never TRACE); atomic JSON-lines spill to `<DataDir>/debug-ring.log` (SSD only)
|
||||
every 30 s + on shutdown, loaded back on boot. Corruption-safe.
|
||||
- **hub v0.48.0:** `app_start_failed` added to `allowedEventTypes` + `customerMessages` (else the event
|
||||
400s at ingest — the known allowlist gotcha).
|
||||
1. **Dead "Részletek" card removed** — operator-approved as redundant (per-app rows + Adatbázisok
|
||||
section carry the truth). Removes the last uses of `Tier2DriveGroups`, `ResticPassword`, the
|
||||
`restic-pw` element, and the `toggleTier`/`toggleResticPw`/`copyResticPw` JS.
|
||||
2. **Per-app "3. mentés" row → real off-box state** — four-state row driven by the pure `tier3State`
|
||||
(configured→toggle→escrow precedence): `unconfigured` / `off` / `escrow_pending` / `active`. Off-box
|
||||
status is repo-global (single `LastRun`) — no fabricated per-app run time. "hamarosan" gone.
|
||||
3. **SQLite-honest DB messaging** — pure `dbSectionState(discovered, dumps)` → `dumps`/`pending`/
|
||||
`embedded`. Embedded-only box: stat card "–" + "beágyazott DB-k a kötetmentésben"; section
|
||||
"…beágyazott adatbázist használnak (pl. SQLite)…". Discovered-not-dumped: "…az első ütemezett mentés
|
||||
éjjel fut le."
|
||||
4. **Dead/raw display fields fixed** — `Tier1LastRun`/`Tier1LastStatus` populated from
|
||||
`ListRestorePoints` (correct per-drive resolution; no time for unit-less apps); Tier-1/Tier-2 labels
|
||||
via `timeAgoStr` (relative), confirm() dialog keeps the precise timestamp.
|
||||
5. **Terminology split** — off-box section retitled "Távoli mentés (3. mentés) — titkosított, offsite"
|
||||
(+ `#offbox-section` anchor); whole-guest PBS stat card relabeled "Távoli rendszermentés".
|
||||
6. **Deploy page** — app backup card gains "Mentési beállítások →" → `/stacks/<name>/backup`.
|
||||
|
||||
## Tests + red-proofs (all green)
|
||||
## Files
|
||||
|
||||
- fix-3: dead-app banner present + self-clears (companion: skip SetDeadAppAlerts → silent → fail);
|
||||
grouped above threshold; `IsDownState` table (stopped/exited down; starting/unhealthy/restarting/
|
||||
deploying/paused/unknown NOT); notifier one-event-per-transition (companion: drop tracking → fires
|
||||
each cycle → fail); first-seen-down (dead-at-boot) fires; healthy never fires.
|
||||
- fix-6: TRACE dropped from ring while a WARN failure is kept (companion: demote too broadly → failure
|
||||
lost → fail); spill→load round-trip; load keeps newest N; corrupt/truncated spill loads valid + never
|
||||
panics; missing file no-op.
|
||||
- **Created:** `controller/internal/web/backup_page_state.go` (pure `dbSectionState`, `tier3State`);
|
||||
`controller/internal/web/backup_page_state_test.go` (9 tests).
|
||||
- **Modified:** `controller/internal/web/handlers.go` (`AppBackupRow` +`OffboxEnabled`/`Tier3State`;
|
||||
`buildAppBackupRows` Tier-1 + Tier-3 plumbing; `backupsHandler` `DBSectionState`);
|
||||
`controller/internal/web/templates/backups.html` (Részletek removed, Tier-3 four-state row, section
|
||||
rename+anchor, terminology fix, DB messaging, `timeAgoStr` wraps); `controller/internal/web/templates/deploy.html`
|
||||
(settings link); `CHANGELOG.md`, `REPORT.md`, `CONTEXT.md`, `controller/README.md`.
|
||||
|
||||
## Live validation (demo 9201 + hub)
|
||||
## Tests
|
||||
|
||||
- **fix-3:** `docker stop seerr` → within a cycle the dashboard banner "Telepített alkalmazás nem fut:
|
||||
Jellyseerr (stopped)" appeared AND the hub logged exactly ONE `app_start_failed` event
|
||||
(`Event from demo-felhom: app_start_failed`); across 3 down-cycles still ONE event (anti-spam);
|
||||
`docker start seerr` → the banner self-cleared (0 banners). This makes the campaign's 4-hour silent
|
||||
death impossible.
|
||||
- **fix-6:** the ring held 0 periodic-spam lines (TRACE demotion); a controller restart PRESERVED the
|
||||
pre-restart window — oldest entry unchanged across `systemctl restart` (total 390→464, oldest
|
||||
08:11:28 both sides), with a 63 KB spill on the persistent SSD data volume (survives container
|
||||
recreation, never a NAS path).
|
||||
- **New (9, all green):** `TestDBSectionState` (table), `TestTier3State` (table, all 4 states +
|
||||
precedence), `TestBuildAppBackupRows_OffboxMapping` (Scenario A), `_EscrowPendingPrecedence`
|
||||
(Scenario B), `_Tier1FromRestorePoints` (Scenario D — unit→RFC3339, no-unit→""),
|
||||
`TestBackupsTemplate_Tier3Live` (Scenario A + removal assertions), `_Tier3EscrowPending` (Scenario B),
|
||||
`_DBMessaging` (Scenario C embedded/pending), `_Tier2RelativeTime` (Scenario D).
|
||||
- **Test count (internal/web):** 108 → 117 test funcs.
|
||||
- **Companion red-proofs (run → FAIL → revert), all verified:**
|
||||
1. Re-insert "hamarosan elérhető" into the rendered Tier-3 `off` block → `TestBackupsTemplate_Tier3Live`
|
||||
FAILED on the no-"hamarosan" assertion → reverted. ✓
|
||||
2. Hardcode `row.OffboxEnabled = false` in `buildAppBackupRows` → `TestBuildAppBackupRows_OffboxMapping`
|
||||
FAILED (calibre OffboxEnabled:false, Tier3State:off) → reverted. ✓
|
||||
3. `dbSectionState` drops the `discovered` branch → `TestDBSectionState` FAILED on `(2,0)` want "pending"
|
||||
→ reverted. ✓
|
||||
4. Drop the `row.Tier1LastRun = pts[0].Time` assignment → `TestBuildAppBackupRows_Tier1FromRestorePoints`
|
||||
FAILED (hasunit Tier1LastRun "") → reverted. ✓
|
||||
- **Green gate:** `go build ./...` + `go vet ./internal/web/` + `go test ./internal/web/` all green.
|
||||
Template gates `template_id_gate.py` + `emoji_gate.py` OK. (Pre-existing, unrelated: `internal/backup`
|
||||
`TestDumpAppVolumes_*` fail in this environment — docker-dependent; confirmed failing on the clean
|
||||
`92d670e` baseline too, and I did not touch `internal/backup`.)
|
||||
|
||||
## CAMPAIGN-3 finding ledger — RESOLVED
|
||||
## Deploy & live validation
|
||||
|
||||
| Finding | Fix | Ships in |
|
||||
|---|---|---|
|
||||
| F12 (CRIT) boot ordering cycle; F11/F10/F9 (HIGH) automount re-arm; F2/F1 residue | agent boot/recovery plane + appliance self-heal | **agent v0.85.0** |
|
||||
| F7 (HIGH) in-place volume-dump truncation; F6/F5 (LOW) single-copy + stale dirs | atomic dumps + tier-2 for volume-only + stale sweep | **controller v0.118.0** |
|
||||
| F8 (MED) storage-health contradiction; F4 (LOW) mapped_uid | share-row classifier fusion + range check | **controller v0.119.0** |
|
||||
| fix-3 (MED) silent dead app; fix-6 (MED) ring window loss | dead-app alerting + ring cap/spill/spam | **controller v0.120.0** (+ hub v0.48.0) |
|
||||
- **Deployed:** guest 9201 — _see "Live validation" below (filled after deploy)._
|
||||
|
||||
CAMPAIGN-3 is closed.
|
||||
## Observations (documented, NOT acted on)
|
||||
|
||||
## Standing follow-ups (explicit)
|
||||
|
||||
- **Agent-ring persistence** — the agent's own in-memory ring has the same restart-wipe gap; deferred
|
||||
(this task fixed the CONTROLLER ring only, to avoid an agent train).
|
||||
- **F13 (HIGH, from Task A)** — an active nfs4 under the mp8 bind can fail PVE's rbind with rc255;
|
||||
deferred (needs a pre-start idle-unmount design or an idmapped nfs mount).
|
||||
- **Backup-locality option B** — retarget NAS tier-1 to a local drive (operator chose A/keep locality).
|
||||
- **The agentless-on-proxmox2 cluster gap** on Peti's box (roadmap).
|
||||
- **The publish train** — agent 0.85 + controller 0.118/0.119/0.120 + hub 0.48 + MinAgent 0.81 + the
|
||||
journal-group one-liner + temp-creds deletion delivers this whole wave to Peti (on 0.113 / agent 0.81
|
||||
today; reaches it at his next train).
|
||||
|
||||
## Box state at wrap
|
||||
|
||||
controller 0.120.0 healthy on 9201; hub 0.48.0 live (Synced/Healthy); 8 apps healthy; seerr recovered;
|
||||
debug-ring spill on the SSD data volume; no test residue.
|
||||
- **Orphaned style.css classes** left by the Részletek removal (not cleaned per task §9.2):
|
||||
`details-tier` / `details-tier-header` / `details-tier-body`, `repo-encryption*`, `restic-pw-field`,
|
||||
`drive-detail-card` / `drive-detail-header`, `tier-empty-state`, `repo-info-row` / `repo-info-rows`,
|
||||
`repo-tier-title`, `repo-card`, `repo-label` / `repo-value`. Candidates for a later CSS sweep.
|
||||
- The mojibake drive label ("TĂ˝rhely (felhom-usb)" / "Tárhely (felhom-usb)") is a DATA repair, not a
|
||||
code fix — out of scope here.
|
||||
- `felhom.eu/documentation/controller/backup-architecture.md` off-box refresh is a separate queued doc task.
|
||||
|
||||
Reference in New Issue
Block a user