v0.170.0: REPORT (overwrite) — root→launcher, gofmt, stale-note, gate-check finding, SMART spike
This commit is contained in:
@@ -1,122 +1,88 @@
|
|||||||
# REPORT — v0.169.0/.1: Disk-health card + degradation notification ("Lemezek állapota")
|
# REPORT — v0.170.0: root→Indítópult; gofmt normalization; stale-note; gate-denial check; SMART spike
|
||||||
|
|
||||||
Part of the disk-health train (agent v0.94.0 + controller v0.169.0/.1 + hub v0.73.1). Appended above
|
**Date:** 2026-07-25 · **REPORT overwritten** (the standing rule; the prior append was a sanctioned one-off).
|
||||||
the previous report (kept below).
|
|
||||||
|
|
||||||
**Baselines:** controller `e164fef` (v0.168.0) → `c97975c` (v0.169.0) → `dbf6313` (v0.169.1). Clean at start.
|
## 1. Baselines
|
||||||
|
|
||||||
**What shipped:**
|
|
||||||
- `agentapi`: `SmartSummary` extended to the full counter set; `DiskInfo.Smart *SmartSummary`; pure
|
|
||||||
`DiskVerdictFor` (shared by card + check) + `Label` + `DegradedAttributes`. Verdicts: FAILING→Hiba;
|
|
||||||
PASSED+any(reallocated/pending/offline/critical/media >0 or percentage_used ≥90)→Figyelmeztetés;
|
|
||||||
PASSED clean→Rendben; nil/UNKNOWN→Nincs adat (never alarms).
|
|
||||||
- `web`: dashboard "Lemezek állapota" card (physical disks only — pbs/lvm/nfs/cifs excluded in v0.169.1)
|
|
||||||
via a 60s /disks TTL cache; unreachable agent → "Nincs adat", page never blocks. 6h `disk-health-check`
|
|
||||||
emits `disk_health_degraded` on a degradation only (in-memory baseline; first run silent; recovery/
|
|
||||||
UNKNOWN never notify; multi-attr → one event). `notify.NotifyDiskHealthDegraded` (warn/critical).
|
|
||||||
**No global banner** (deliberate).
|
|
||||||
|
|
||||||
**Tests + red-proofs (all recorded):** verdict table (red-proof: ≥90→>90 → boundary FAIL); notifier
|
|
||||||
emit (type/severity/subject); check first-run-silent (red-proof: disable the guard → first run notifies
|
|
||||||
FAIL); degradation-once; recovery-silent; UNKNOWN-excluded; FAILING→critical; nil-smart card graceful;
|
|
||||||
TTL cache. Full controller suite: 27 packages pass, 0 fail; build + vet clean; template/emoji/mojibake
|
|
||||||
gates OK.
|
|
||||||
|
|
||||||
**Deploy + live validation (guest 9201):** built + deployed 0.169.0 → Up (healthy); dashboard rendered
|
|
||||||
the card. **§13(2):** first render showed 4 rows (pbs/lvm carried a default UNKNOWN SMART) → shipped
|
|
||||||
**v0.169.1** to exclude logical/network types; re-verified the card now shows exactly **2 physical rows**
|
|
||||||
(`local`, USB), both "Nincs adat" — the correct real verdict on the virtualized demo hardware.
|
|
||||||
**§13(3) notification path** (proven with the hub, see hub REPORT): a `disk_health_degraded` POST — the
|
|
||||||
exact wire call `notify.PushEvent` makes — was **400-rejected by hub v0.73.0** and **200-accepted +
|
|
||||||
emailed by hub v0.73.1**. Method: direct endpoint POST via curl (no fixture code committed → nothing to
|
|
||||||
revert; the demo's real disks stay "Nincs adat" so normal operation is quiet).
|
|
||||||
|
|
||||||
**Awaiting operator:** browser visual check of the card layout (rendered HTML strings confirmed). A real
|
|
||||||
FAILING/Figyelmeztetés verdict can't occur on the virtualized demo hardware (no real SMART) — the verdict
|
|
||||||
logic + transitions are covered by unit tests + the live event-pipeline proof.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# REPORT — v0.168.0: Customer-configurable backup window ("Mentési időablak")
|
|
||||||
|
|
||||||
**Date:** 2026-07-24 · **Deployed & verified live on guest 9201; box left configured at 02:30.**
|
|
||||||
|
|
||||||
## 1. Baseline
|
|
||||||
| Repo | `main` @ start | version | → |
|
| Repo | `main` @ start | version | → |
|
||||||
|------|----------------|---------|---|
|
|------|----------------|---------|---|
|
||||||
| felhom-controller | `e33c1ae` | v0.167.1 | **v0.168.0** (committed `82c67e3`) |
|
| felhom-controller | `0a582ea` | v0.169.1 | v0.170.0 |
|
||||||
|
| felhom.eu | `e49f20f` | — | docs-only |
|
||||||
|
|
||||||
Verified against live Gitea at task start (HEAD == origin == live). No agent coupling; MinAgent unchanged.
|
Re-verified clean at start.
|
||||||
|
|
||||||
## 2. What shipped
|
## 2. Commits (feature commit NOT squashed with the gofmt commit — Scenario B)
|
||||||
ONE customer setting — **"Mentési időablak kezdete"** (window start W) — drives every nightly leg at
|
- **`2487681`** — `style: gofmt normalization — no logic changes` (commit 1, style-only).
|
||||||
FIXED, never-stored offsets so misordering is impossible: DB dump at **W**, tier-2 at **W+60m**, off-box
|
- **`9cc8424`** — `v0.170.0: root → Indítópult (302); Vezérlőpult at /dashboard; CLAUDE.md stale-note fix` (commit 2, feature).
|
||||||
at **W+105m** (wrap-safe). The whole-guest (agent PBS/vzdump) cycle is gated to **[W+2h, W+6h)** with a
|
- **felhom.eu `770ee5a`** — the SMART-coverage spike doc (+ nothing else).
|
||||||
safety valve. Precedence: settings > controller.yaml `db_dump_schedule` > "02:30". Changes apply
|
|
||||||
**without restart**.
|
|
||||||
|
|
||||||
## 3. Files
|
## 3. Part 1 — Root → Indítópult
|
||||||
- **New** `internal/backupwindow/` (backupwindow.go + _test.go) — pure time math: `ParseHHMM`/`FmtHHMM`/
|
`server.go` route split: `"/"` → `http.Redirect(…"/launcher", 302)`; `"/dashboard"` keeps the handler.
|
||||||
`LegTimes`/`GateWindow`/`EffectiveWindow`/`Valid`; offsets are constants (derived, never stored).
|
`layout.html`: Vezérlőpult `href="/dashboard"` (topbar logo keeps `/` → redirects to launcher).
|
||||||
- `internal/scheduler/scheduler.go` (+ new _test.go) — `UpdateDaily(name,timeStr) bool`, per-daily-job
|
`auth.go` unchanged (post-login default `/` flows through the redirect to the launcher — verified).
|
||||||
buffered `resched` chan, a `case <-job.resched` in `runDailyJob`, Schedule read under the mutex.
|
Two dashboard-card tests repointed `/`→`/dashboard`.
|
||||||
- `internal/settings/settings.go` — `BackupWindowStart` field + Get/Set.
|
- Tests (all pass): `TestRoot_RedirectsToLauncher` (302 + `Location: /launcher` + no launcher body at `/`),
|
||||||
- `internal/quiesce/quiesce.go` (+ new quiesce_window_test.go; fakes updated in quiesce_test.go /
|
`TestDashboard_ServedAtDashboardURL` (200), `TestNav_LauncherAndDashboardHrefsAndActive`.
|
||||||
quiesce_8b2_test.go) — `Backend.Due` now returns the backup age; `Options.WindowStartFn`/`Cadence`;
|
- **Red-proof (recorded):** fold `/` back into the dashboard case → `GET / status = 200, want 302` — FAIL, then restored.
|
||||||
`scheduledRunAllowed` predicate + Budapest helper; gate on the scheduled path in `runOnce`.
|
|
||||||
- `internal/web/backup_handlers.go` (+ new backup_window_test.go), `handlers.go`, `server.go` route,
|
|
||||||
`templates/backups.html` — the "Mentési időablak" card + POST `/backups/window`.
|
|
||||||
- `cmd/controller/main.go` — the three Daily legs + backup-cache + initial-cache + quiesce loop wired
|
|
||||||
from the effective window.
|
|
||||||
- Docs: `CHANGELOG.md`, `CONTEXT.md`, `controller/README.md`, `REUSE.md` (UpdateDaily + backupwindow +
|
|
||||||
3rd Budapest-loader duplication note), this `REPORT.md`.
|
|
||||||
|
|
||||||
> Note: a stray `gofmt -w internal/ cmd/` reformatted ~46 unrelated (non-gofmt-clean) files; all were
|
## 4. Part 2 — gofmt normalization (46 files)
|
||||||
> restored to `origin/main` so the commit is surgical — only the files above are in the diff.
|
`gofmt -w` across the controller tree; `gofmt -l` now **empty**. Pure formatting: whitespace +
|
||||||
|
optional-semicolon removal in reflowed inline closures. **One caveat handled:** this system's Go-1.26
|
||||||
|
`gofmt` doc-comment reformatter rewrites `''`→`”` in doc comments; it hit exactly one comment
|
||||||
|
(`app_export_domain_test.go`, "yields ''") — reworded to "yields the empty string" so the result is
|
||||||
|
gofmt-clean without a garbled quote. Build+vet+test green before and after; separate prior commit.
|
||||||
|
|
||||||
## 4. Tests + red-proofs (all recorded)
|
## 5. Part 3 — CLAUDE.md stale note
|
||||||
`go build ./... && go vet ./... && go test ./...` — **27 packages pass, 0 fail.** Template/design gates
|
Corrected the "vacation — agent DOWN at a remote site" block: felhom-pve is back on the home LAN, the
|
||||||
(template_id / emoji / mojibake / native-confirm) all OK.
|
agent is **up** (`localapi` binds `192.168.0.162:8443`, `active`, capabilities 68/68), Tailscale alias
|
||||||
|
still available. (Inside the feature commit.)
|
||||||
|
|
||||||
| Group | Test(s) | Red-proof (mutate → FAIL → restore) |
|
## 6. Green gates + build/deploy
|
||||||
|-------|---------|--------------------------------------|
|
`go build ./... && go vet ./... && go test ./...` — **27 packages pass, 0 fail**; template/emoji/mojibake
|
||||||
| A | `LegTimes` (02:30/23:30/22:00/00:00, wrap), `LegTimes_InvalidRejected`, `GateWindow` | drop the FmtHHMM modulo → `LegTimes("23:30")` yields "24:30"/"25:15" → **FAIL confirmed** |
|
gates OK. Built `felhom-controller:0.170.0`, deployed to guest 9201 → **Up (healthy)**,
|
||||||
| B | `EffectiveWindow` precedence table (settings>yaml>"02:30", corrupt falls through) | — |
|
`Controller elindult (0.170.0)`.
|
||||||
| C | `UpdateDaily_MutatesAndSignals`, `_UnknownAndInvalid`, `_GoroutineConsumesReschedule` | remove `case <-job.resched` → signal stays buffered → immediacy test **FAIL confirmed** |
|
|
||||||
| D | `scheduledRunAllowed` truth table (inside/boundary/outside/valve/wrap/nil-age) + Loop integration (defer outside / run inside / valve runs / **manual TriggerNow never gated**) | invert the valve `>`→`<` → starving-box + no-valve cases **FAIL confirmed** |
|
|
||||||
| E | `BackupWindowSave_Valid` (settings + 3× UpdateDaily + flash), `_Invalid` (nothing stored + error flash) | skip the `Valid()` check → invalid value gets stored → **FAIL confirmed** |
|
|
||||||
|
|
||||||
Existing quiesce/scheduler/web suites still pass unchanged.
|
**Live (authed curl to `172.17.0.2:8080`, `Host: felhom.demo-felhom.eu`, login 302):**
|
||||||
|
- `GET /` → **302 → /launcher**.
|
||||||
|
- `GET /dashboard` → **200**, renders `<h2>Vezérlőpult</h2>`, nav `href="/dashboard" class="active"`.
|
||||||
|
- `GET /launcher` → **200**, nav `href="/launcher" class="active"`.
|
||||||
|
|
||||||
## 5. Deploy + live validation (guest 9201)
|
## 7. Part 4 — Quiesce gate-denial live check (v0.168.0 caveat)
|
||||||
Built `felhom-controller:0.168.0` (145M) from the clean pushed tree; deployed via bootstrap →
|
**The v0.168.0 blocker (agent down) is GONE** — the agent is up and the gate is **code-live on 0.170.0**;
|
||||||
`Up (healthy)`, `Controller elindult (0.168.0)`. Startup registered the legs at the default window:
|
the quiesce loop is polling (`[quiesce] loop started (poll 5m0s…)`). **But the denial line is not
|
||||||
`db-dump 02:30 · tier2-backup 03:30 · offbox-backup 04:15` (CEST). Method: endpoint-level (in-guest
|
capturable right now**, for a *different* reason: the gate is only reached when a backup is **due**, and
|
||||||
curl to `172.17.0.2:8080`, `Host: felhom.demo-felhom.eu`; login 302 + scraped session CSRF).
|
the demo box is **within its cadence window** — `/backup/due` → `{"due":false,"reason":"within cadence
|
||||||
|
window","age_seconds":38260}` (~10.6 h since the last backup; default cadence `backup_cadence_seconds:0`
|
||||||
|
≈ 24 h). The debug ring (always-on for DEBUG) confirms **zero** quiesce gate lines. Also, "now"
|
||||||
|
(07:46 CEST) is *inside* the window `[04:30, 08:30)`, so even a due poll would ALLOW, not deny.
|
||||||
|
|
||||||
**Save a window (POST `/backups/window`, window_start=22:00) → 303:**
|
Forcing `due=true` would require changing the agent's `backup_cadence_seconds` — an **agent config
|
||||||
- Page re-rendered: input `value="22:00"`, derived rows `22:00 / 23:00 / 23:45`, rendszermentés
|
change, forbidden** by this task (read-only host). So the capture is **timing-gated**: the line will
|
||||||
`kb. 00:00–04:00 között` (W+2h..W+6h, wrap-safe).
|
emit on the next **due + outside-window** poll (next due ≈ this evening; any poll after `08:30` while
|
||||||
- Logs (NO restart): `db-dump rescheduled 02:30 → 22:00`, `tier2-backup 03:30 → 23:00`,
|
due denies). **The gate itself is fully unit-proven** (v0.168.0 Group D: outside→defer, safety-valve,
|
||||||
`offbox-backup 04:15 → 23:45`, `backup window set to 22:00 (legs 22:00/23:00/23:45)`.
|
manual-never-gated). **Nothing was enabled or changed** — logging stays `info`, the window stays default
|
||||||
|
`02:30`; no restoration needed. *This is the one item I could not fully close in-session; it needs a
|
||||||
|
timed capture (operator, or a scheduled check) when the box is next due outside its window.*
|
||||||
|
|
||||||
**Restore (window_start=02:30) → 303:** page shows `02:30 / 03:30 / 04:15` + `kb. 04:30–08:30 között`;
|
## 8. Part 5 — SMART-coverage spike (findings-only, STOP)
|
||||||
logs show the three legs rescheduled back. **Box left at 02:30.**
|
Full doc: `felhom.eu/documentation/audits/SPIKE-smart-coverage-2026-07-25.md`. Read-only probes on
|
||||||
|
`demo-felhom` (a **real** N100, not virtualized). **Both physical disks report PASSED** via the exact
|
||||||
|
allowlisted `smartctl -a -j <dev>` (system SSD `sda` "AirDisk 512GB SSD"; USB HDD `sdb` "TOSHIBA
|
||||||
|
MQ04ABF100"). The card shows "Nincs adat" because the **agent never reads** them:
|
||||||
|
1. **`local` (system SSD) → UNKNOWN:** it's a `dir` on **LVM `pve-root`**; `backing_device` is empty and
|
||||||
|
`smartDeviceFor` has **no dm/LVM branch**, so `/`→`pve-root`→`sda3`→`sda` is never resolved.
|
||||||
|
2. **USB `sdb` → ABSENT:** surfaced only via the **non-enriched `driveTargets.Known` union path** (sets
|
||||||
|
`backing=/dev/sdb` but never calls `enrich`).
|
||||||
|
|
||||||
## 6. NOT live-exercised on 9201 (stated method)
|
`-d sat` is **not needed** (the bridge passes SMART through with `-a -j`) and **not in sudoers** anyway
|
||||||
- **The disk-tier window-gate DEBUG denial** could not be triggered live: the demo host agent is DOWN
|
(only `-a -j /dev/sd[a-z]*` etc. are granted). The `by-uuid` symlink hypothesis doesn't apply (backing
|
||||||
for the remote-site window (its `localapi` binds `192.168.0.162`, which doesn't exist there), so the
|
is already the resolved `/dev/sdb`). **Graded fix directions (do-not-implement):** **B** — enrich the
|
||||||
quiesce loop's `/backup/due` call errors before the gate is reached. The gate + safety valve + the
|
union path (LOW risk, reaches the USB) **first**; **A** — dm/LVM resolution (MEDIUM risk, multi-PV care,
|
||||||
manual-never-gated property are fully covered by Group D (truth table + Loop integration). Re-verify
|
reaches the SYSTEM disk) **second**; **C** — `-d sat` + sudoers widening **rejected** (disproven here).
|
||||||
the live DEBUG line once the agent is reachable again.
|
**STOP — no fix, no agent/sudoers/manifest change.**
|
||||||
- Browser visual check of the "Mentési időablak" card layout is Viktor's acceptance step (the rendered
|
|
||||||
HTML strings are confirmed; the pixels are not).
|
|
||||||
|
|
||||||
## 7. Observations (out of scope — documented only)
|
## 9. Observations (out of scope)
|
||||||
- Saving explicitly writes `backup_window_start:"02:30"` — functionally identical to the empty/default
|
- The `catchall.html` "Vezérlőpult" button uses `{{.ControllerURL}}` (the controller base URL) — it now
|
||||||
state, so the box is correctly "at 02:30" either way.
|
lands on the launcher via the `/` redirect. A pre-existing label/destination nuance on a fallback page;
|
||||||
- Cadence for the safety valve defaults to 24h **controller-side** (the agent's `/backup/due` response
|
left as-is (out of scope). Reword to "Indítópult" or point it at `/dashboard` in a future polish.
|
||||||
carries no cadence field); if cadence policy ever becomes hub/agent-driven, thread it through instead
|
- The v0.170.0 image gives the whole disk-health feature a real-hardware home once the SMART-coverage
|
||||||
of the constant.
|
fix (Part-5 directions) ships — the card will flip from "Nincs adat" to real PASSED verdicts.
|
||||||
- Three Budapest-timezone loaders now exist (scheduler, web, quiesce) — noted in REUSE.md §6; kept
|
|
||||||
local to avoid a scheduler↔quiesce import edge.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user