B2b: decommission orchestration + missing-storage indicator + re-enroll fix (v0.65.0)

agentapi.Decommission + handleStorageDecommission (migrate-all-or-none, Change 2):
migrate-then-decommission via the migration done-hook, or decommission-anyway (stop
apps, keep HDD_PATH). 'Hiányzó tárhely' badge on dashboard/stacks/app card when an
app's drive is decommissioned/disconnected/absent. Change 4: registerStoragePath
clears the decommissioned marker on re-enroll (ClearDecommissioned had no callers).
Non-hollow tests incl. mutation-proven Change-4 companion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 20:08:52 +02:00
parent 16a4c3e878
commit f2596ea433
13 changed files with 435 additions and 35 deletions
+43
View File
@@ -1,5 +1,48 @@
## Changelog
### v0.65.0 — data migration + self-serve decommission (B1+B2) (2026-06-14)
Customer-self-serve storage **migration** (move app data between drives) and **decommission** (retire
a drive), implemented trunk-based with the locked spike design
(`felhom.eu/documentation/audits/SPIKE-decommission-migration-2026-06-14.md`). Pairs with agent
v0.32.0 (the self-serve `/disks/decommission` endpoint + intent-aware re-assert). Built + deployed to
demo guest 9201. **Live decommission/migration of real data is NOT yet validated — that is the
supervised B3 session.**
- **B1 — migration engine** (`internal/stacks/migrate.go`). In-process over the controller's
`/mnt:/mnt:rslave` RW mount; crash-safe + resumable via a single journal (`<dataDir>/migration.json`).
Two entry points share one pipeline: `MigrateAll` (whole namespace — every app + a conflict-merge
walk for non-app/customer content) and `MigrateApp` (one app subtree; handles drive→drive AND
SSD→drive). Pipeline: validate → stop → copy (`rsync -a --checksum`, additive, NO `--delete`) → verify
(`rsync -ani --checksum`, zero pending) → flip+redeploy (`RedeployFromEnv`, one idempotent unit) →
cleanup. **CLEANUP is the only destructive step and is gated on every unit verified AND every app
redeployed.** Conflict-merge: skip-identical (checksum vs the target file AND its `(N)` siblings),
rename-on-differ to the lowest-free `<base>(N)<ext>`, never overwrite; idempotent (no `(1)(1)`).
Single-flight; **mutual exclusion with the backup orchestrator** (Change 3 — migration refuses while a
backup runs; the scheduled DB-dump/Tier-2 skip while a migration runs).
- **B1 UI**`POST /api/storage/migrate` (whole-namespace), `POST /api/storage/migrate-app` (per-app),
`GET /api/storage/migrate/status` (poll). The greyed migrate-all `<span>` in settings.html is now a
real target-select + button; app_info.html gains a per-app "Áthelyezés másik tárhelyre" control; both
share a Hungarian progress panel.
- **B2b — decommission orchestration** (`handleStorageDecommission`, `POST /api/storage/decommission`).
Two choices, no partial (Change 2): **migrate-all-then-decommission** (runs `MigrateAll`; the
migration done-hook soft-marks the source + calls the agent once every app has moved) or
**decommission-anyway** (type-to-confirm; stops the apps but KEEPS their `HDD_PATH` so they show
"missing storage"). `agentapi.Decommission` added; both branches end at `SetDecommissioned` (soft
marker retained — blocks A1 resurrection) + agent `Decommission`.
- **"Hiányzó tárhely" indicator** — a deployed app whose `HDD_PATH` resolves to a decommissioned/
disconnected/absent registry path now shows a distinct warning badge on the dashboard, stacks page,
and app card (label via `GetStorageLabel`); persists until re-enroll or migrate.
- **Change 4 — re-enroll clears the marker.** `registerStoragePath` now un-retires a re-plugged
decommissioned drive (`ClearDecommissioned` + restore `Schedulable`) — previously `AddStoragePath`
deduped the re-register into a no-op and the soft marker (and the apps' missing-storage badge) would
persist forever. (`ClearDecommissioned` had zero callers before this.)
- Non-hollow tests across `internal/stacks` (engine: collision-refuse, merge dedup/idempotency,
cleanup-only-after-redeploy, verify-catches-corruption, resume, single-flight, SSD→drive, backup
exclusion), `internal/backup` (scheduled backup skipped while migrating), and `internal/web`
(finalize soft-mark+agent, re-enroll clears marker, missing-storage label). Companions for the
collision guard, cleanup gate, and Change-4 clearing were mutation-proven to fail on the pre-fix code.
### v0.64.0 — storage-lifecycle cleanups (2026-06-14)
Two settings-layer cleanups from the F9 storage-registration diagnosis