Files
felhom-controller/REPORT.md
T
admin a5d870ea0e docs(shares): REPORT.md — R-7b v0.145.0 ship report
Findings, all six red-proof outcomes, deployment, live-validation evidence, and an
explicit list of what was NOT live-exercised (offsite leg + restore round-trip: the
demo box has no offsite target).
2026-07-18 13:34:59 +02:00

11 KiB
Raw Blame History

REPORT — most recent implementation

felhom-controller v0.145.0 — R-7b: share data enters the live backup runs (Model B) + samba liveness — 2026-07-18

Class: Implementation. Shipped: controller v0.145.0, deployed and live-validated on demo guest 9201.

1. Baselines (re-confirmed at session start)

Repo main @ start Version → Shipped
felhom-controller 3dfc49e v0.144.0 v0.145.0
felhom.eu b76dad0 docs tip docs only

Trunk-based, explicit-path staging, no Co-Authored-By.

The repo root also held a stale, unrelated TASK.md ("Telemetry Debug Section"). It was ignored — the R-7b prompt was the spec.

2. What shipped, and why it matters

Before this release the „Megosztás" page rendered „Felhőmentés: bekapcsolva" for a share whose files were in no backup at all. backup.RunTier2 short-circuits on os.Stat(unitDir) before the classification seam, and the offsite runner enumerates GetOffboxApps(); a share-only infra stack has neither a recovery unit nor an offbox toggle, so it fell through both engines. The toggle is now true.

