docs: REPORT.md for controller v0.69.0 infra-backup stub removal
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,96 +1,47 @@
|
||||
# REPORT — two UI cleanups + M3 live re-verification (2026-06-15)
|
||||
# REPORT — controller v0.69.0: remove dead infra-backup stubs + unused restic-password report field (2026-06-16)
|
||||
|
||||
**Deployed:** controller **v0.68.3** on guest 9201 / felhom-pve (bootstrap-managed; healthy).
|
||||
**Commits:** `37ed757` (v0.68.2 CSS) · `a821a9d` (v0.68.3 refresh fix). Trunk-based, pushed to `main`.
|
||||
**Deployed:** controller **v0.69.0** on guest 9201 / felhom-pve (bootstrap-managed; healthy, hub-reporting OK).
|
||||
**Scope:** controller half of Phase-1 Infra Backup retirement (hub **v0.12.0**). See
|
||||
`felhom.eu/documentation/audits/SPIKE-infra-backup-2026-06-15.md`. Commit `9f59bc2`.
|
||||
|
||||
---
|
||||
## What changed (pure dead-code removal — no behaviour change)
|
||||
|
||||
## Part 1 — stack-card state-badge clipping fix (v0.68.2, CSS-only)
|
||||
- **Removed `report.Pusher.PushInfraBackup`** (`internal/report/pusher.go`) — pushed the infra-backup
|
||||
payload to the now-removed hub endpoint `POST /api/v1/infra-backup`. Dead since slice 8C; no callers.
|
||||
- **Removed `notify.Notifier.NotifyBackupCompleted`** (`internal/notify/notifier.go`) and the
|
||||
`backup_completed` event — caller-less since whole-guest backup moved to the agent in slice 8C. The
|
||||
hub's backup-deadline check now reads the agent host-report's PBS snapshots instead. `NotifyBackupFailed`
|
||||
and the DB-dump notifiers are untouched and still used.
|
||||
- **Removed `report.BackupReport.ResticPassword`** (`internal/report/types.go`, `json:"restic_password"`).
|
||||
|
||||
**Symptom:** on the /stacks page, an **unhealthy** app's state badge clipped — "Nem egészséges" rendered
|
||||
as "Ner…". Healthy / not-deployed cards were fine.
|
||||
## STEP-2 gate — confirmed before removing ResticPassword
|
||||
|
||||
**Root cause (confirmed):** `.stack-detail-header` is `flex` / `space-between` holding `.stack-title-row`
|
||||
(logo + title + subdomain link + the `route-unpublished` warning) and `.stack-state-badge`. Only on an
|
||||
unhealthy app does the long "⚠ URL nem elérhető – útvonal nincs publikálva" warning render, inflating the
|
||||
title-row. The title-row had no `min-width:0` (so it wouldn't shrink below its content) and the
|
||||
`white-space:nowrap` badge had no `flex-shrink:0` (so the flexbox compressed the *badge* instead → clip).
|
||||
The task required proving the live builder leaves `ResticPassword` empty (else it would be an ongoing
|
||||
restic-password leak to the hub). Confirmed two ways:
|
||||
- **Source:** `buildBackupReport` (`internal/report/builder.go`) constructs `BackupReport{Enabled:…}` +
|
||||
`LastDBDump` only; the comment at the disk-tier seam states restic/snapshot fields are left zero. No
|
||||
code anywhere assigns `BackupReport.ResticPassword`.
|
||||
- **Live data (hub DB, read-only):** the latest controller reports (v0.68.3) carry **no**
|
||||
`restic_password` field at all. So no ongoing leak — safe to remove.
|
||||
|
||||
**Fix** (`internal/web/templates/style.css`):
|
||||
```css
|
||||
.stack-title-row { flex: 1; min-width: 0; } /* shrinks + wraps its own content */
|
||||
.stack-state-badge { flex-shrink: 0; } /* never compressed */
|
||||
```
|
||||
> **Flagged (separate, worse historical finding — not in this task's scope):** the hub's legacy
|
||||
> `reports` table holds **thousands of historical rows with a plaintext `restic_password` value** from
|
||||
> *old* controller versions that DID populate it. The current controller does not, and this removal
|
||||
> makes the field structurally unsendable, but the historical rows persist on the hub — a distinct
|
||||
> purge/rotation decision for the operator.
|
||||
|
||||
**Browser-verified on /stacks (demo, guest 9201):**
|
||||
- BEFORE (v0.68.1): komga badge clipped to "Ner…" at the card's right edge (screenshot captured live).
|
||||
- AFTER (v0.68.2): komga badge reads the full **"Nem egészséges"**; the "⚠ URL nem elérhető…" warning
|
||||
**wraps within the title column** (screenshot captured live).
|
||||
- Regression: Mealie ("Fut", healthy) and all "Nincs telepítve" cards visually unchanged.
|
||||
## Verification
|
||||
|
||||
**Defensive note (not applied):** the spec offered adding `flex-shrink:0` to
|
||||
`.badge-missing-storage`/`.badge-orphaned` *if* a card showing BOTH unhealthy AND a missing-storage/
|
||||
orphaned badge still clipped. No such card exists live right now, and the primary fix's mechanism
|
||||
(title-row `min-width:0` absorbing all shrink) already shields sibling badges, so it was left out — a
|
||||
one-liner if that combo ever appears.
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — M3 live re-verification (no code change — the migrate path is correct)
|
||||
|
||||
**Goal:** prove the migrate merge-walk re-asserts the `2775`-setgid/gid-1000 convention on a
|
||||
**pre-existing STALE dir** (the B3 calibre case), which the acceptance audit had only covered by unit
|
||||
test + static convention.
|
||||
|
||||
**Method:** two full **migrate-all** runs driven through the dashboard UI (the real
|
||||
`/api/storage/migrate` endpoint, all 8 flash apps: audiobookshelf, calibre-web, immich, jellyfin, komga,
|
||||
paperless-ngx, radarr, romm): **flash→usb**, then **usb→flash** (restoring apps to the default drive).
|
||||
Before each, the migrate **target** was pre-seeded host-side with deliberately-stale `0755`,
|
||||
**non-setgid** userdata dirs; `stat` recorded.
|
||||
|
||||
**Result — PASS (clean, non-hollow):**
|
||||
- Pre-seeded `userdata/documents` at `755 gid1000` — a dir **no app mounts** — became
|
||||
**`2775 gid1000`** after migrate (the `EnsureUserdataDir` re-assert in `walkMerge`, migrate.go:854-859).
|
||||
Same for the seeded `userdata` root and `userdata/import`. Whole target userdata tree = `2775`.
|
||||
- Integrity: static seeded files (`b3-document.txt`, `b3-movie.txt`, `b3-photo.txt`, `demo.jpg`) had
|
||||
**identical sha256** before/after.
|
||||
- All 8 apps redeployed **healthy** on the target (komga "unhealthy" = its pre-existing
|
||||
`/api/v1/actuator/health` 401 — serving, unrelated to migration).
|
||||
|
||||
**Secondary finding — the `import/calibre` 755 is an APP clobber, not a migrate bug.** In both runs
|
||||
`userdata/import/calibre` ended at `755`. Isolated live: **calibre-web (CWA)** bind-mounts
|
||||
`${USERDATA_PATH}/import/calibre` as `/cwa-book-ingest` and **chmods it to 755 (strips setgid) on every
|
||||
startup** — proven by setting it to `2775` and restarting calibre-web alone (reverted to `755`, no
|
||||
migration). The merge-walk *does* re-assert `2775` during copy; calibre-web re-clobbers it during the
|
||||
flip/redeploy phase (this is also why the source flash calibre was already `755` pre-migration).
|
||||
**migrate.go was therefore NOT modified** — the M3 path works. If the convention must hold for that
|
||||
single-app ingest dir, the fix belongs in the catalog/app layer (an `umask`/entrypoint wrapper for CWA),
|
||||
not in migrate.
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — Beállítások endless-refresh loop fix (v0.68.3) — found during Part 2
|
||||
|
||||
**Symptom (user-reported):** after any migration finished, the settings page reloaded itself every
|
||||
~1.5 s, forever.
|
||||
|
||||
**Root cause:** `MigrationStatus` keeps returning the last `done` job indefinitely (never cleared). The
|
||||
settings page's resume-view IIFE called `migWatch()` for *any* returned job, and `migWatch`'s `done`
|
||||
branch does `setTimeout(()=>location.reload(),1500)`. So every load saw the persisted `done` job → watched
|
||||
it → reloaded → saw it again → endless loop.
|
||||
|
||||
**Fix** (`internal/web/templates/settings.html`): the resume-view now starts the watcher **only for an
|
||||
in-progress job** (`phase!=='done' && phase!=='aborted'`). The one-time post-completion reload still fires
|
||||
from the *active* watcher started by `storageMigrateAll`, so a real migration refreshes drive state once
|
||||
when it finishes — but a stale terminal job no longer triggers the loop.
|
||||
|
||||
**Browser-verified on v0.68.3:** with a persisted `done` job present in the journal, the settings page
|
||||
stayed put for 25 s (`navType:navigate`, planted marker survived, migration panel idle) — no reload.
|
||||
|
||||
---
|
||||
|
||||
## Resting state
|
||||
- Apps healthy on **felhom-flash** (the default drive); **felhom-usb** cleaned/empty.
|
||||
- Controller **v0.68.3** deployed live on guest 9201.
|
||||
- Build/deploy: built+pushed on 192.168.0.180, deployed via the bootstrap mechanism
|
||||
- `go build ./... && go test ./...` — green; `go vet` clean. No residual references to
|
||||
`PushInfraBackup` / `NotifyBackupCompleted` / `BackupReport.ResticPassword` (the `config.go`
|
||||
`ResticPasswordFile` is the separate restic-password-file *path* config, intentionally untouched).
|
||||
- Built `felhom-controller:0.69.0`, deployed to guest 9201 via the bootstrap mechanism
|
||||
(`docker pull` → `/etc/felhom-controller-image` → restart `felhom-controller-bootstrap.service`).
|
||||
- Live: container `Up … (healthy)` on 0.69.0; startup clean (no errors/panics); hub connectivity
|
||||
`HTTP 200`; `Hub report pushed successfully (8309 bytes)` — confirming the caller-less removal causes
|
||||
no behaviour change and reporting still works.
|
||||
|
||||
## Out of scope
|
||||
|
||||
Credential rotation (operator); the historical `reports`-table restic_password rows (flagged above);
|
||||
the Komga healthcheck.
|
||||
|
||||
Reference in New Issue
Block a user