Compare commits
2 Commits
1f06029b70
...
f35b382c6d
| Author | SHA1 | Date | |
|---|---|---|---|
| f35b382c6d | |||
| 2b46619e15 |
@@ -0,0 +1,77 @@
|
|||||||
|
# AUDIT — felhom-controller deep sweep — 2026-06-13
|
||||||
|
|
||||||
|
**Branch:** `audit/2026-06-13-deep-sweep`
|
||||||
|
**Auditor:** Claude Code (unattended overnight session)
|
||||||
|
|
||||||
|
| Repo | HEAD commit | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| felhom-controller | `76a570da3284a742829cfeabe588255e3c74095f` | v0.51.0 (2026-06-12) — all findings cite this commit |
|
||||||
|
| felhom-agent (reference) | `716cbcd70500602f80a3e71869308a171396f1b6` | v0.28.0 |
|
||||||
|
| felhom.eu / hub (reference) | `d59691dd826a901da39562aeaa5d989b8ea1a7ee` | hub v0.11.0 |
|
||||||
|
|
||||||
|
**Tooling:** go1.26.0 windows/amd64; staticcheck (latest, installed this session); go vet.
|
||||||
|
**Prior art:** `BUGHUNT.md` (2026-02-25, v0.30.3) read in full — findings there are NOT re-reported unless regressed; note that many BUGHUNT items refer to packages deleted in slice 8C.
|
||||||
|
|
||||||
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-13 ~00:05 — Phase 0 start: repos pulled, branch created, baseline run.
|
||||||
|
- (in progress)
|
||||||
|
|
||||||
|
## Baseline (Phase 0)
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
|---|---|
|
||||||
|
| `go build ./...` | PASS |
|
||||||
|
| `go vet ./...` | PASS (clean) |
|
||||||
|
| `gofmt -l .` | ~75 files flagged — **all CRLF noise** from `core.autocrlf=true` on this Windows checkout; `gofmt -d` shows whitespace-only diffs. Not a code finding; see Info section (missing `.gitattributes`). |
|
||||||
|
| `go test ./...` | **1 pre-existing FAIL**: `TestBackupCopiesOnPath` (internal/web/storage_handlers_test.go:295) — see findings. |
|
||||||
|
| staticcheck | 17 reports — triaged in Phase 1. |
|
||||||
|
|
||||||
|
## Executive summary
|
||||||
|
|
||||||
|
(to be written in Phase 6)
|
||||||
|
|
||||||
|
## Top-10 action list
|
||||||
|
|
||||||
|
(to be written in Phase 6)
|
||||||
|
|
||||||
|
## Findings — Critical / High
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Findings — Medium / Low
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Findings — Info
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Contract checks (controller↔agent, controller↔hub)
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Invariant checklist results
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Refactor & shared-code opportunities
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Test-gap analysis
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Dead code inventory
|
||||||
|
|
||||||
|
(pending)
|
||||||
|
|
||||||
|
## Session notes, assumptions, open questions
|
||||||
|
|
||||||
|
- Session is unattended; conservative assumptions recorded inline.
|
||||||
|
- gofmt noise: repo is checked out with CRLF (`core.autocrlf=true`); `gofmt -l` flags nearly every file. Treated as environment artifact.
|
||||||
|
|
||||||
|
## What was NOT covered
|
||||||
|
|
||||||
|
(to be written honestly in Phase 6)
|
||||||
@@ -1,5 +1,27 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v0.52.0 — Phase 1 GATE: deploy-side double-nest fix + path-agreement lock (2026-06-13)
|
||||||
|
|
||||||
|
Completes the Model-A double-nest reconciliation deferred in v0.48.0. v0.51.0 fixed the **backup
|
||||||
|
helper** side (`NamespaceRoot` provenance); the **deploy/compose** side still wrote one segment too
|
||||||
|
deep. On a Model-A in-guest drive the guest mount `/mnt/<drive>` already IS the host's
|
||||||
|
`<drive>/felhom-data` namespace, so the catalog templates' `${HDD_PATH}/felhom-data/appdata/<app>`
|
||||||
|
double-nested to `.../felhom-data/felhom-data/...` on disk — diverging from where the backup helpers
|
||||||
|
look (`AppDataDir(NamespaceRoot(HDD_PATH,true))`, single-nested).
|
||||||
|
|
||||||
|
- **Fix lives in the app catalog** (`app-catalog-felhom.eu`): all four HDD app templates
|
||||||
|
(`romm`, `nextcloud`, `immich`, `paperless-ngx`) changed `${HDD_PATH}/felhom-data/appdata/<app>` →
|
||||||
|
`${HDD_PATH}/appdata/<app>`. The controller passes `HDD_PATH` through verbatim and never appended
|
||||||
|
the segment, so no controller runtime change was needed. Catalog change lands via git-sync /
|
||||||
|
"Sablonok frissítése".
|
||||||
|
- **Agreement test (new):** `internal/stacks/hddpath_agreement_test.go` resolves a compose's
|
||||||
|
`${HDD_PATH}` bind mounts via the real deploy-side `ParseComposeHDDMounts` and asserts they are
|
||||||
|
byte-identical to the backup-side `AppDataDir(NamespaceRoot(HDD_PATH,true))` — no doubled
|
||||||
|
`felhom-data`, deploy and backup locked together so they cannot drift again.
|
||||||
|
- **Live migration:** existing drive-resident apps whose data sat at the doubled
|
||||||
|
`…/felhom-data/felhom-data/appdata/<app>` are migrated (stop → move → verify → redeploy) to the
|
||||||
|
single-nested path (RomM confirmed on the demo guest).
|
||||||
|
|
||||||
### v0.51.0 — offsite-backup UI (felhom-pbs DR) + Model-A double-nest fix (2026-06-12)
|
### v0.51.0 — offsite-backup UI (felhom-pbs DR) + Model-A double-nest fix (2026-06-12)
|
||||||
|
|
||||||
Pairs with felhom-agent v0.28.0 (whole-guest backup re-targeted to the offsite PBS tier).
|
Pairs with felhom-agent v0.28.0 (whole-guest backup re-targeted to the offsite PBS tier).
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package stacks_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||||
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDeployPathAgreesWithBackupHelpers is the Phase-1 GATE invariant: the host path an app actually
|
||||||
|
// writes to — resolved from its compose ${HDD_PATH} bind mounts, exactly as deploy does — must be
|
||||||
|
// byte-identical to where the app-data backup helpers look. Single-nested, no doubled felhom-data.
|
||||||
|
//
|
||||||
|
// Before the fix, catalog templates used ${HDD_PATH}/felhom-data/appdata/<app>. On a Model-A in-guest
|
||||||
|
// drive the guest mount /mnt/<drive> ALREADY is the host's <drive>/felhom-data namespace, so that extra
|
||||||
|
// segment produced .../felhom-data/felhom-data/... on disk, while NamespaceRoot(drive, true) resolved
|
||||||
|
// the single-nested .../appdata/<app>. Deploy and backup disagreed. The catalog templates now use
|
||||||
|
// ${HDD_PATH}/appdata/<app>; this test locks the two sides together so they can't drift again.
|
||||||
|
func TestDeployPathAgreesWithBackupHelpers(t *testing.T) {
|
||||||
|
const hddPath = "/mnt/felhom-usb" // an enrolled in-guest drive mount (basename is NOT felhom-data)
|
||||||
|
const app = "romm"
|
||||||
|
|
||||||
|
// A compose volumes block in the FIXED form (no felhom-data segment), mirroring the catalog template.
|
||||||
|
compose := "services:\n" +
|
||||||
|
" romm:\n" +
|
||||||
|
" volumes:\n" +
|
||||||
|
" - ${HDD_PATH}/appdata/romm/library:/romm/library\n" +
|
||||||
|
" - ${HDD_PATH}/appdata/romm/resources:/romm/resources\n" +
|
||||||
|
" - romm_config:/romm/config\n" +
|
||||||
|
"volumes:\n" +
|
||||||
|
" romm_config:\n"
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
composePath := filepath.Join(dir, "docker-compose.yml")
|
||||||
|
if err := os.WriteFile(composePath, []byte(compose), 0600); err != nil {
|
||||||
|
t.Fatalf("writing temp compose: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy side: where the app's bytes actually land (named volume romm_config is correctly excluded).
|
||||||
|
mounts := stacks.ParseComposeHDDMounts(composePath, hddPath)
|
||||||
|
if len(mounts) != 2 {
|
||||||
|
t.Fatalf("expected 2 HDD bind mounts, got %d: %v", len(mounts), mounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup side: the app-data dir the helpers resolve for the same in-guest drive.
|
||||||
|
ns := appbackup.NamespaceRoot(hddPath, true) // in-guest Model-A drive → mount as-is, no felhom-data
|
||||||
|
wantAppDir := filepath.ToSlash(appbackup.AppDataDir(ns, app))
|
||||||
|
if wantAppDir != "/mnt/felhom-usb/appdata/romm" {
|
||||||
|
t.Fatalf("backup app-data dir unexpected: %q", wantAppDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range mounts {
|
||||||
|
s := filepath.ToSlash(m)
|
||||||
|
// (1) No doubled felhom-data, and in guest-path space no felhom-data segment at all.
|
||||||
|
if strings.Contains(s, appbackup.FelhomDataDir+"/"+appbackup.FelhomDataDir) {
|
||||||
|
t.Errorf("deploy mount double-nests felhom-data: %q", s)
|
||||||
|
}
|
||||||
|
if n := strings.Count(s, appbackup.FelhomDataDir); n > 0 {
|
||||||
|
t.Errorf("deploy mount unexpectedly carries %d felhom-data segment(s): %q", n, s)
|
||||||
|
}
|
||||||
|
// (2) Every app bind mount lives under the backup helpers' app-data dir — deploy and backup agree.
|
||||||
|
if !strings.HasPrefix(s, wantAppDir+"/") {
|
||||||
|
t.Errorf("deploy mount %q is not under backup app-data dir %q — the two sides diverge", s, wantAppDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user