Model B (Viktor's ruling) and its invariant. Share data enters the runs through a sibling shares source — additive job/leg code reusing the proven primitives while leaving every per-app engine path byte-identical. That invariant is the headline claim and is enforced by test in both tiers, each with a red-proof.

Part File Shape
1 payload internal/backup/shares_payload.go deterministic _shares-manifest.json + best-effort passdb.tar
2 tier-2 internal/backup/tier2_shares.go per-source-drive legs → backups/secondary/_shares/<driveKey>/<share>, _payload/, marker LAST
3 offsite internal/backup/offbox_shares.go ONE restic backup --tag felhom-offbox --tag _shares, after the app loop, before retention
4 restore internal/backup/shares_restore.go scratch → prefix-asserted missing-only merge → definitions (existing-wins) → ReconcileSamba → credential
5 liveness internal/monitor/healthcheck.go EffectiveProtected gains a settings-backed dynamic extra
6 UI sharing.html, backups_restore.html, handlers per-tier status lines; „Megosztások" restore entry

Seams extracted (never forked): selectTier2TargetFrom (source drive supplied explicitly; selectTier2Target is now a thin wrapper — the headroom math is untouched) and tier2ReconcileRoots (pure extraction; tier2Reconcile calls it with hdd/userdata).

Degradation contract: a quota-blocked offsite push falls back to the manifest only, never to nothing — definitions protection must not regress because the files stopped fitting.

3. Commits

Hash Subject
c81df55 Parts 12 — payload builder + tier-2 shares job
85b76e0 Part 3 — offsite shares leg + B isolation proof
900c870 Parts 46 — restore, samba liveness, UI truth-up
3b70a9e docs — CHANGELOG/CONTEXT/REUSE/README; caveats cleared
d0c1d77 fix — reserved key leaked into the crossdrive_completed hub event (found live)

felhom.eu: capability-map SMB row, backlog/ROADMAP.md R-7b, controller/sharing.md.

4. Findings

(a) The reserved-name assumption in the task was FALSE — guard added. The task asked me to verify at source that slice-1 validation excludes a leading underscore. It does not: settings.nbNameRe begins with [A-Za-z0-9_], so „_shares" was an accepted share name. ValidateSMBShareName now refuses the leading-underscore namespace (on ADD only, so existing shares are never retroactively invalidated). Stack names come from the git-synced catalog rather than customer input, so a _shares STACK is not realistically reachable — but RunAllTier2 and RunOffboxBackup skip one loudly as defense in depth rather than let it clobber the shares tree.

(b) Part 5 — the alert pipeline needed no further change. A missing protected container → report.IssuesStatus="fail"notifier.NotifyHealthChange → the existing health_critical event. No new event type is introduced, so the allowedEventTypes gotcha does not apply. Verified live: the hub accepted the event with HTTP 200.

(c) A real bug, surfaced by test. shareSourceDrive returned a slash-normalised path, which made the target selector's source-drive equality check miss — a share group could have targeted its own source drive, i.e. a same-disk copy pretending to be tier 2. POSIX-only in effect (ToSlash is identity there), but a genuine defect. Fixed.

(d) A real leak, surfaced by LIVE VALIDATION, not by any unit test. The first demo run pushed a hub event reading „Másodlagos mentés elkészült: _shares" — the reserved key reached Hungarian customer/operator copy through the tier2Notifycrossdrive_completed path I had not mapped. Fixed at the source of the notification (so no future notifier wiring can reintroduce it), with DisplayStackName at the main.go wiring as idempotent defense in depth, plus a regression test and a red-proof. Re-validated live: the event now reads „…: Megosztások". This is the clearest argument for the live leg — six green red-proofs did not catch it.

(e) Two warning-prose sites need no mapping. offbox_capture.go and tier2_capture.go embed a raw stack name in Hungarian prose, but the shares source is a sibling and never flows through the capture-set helpers, so _shares cannot reach them. Verified rather than assumed; no edit made.

(f) Pre-existing gate failure, untouched. scripts/docker_run_volume_path_gate.py fails on internal/appexport/estimate.go:179. It fails identically on the unmodified tree (verified by stashing), predates this work, and is out of scope. All six other gates pass.

5. Tests

go build ./... && go test ./...green (23 packages). 25 new/extended cases in internal/backup + 2 in internal/monitor.

Red-proofs — all six run, all fired, all reverted

# Break introduced Test that failed
1 shares leg appends its paths into the app's argv TestOffboxSharesLegLeavesAppCallsByteIdentical — "B INVARIANT VIOLATED"
2 mandatory→offsite mapping inverted TestOffboxSharesLegPushesMandatoryShare + …ExcludesOptionalShare
3 manifest-only degradation dropped (skip leg when blocked) TestOffboxSharesLegQuotaDegradesToManifestOnly
4 prefix-assert removed (liveShareRootOK → true) TestSharesRestoreRefusesDestinationOutsideLiveRoots — "PLACE GUARD BREACHED" ×2
5 dynamic samba extra removed TestEffectiveProtectedTracksSharingToggle (enabled case)
6 shares destBase drops the reserved segment TestSharesTier2LeavesPerAppTreeUntouched

Plus the post-hoc regression: passing the raw key to tier2Notify fails TestSharesTier2NotifierNeverLeaksReservedKey.

6. Deployment

gitea.dooplex.hu/admin/felhom-controller:0.145.0   Up (healthy)   guest 9201

Built on 180 (/mnt/5_hdd/felhom.eu/build/felhom-controller, explicit pull first), deployed via the bootstrap service. Deployed twice — the second time carrying the finding-(d) fix.

7. Live validation (demo, through the REAL server-side pipeline)

Method: logged in and POSTed /api/backup/tier2 with a real session + CSRF token — the exact endpoint the UI button invokes. No engine internals were called directly.

Box state: sharing ON, 2 shares (dokumentumok, filmek, both „Felhőmentés" ON) on /mnt/felhom-drives/hdd_1 (/dev/sdb); second schedulable drive scratch1 (/dev/sdd) — genuinely different physical disks.

Tier-2 leg — PASS.

[shares] payload staged: 2 share definition(s), credential copy=true
[shares] tier-2 copied 2 share(s) from /mnt/felhom-drives/hdd_1
         → /mnt/felhom-drives/scratch1/backups/secondary/_shares (8.1 KB)
_shares/.felhom-tier2-layout                       → "2"          (marker present, written last)
_shares/_payload/_shares-manifest.json             → -rw------- , both shares, correct paths/flags
_shares/_payload/passdb.tar                        → -rw------- , 855 040 B (real capture)
_shares/mnt_felhom-drives_hdd_1/dokumentumok/…     → r7.txt, r7b-live.txt
_shares/mnt_felhom-drives_hdd_1/filmek/

A file created after deploy (r7b-live.txt) was picked up on the next run and is md5-identical to the source (d6ce02292924d3dfb48cac0ede437225 both sides). Payload permissions survive the rsync mirror as 0600.

Display mapping — PASS (after the finding-(d) fix). Event pushed: crossdrive_completed (info) — Másodlagos mentés elkészült: Megosztások

Samba liveness (Scenario E, ON direction) — PASS. With sharing on, stopping felhom-samba:

health: {"issues":["Protected container not running: felhom-samba"],"status":"fail"}
11:29:55  Event pushed: health_critical (error) — Rendszer állapot kritikus (volt: ok)   hub_status 200

The next tick's EnsureBaseStack self-healed the container (Up 32 seconds), and an earlier cycle produced the matching health_recovered. A first attempt showed no degradation event; investigating rather than assuming showed scheduler.Every waits a full interval before its first run, so that tick hit the deliberate prev == "" first-observation guard — a test-timing artifact, not a defect. The clean re-run above is the real proof.

NOT live-exercised — stated explicitly

  • The offsite _shares restic leg and the „Megosztások" restore round-trip (Scenario D). The demo box has no offsite target configured (offbox_target empty — no Storage Box credentials, no escrow), and the run gate refuses offsite work until the repo password is escrowed. Configuring one is operator/Viktor territory ("first offsite run = Viktor"). Both are unit-covered and red-proofed (red-proofs 2, 3, 4), but neither has touched a real restic repo.
  • Scenario E's OFF direction was not live-toggled — disabling sharing on the demo box would down the stack and churn customer state. Unit-tested in both directions with red-proof 5.
  • Scenario F on a genuinely disconnected drive (unit-tested; the live shares are on healthy drives).

8. Handoff

  • Viktor: raise the managed-update floor to v0.145.0 (supersedes the earlier 0.144 note) so the N100 rehearsal's day-0 box converges onto the honest version.
  • Viktor: the offsite leg + restore round-trip need a live leg on a box with an escrowed offsite target. That is the one part of R-7b whose real-world behaviour rests on tests alone.
  • R-7 slice-2 remainder is unchanged (avahi/.local, app-folder presets, per-share users, recycle bin).