Files
felhom-controller/REPORT.md
T

55 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REPORT — TASK C2: one-click class-C file restore from the Tier-2 copy (closes F2)
**Date:** 2026-07-05 · **Class:** implementation (code + tests + deploy + live validation)
**Baseline:** `main` @ `0313ecd` (v0.99.0, matched the task's confirmed baseline exactly; not behind origin)
**Shipped:** **v0.100.0**, live on guest 9201 (`gitea.dooplex.hu/admin/felhom-controller:0.100.0`, healthy)
**Outcome:** drill finding **F2 CLOSED** — the whole C-series (F1/F2/F3/O4 from `DRILL-appdata-restore-2026-07-04.md`) is now resolved.
## Commits (all on `main`)
| Hash | What |
|---|---|
| `30b1110` | Part 1 — `RestoreTier2Files` engine + `rsyncRestoreMissing` + copier seam + tests |
| `27aeb41` | Parts 2+3 — `POST /backup/tier2/restore` + "Fájlok visszaállítása" button + handler-guard test |
| `85a5727` | Part 4 — CHANGELOG v0.100.0, CONTEXT (F2 closed), REUSE, controller/README |
| felhom.eu `7e42ca6` | backup-architecture.md — class-C in-place path, additive-only semantics, reindex caveat |
**Files (code):** `internal/backup/{tier2_restore.go(new), backup.go(seam)}`, `internal/web/{server.go, handlers.go, templates/backups.html}`. Tests (new): `internal/backup/tier2_restore_test.go`, `internal/web/tier2_restore_handler_test.go`. Template gates (id + emoji) green.
## What shipped
- **Engine** `Manager.RestoreTier2Files(stack) (int, error)`: in-place, **additive-only** restore of missing user files from the **recorded** Tier-2 copy (`CrossDriveBackup.DestinationPath` + the `backups/secondary/<stack>/appdata` literals from `RunTier2` — never a fresh `selectTier2Target`). Single-flight with backup/restore; **all refusals before any stop**, each with a customer-readable Hungarian reason (no copy / never ran / copy dir gone / Tier-2 drive disconnected / live drive disconnected or decommissioned); stop → copy → start → 90s health-wait; copy and restart errors surface (F17), health timeout only warns.
- **Copier** `rsyncRestoreMissing`: `rsyncMirror`'s exec shape (mkdir, 60m ctx, trailing-slash contents copy, rsync's own exit code) with the opposite-direction flags — **`-a --ignore-existing --itemize-changes`**. Existing live files never overwritten; nothing ever deleted (the `--delete` trap). Count = `>f` itemize lines (pure `countRestoredFiles`); file names logged at DEBUG only, never INFO.
- **Endpoint + UI**: `POST /backup/tier2/restore` (backupRestoreHandler-shaped guards) + the button on the **healthy Tier-2 row only** (Tier2Configured branch already excludes disconnected/inactive; extra `Tier2LastRun` gate), with the confirm dialog naming the additive-only contract + last-copy timestamp. Flash strings exactly per spec; n==0 is a success.
- **Out of scope honoured:** no overwrite/point-in-time mode, no per-file selection, `recovery-unit/` untouched, offbox untouched, C1-report Obs 1/2/3 untouched.
## Tests & the §10 companion
`go build ./... && go vet ./... && go test ./...` green after every commit. **Top-level test functions: 286 → 293 (+7).**
- Orchestration (seam): stop→copy→start order recorded; copier src = the recorded Tier-2 layout, dst = live `AppDataDir`; count passthrough.
- Refusals C1C5: each asserts the **non-effect**`StopStack` never called, copier never invoked (C5 via a held running flag). C6 at the handler (`httptest`; nil backupMgr would panic if reached — it isn't).
- Scenario D: `(0, nil)` success; copy-error surfacing (app still restarted).
- FS-level semantics against **real rsync** (LookPath-skipped on Windows; **PASS on the build server**): deleted file restored byte-identical + counted; differing live file keeps live bytes; live-only file survives.
- **Companion red-proof (run on the build server, where rsync exists):** flags swapped to `rsyncMirror`'s (`-a --delete`, no `--ignore-existing`) → the test failed **on both harms**: `live-edited b.txt was CLOBBERED: "BACKUP-VERSION"` **and** `live/c.txt: no such file or directory` (plus count 2≠1). Mutation reverted (`git status` clean on the build server), test green again.
## Deploy + live validation (§13, felhom-pve / guest 9201 only — Peti's box untouched)
Build `./build.sh 0.100.0 --push` on 180; bootstrap deploy; verify `:0.100.0 Up (healthy)`, no fatal/panic.
**Method note:** the claude-in-chrome bridge disconnected mid-session, so the first pass verified the **rendered UI HTML** (button + confirm + hidden `stack_name`, exactly once on nextcloud's Tier-2 row) and exercised the restore as the **exact form POST the button submits**. After Chrome was reopened, the **real click was also validated end-to-end** (step 8 below) — the UI path is now fully click-proven, no proxy caveat remains.
1. **Fixtures:** sentinel `d982fa3b….bin` (sha `1e5f82d7…`) + `modify_me.txt` (sha `c5ca964b…`) planted in live `appdata/nextcloud/drill``POST /api/backup/tier2` → both in the Tier-2 copy on felhom-flash, shas identical.
2. **Three fates staged after the copy:** `.bin` deleted live; `modify_me.txt` edited live (new sha `bd24729a…`); `live_only.txt` created live (sha `3c3474d5…`).
3. **Restore:** `302 → flash "nextcloud: 1 fájl visszaállítva a másodlagos másolatból."` Log: `Tier-2 file restore for nextcloud: …/felhom-flash/…/appdata → …/felhom-usb/appdata/nextcloud (additive-only)``1 file(s) restored (15s)`.
4. **All three fate assertions PASSED:** `.bin` back **byte-identical** (`1e5f82d7…`); `modify_me.txt` kept its **LIVE** sha (`bd24729a…`, NOT the backup's `c5ca964b…`); `live_only.txt` **survived** (`3c3474d5…`). Nextcloud healthy after restart.
5. **Idempotency (Scenario D):** immediate rerun → `flash "Nincs hiányzó fájl — minden fájl megvan a helyén."` (success, 0 files).
6. **Refusal non-effect:** sparkyfitness (no Tier-2 record) — the button is **absent** from its row (rendered-HTML count = 1, nextcloud only); direct POST → `flash_error "Fájl-visszaállítás sikertelen: nincs másodlagos fájlmásolat ehhez az alkalmazáshoz"`; container IDs + uptimes **unchanged** (never stopped).
7. **Cleanup:** drill dir removed live; fresh Tier-2 run purged the copy side (mirror semantics); zero residue both sides; final `docker ps` all healthy.
8. **Real-click UI validation (Chrome reopened):** fresh sentinel (`click.txt`, sha `6ce3342e…`) → Tier-2 run → deleted live → **the actual "Fájlok visszaállítása" button clicked in the dashboard** (Nextcloud row expanded; the native confirm dialog fired with the exact spec text incl. the live last-copy timestamp `2026-07-05T11:35:49Z`, auto-accepted via a test-only `window.confirm` override to avoid the extension-blocking dialog) → flash banner **"nextcloud: 1 fájl visszaállítva a másodlagos másolatból."** rendered on the page (screenshot) → file back **byte-identical** (`6ce3342e…`), nextcloud healthy. Cleanup repeated: zero residue, all healthy.
## Observations (not acted on)
1. **Reindex caveat is real but untested in-app** — the restored file returns at filesystem level; whether Nextcloud's own UI lists it may require `occ files:scan` (documented in backup-architecture.md; not exercised — the drill fixture lives outside Nextcloud's indexed user dirs).
2. **Carried from C1 (still open, unchanged):** O4-residual/F3 interaction, compose stderr truncation, compose volume-label warnings, demo-box unauth/empty-CSRF exposure.