docs: REPORT.md for TASK A storage-lifecycle cleanups (v0.64.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 17:40:04 +02:00
parent e850402a95
commit c37ee6d43a
+81 -43
View File
@@ -1,56 +1,94 @@
# REPORT — live-drive Batch 1 (+F17) → controller v0.61.0 (2026-06-14)
# REPORT — TASK A storage-lifecycle cleanups → controller v0.64.0 (2026-06-14)
Implemented the controller-side fixes from `LIVE-DRIVE-FIXSPEC-2026-06-14.md` (Batch 1 + the CRITICAL
F17), shipped as **v0.61.0** to demo guest 9201. Each fix has a regression test that fails on the
pre-fix code; the authoritative `go build && go vet && go test ./...` is green on the Linux build server.
Two settings-layer cleanups (A1, A2) + two doc-only changes (A3, A4) from the F9 storage-registration
diagnosis. Trunk-based, all on `main`, no branches. Each code change has a table-driven test that fails on
the pre-fix code. Built, pushed, and deployed to demo guest 9201; verified `:0.64.0 Up (healthy)` with a
clean startup (`controller_started (0.64.0)`, no errors).
## Fixes (each its own commit)
> Verify against pushed source, not this report.
| Finding | Sev | Change | Test | Live-verified |
|---|---|---|---|---|
| **F17** | CRIT | per-app restore now **replays the captured `.sql` dump** (new `appbackup.ImportDump` + `backup.reimportDBDumps`), AFTER volume restore so the **dump wins**; volume/DB failures now surface | `restore_db_test.go` (injectable seams) | **marker DB round-trip PASSED** on romm |
| **F1** | HIGH | guest RAM cap from `docker info` (daemon runs in LXC); deploy guard "used" = committed memory | `info_cgroup_test.go` (cgroup + docker-info paths) | `total_mem_mb=2048` (was 15771) |
| **F20-BUG1** | HIGH | `agentapi.FormatDisk` surfaces non-2xx/`ok:false` instead of zero-value success | `disks_test.go` (502→err) | unit |
| **F5 (catalog)** | HIGH | uptime-kuma healthcheck → v2 `extra/healthcheck` binary + 180s start_period | — (catalog) | uptime-kuma **healthy → route 302** (was 404) |
| **F5 (dashboard)** | HIGH | `routeUnpublished` helper + distinct indicator for unhealthy/restarting deployed apps | `route_unpublished_test.go` | render test |
| **F8** | LOW-MED | `controller.yaml` persisted 0600 (holds infra tokens) | `config_perms_test.go` | unit (Linux) |
| **F6** | LOW | deploy POST → 202 + "Telepítés elindítva" (was "deployed" before compose) | — | code/UI |
| **F7** | LOW | `status-refresh` 30s → 10s | — | cadence |
| **F4** | TRIV | `GET /api/stacks/rescan` → 405 + `Allow: POST` | — | `→ 405` |
## Pre-flight
## Key implementation notes
`main` is the only branch in felhom-controller; HEAD was `688ba0d v0.63.0` (CHANGELOG top + live container
both `:0.63.0`). Confirmed before starting.
- **F17 reuse decision:** `ImportDump` lives in `internal/appbackup` (the DB-domain package with
`DiscoverDatabases`/`DumpOne`/`getMariaDBPassword`), NOT reusing `appexport`'s unexported copies —
`appexport→appbackup` already exists, so the reverse would import-cycle. `DiscoveredDB` carries the live
container's own creds, so no env threading. Precedence: replay runs after volume restore → `.sql` wins.
- **F1 reroot:** the FIXSPEC's cgroup-limit approach was a **no-op** on the demo (the container's own
cgroup is unlimited; the 2 GB cap is on the LXC ancestor, hidden; no lxcfs in the container). The
working source is `docker info` MemTotal. The deploy guard's "used" switched from host RSS (not
per-guest observable) to the controller's committed-memory accounting.
## Per-item results
## Deploy & verification
### A1 — `AutoDiscoverStoragePaths` made ADDITIVE
- **File:** `controller/internal/settings/settings.go` (the function previously at L584).
- **Change:** removed the `if len(s.StoragePaths) > 0 { return }` early-return. It now registers only
discovered paths NOT already present. Invariants enforced:
- Never removes/modifies a manually-added path.
- SKIPS any path already in the registry IN ANY STATE — including a `Decommissioned` soft-marked entry —
so it is never re-added or reactivated (`if existing[path] { continue }`, keyed on `filepath.Clean`).
- Never flips `IsDefault`: a newly-discovered path becomes default ONLY if the registry currently has no
default at all (and then only the first such new path) — `IsDefault: !hasDefault`, with `hasDefault`
seeded from existing entries and set true after the first new default.
- **Tests:** new `controller/internal/settings/storage_discovery_test.go`
(`TestAutoDiscoverStoragePaths_Additive`, table-driven, + `..._DecommissionedNotReactivated`):
- (a) non-empty registry + a deployed-app path missing from registry → exactly that path registered;
pre-existing entry byte-identical via `reflect.DeepEqual` (IsDefault asserted unchanged); new path not
promoted to default.
- (b) Decommissioned path present + a deployed app still pointing at it → NOT reactivated; entry
byte-identical; path count unchanged.
- extra cases: empty registry (first becomes default), registry-with-no-default (first new becomes
default), fallback path pickup.
- **Non-hollow proof:** temporarily removing the `if existing[path] { continue }` guard makes
`..._DecommissionedNotReactivated` FAIL (`path count changed: got 4 want 2`); restored → green.
- **Commit:** `2d4d432` (shared with A2). **Test result:** `ok internal/settings`.
- Built+pushed `gitea.dooplex.hu/admin/felhom-controller:0.61.0`; deployed via the guest-9201
golden/bootstrap mechanism (`/etc/felhom-controller-image` + restart bootstrap). Healthy, `/api/health`
+ dashboard 200, no startup errors.
- Live-verified: F1 (`total_mem_mb=2048`), F4 (405), F5 (uptime-kuma healthy → route 302), **F17 (marker
row dropped after backup was restored by `/backup/restore`)**.
### A2 — internal-SSD label disambiguation
- **File:** `controller/internal/settings/settings.go` (`InferStorageLabel`).
- **Change:** when `filepath.Base(path) == appbackup.FelhomDataDir` (the `"felhom-data"` namespace dir), the
label is now `Belső SSD (rendszer)` instead of the colliding `Tárhely (felhom-data)`. Imported
`internal/appbackup` for the constant (no import cycle — appbackup only depends on `internal/util`).
Still overridable via `SetStorageLabel`.
- **User-drive safety confirmation:** Model-A user drives register their MOUNT ROOT (e.g. `/mnt/felhom-usb`)
— confirmed live on 9201, where the user drive is registered as `/mnt/felhom-usb`, never
`.../felhom-data` — so the `base == felhom-data` match cannot mislabel a user drive.
- **Untouched:** the separate host-metrics label in `web/agent_host_metrics_handler.go:91` was left as-is
(display-only host-metrics path; A2 is the storage-registry label only).
- **Tests:** `TestInferStorageLabel` covers `/mnt/sys_drive/felhom-data``Belső SSD (rendszer)`, a
second felhom-data path, plus the existing hdd/ssd/usb/`Tárhely` branches (note: `felhom-usb` does NOT
start with `usb`, so it correctly labels `Tárhely (felhom-usb)` — pre-existing behavior).
- **Demo seeded-label update (before/after):** the seeded `settings.json` on 9201 had
`"/mnt/sys_drive/felhom-data" → "Tárhely (felhom-data)"`. Updated **through the real UI endpoint**
(`POST /settings/storage/label``SetStorageLabel`, the same handler the dashboard form posts to) to
`"Belső SSD (rendszer)"`; verified persisted in `settings.json`. (Note: the public URL
`https://felhom.demo-felhom.eu` returned 200 without applying the change — the Cloudflare/Traefik path
mangled the POST body; posting to the container's own `127.0.0.1:8080` returned 302 success and
persisted. Same handler either way.) Before: `Tárhely (felhom-data)`. After: `Belső SSD (rendszer)`.
- **Commit:** `2d4d432` (shared with A1). **Test result:** `ok internal/settings`.
## Test approach
### A3 — doc: reject auto-register-on-attach (no deploy)
- **File:** `felhom.eu/documentation/backlog/DIAGNOSIS-f9-storage-registration-gap-2026-06-14.md` (the doc
lives in `backlog/`, not `audits/` as the task said).
- **Change:** recommendation #1 annotated **REJECTED (2026-06-14)** — contradicts the new→enrolled
manual-enrollment model; manual enroll is by design. Noted that the "make discovery additive" sub-point
WAS adopted (v0.64.0 A1) but only for paths deployed apps reference, not auto-register of agent-attached
drives.
- **Commit:** `b5a9614` (felhom.eu repo).
Controller code is largely `//go:build linux`; dev host is Windows. Per-commit gate =
`CGO_ENABLED=0 GOOS=linux go build && go vet` locally (compiles code + tests); authoritative
`go test ./...` ran on the Linux build server (all green, incl. the linux `system` tests).
### A4 — doc: live-validation rule (no deploy)
- **Files:** `felhom-controller/CLAUDE.md` and `felhom-agent/CLAUDE.md` — added the verbatim "Live
validation" paragraph to both.
- **Commits:** `e850402` (felhom-controller), `8e6d00a` (felhom-agent).
## Not in this batch (SUPERVISED, next session — agent/golden)
## Build / deploy / verify
**F9** (HDD provisioning/guest-attach + reporting), **F20-BUG2** (durable_id scheme), **F20-BUG3**
(async mkfs). These touch the host agent, golden template, and bring-up (blast radius beyond the
disposable demo).
- `go build ./... && go vet ./internal/settings/ && go test ./...` → all green locally.
- Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.64.0` on 192.168.0.180.
- Deployed to guest 9201 via the golden/bootstrap mechanism (`docker pull` → write
`/etc/felhom-controller-image` → restart `felhom-controller-bootstrap.service`).
- **Verify:** `docker ps``gitea.dooplex.hu/admin/felhom-controller:0.64.0 Up (healthy)`; logs show
`Event pushed: controller_started (info) — Controller elindult (0.64.0)` and no startup errors. No
rollback needed.
## Final state
## Commit hashes (per item)
Demo guest 9201 on `:0.61.0`, healthy; agent unchanged. Apps: actualbudget, gokapi, mealie, privatebin,
romm(+db,+redis), uptime-kuma (now **healthy**), vikunja — all healthy. felhom-usb still not attached to
the guest (F9, deferred).
| Item | Repo | Commit |
|---|---|---|
| A1 + A2 (code + tests + CHANGELOG + README) | felhom-controller | `2d4d432` |
| A4 (CLAUDE.md) | felhom-controller | `e850402` |
| A4 (CLAUDE.md) | felhom-agent | `8e6d00a` |
| A3 (diagnosis annotation) | felhom.eu | `b5a9614` |