diff --git a/CONTEXT.md b/CONTEXT.md index 350bcde..d3e04dd 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,25 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-07-22 (v0.159.0 — R-66: the box's own address becomes visible) +Last updated: 2026-07-22 (v0.160.0 — R-67: the NAS share appears in FileBrowser) + +> **2026-07-22 — v0.160.0 (R-67), SHIPPED + deployed BOTH boxes, full live leg on demo-hp.** +> Network shares now bind their share ROOT into FileBrowser (`…/:/srv/:rslave`) — no +> skeleton/userdata toward the NAS, ever. Pure assembly = `buildFileBrowserPaths` + `fbPathDeps` +> (handlers.go), returning mounts AND config sources together so they can't disagree. +> +> **DECISION — two classes, two gates:** drives keep the drive-absent gate (byte-identical, +> tested + observed live: demo-felhom logged a no-op sync); network shares use the STUB classifier +> gate instead (stub ⇒ excluded from both lists + WARN — an exposed stub swallows uploads the real +> mount later shadows; idle autofs is HEALTHY and included; unknown fails open). Never force-wake +> in the sync (doctrine). +> +> **Phase-0 probe = GO:** in-container access through an rslave bind WAKES an idle autofs trigger +> (proved on demo-hp against the real Felhom-Share). Live leg: upload from demo-hp's filebrowser +> container (uid 1000) landed on demo-felhom's share dir and deleted clean; dead-NAS gave +> `Host is down` in seconds (no hang) and recovered unaided after samba restart. RESIDUAL for the +> operator: the FileBrowser HTTP click-through — its admin credential is customer-held (CC got 401 +> on admin/admin and the demo password; by design). ROADMAP R-67 SHIPPED (coupled to R-64). > **2026-07-22 — v0.159.0 (R-66), SHIPPED + deployed to BOTH boxes.** Three legs: „Hálózat" card on > Beállítások → Rendszer (Helyi cím / Hálózati név only-while-Megosztás / Átjáró; „—" fallback), diff --git a/REPORT.md b/REPORT.md index 6346eaf..ca5b157 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,98 +1,97 @@ -# REPORT — R-66: the box's own address becomes visible (controller v0.159.0, 2026-07-22) +# REPORT — R-67: the NAS share appears in FileBrowser (controller v0.160.0, 2026-07-22) -**Spec:** the R-66 prompt (three XS legs, controller-only, v0.158.1 → v0.159.0). -**Commit:** `7013a5f` on `main` · image `felhom-controller:0.159.0` -(sha256 `5bbc7d62433c464fbbcdbe8ef819636b94075d0cc481631d50471880d99630c5`) · deployed to -**demo-felhom 9201** and **demo-hp 9201** (both now above the 0.156.0 floor; floor untouched). +**Spec:** the R-67 prompt (Phase-0 probe + implementation, controller-only, v0.159.0 → v0.160.0). +**Commit:** `59cd260` on `main` · image `felhom-controller:0.160.0` · deployed to **demo-felhom +9201** and **demo-hp 9201**, both healthy. No agent changes; MinAgent unchanged. -## 1. The one design decision (spec deviation, recorded) +## 1. Phase-0 probe — GO -The spec sketched Leg A's Átjáró as a `/proc/net/route` read and Leg B's DNS as `/etc/resolv.conf`. -**The controller container is bridge-networked** (verified live: `docker inspect` → -`NetworkMode=bridge`; the bootstrap `docker run` has no `--network host`) — so every in-process -answer describes the CONTAINER's netns: routes → the docker bridge gateway (172.x), resolv.conf → -docker's 127.0.0.11 embedded resolver, interfaces → one 172.x eth0. That is exactly the -wrong-kind-of-true trap the codebase already documents on `SambaLANAddress` (S-2: "the classic -wrong answer that already burned the setup wizard"). - -**Resolution:** every guest-net read goes through the ONE guest-netns door this process has — a -docker-exec into the host-networked `felhom-samba` container (`internal/stacks/guestnet.go`, one -`guestNetExecFn` seam for route/link/addr/resolv.conf reads). Accepted consequence, by the spec's -own S-5 logic ("an address-less row beats a wrong address"): with Megosztás off the door is closed -and the card shows „—" / the dump carries per-item error strings. The spec's §2.1 instruction -("check whether it is SMB-conditional… reuse as-is") anticipated exactly this shape. +On demo-hp, with the Felhom-Share automount confirmed IDLE (autofs trigger present in the guest's +/proc/mounts, `timeout=60`, no cifs mount): +`docker run --rm -v /mnt/felhom-drives/Felhom-Share:/probe:rslave alpine ls -la /probe` +listed the REAL share content (demo-felhom's `teszt/` + macOS dotfiles) and left cifs mounted in +the guest. **An in-container access through an rslave bind wakes the idle trigger** — one +namespace further than the spike's in-guest proof. The design shipped exactly as specified; the +fallback fork was not needed. ## 2. What shipped -- **Leg A** — „Hálózat" card on Beállítások → Rendszer, between „Verzió és frissítés" and „Szerver - memória" (`settings_system.html`, `systemPageData`): Helyi cím (LAN) via the reused - `sambaLANAddress`; Hálózati név `\\` gated on `smb.Enabled` (the NetBIOS name - exists only while samba runs); Átjáró via the new `guestGateway`; muted footer („…Távoli - hibaelhárításnál ezt az oldalt kérjük felolvasni."). No host/Proxmox address, no interface dump. -- **Leg B** — `dump["network"]` in `debugDump`: `interfaces` (name/up/addresses; veth*/docker*/br-* - plumbing skipped), `default_route` (gateway + source interface), `dns_servers` (guest - resolv.conf — docker copies the HOST's file into a `network_mode: host` container, so the read - answers for the box), `lan_address` (the SAME live value Leg A shows, the cross-check anchor). - Best-effort per item: a failed read yields `"error: …"` in place, dump never aborts. -- **Leg C** — helper text under the Szerver field (`storage_network.html`, naming the »közvetlen - cím« row on the Megosztás page), plus `netAddMessage`'s `unreachable` case appends „Tipp: a(z) - »« Windows-hálózati névnek tűnik — használja az eszköz IP-címét." when - `looksLikeFlatNetworkName(server)` (non-empty ∧ no dot ∧ not `net.ParseIP`-able). Purely - lexical; no NetBIOS/mDNS anywhere; agent untouched. -- Nothing is stored: no settings.json/app.yaml/hub-report field carries any of these values. +`syncFileBrowserMounts`' path loop extracted into the pure **`buildFileBrowserPaths`** +(`fbPathDeps`: mount probe / FS classifier / skeleton fn / logger — every edge seamed), returning +BOTH the mount lines and the config source set so a source can never render without its mount. + +- **Network branch:** bind = the share ROOT, `…/:/srv/:rslave` (`:rslave` + load-bearing — automount wake / idle-unmount propagate into the RUNNING container). No + `EnsureUserdataSkeleton`, no userdata scoping, nothing written toward the NAS. The drive-absent + gate does NOT apply (idle is healthy — the old gate skipped an idle share forever); the gate is + the **stub** classifier verdict via the existing `Server.classifyFSPath` seam: stub ⇒ excluded + from mounts AND sources this pass + WARN (`namespace sees a local stub, not the NAS`). autofs / + network / unknown / nil-classifier include (fail open). **The stub rationale:** an exposed local + stub dir accepts uploads that the real mount later SHADOWS — the customer's files silently + vanish from view; exclusion-until-recovery is the only safe rendering. +- **Drives:** behavior byte-identical (userdata scoping, skeleton, drive-absent gate, always in + the source list) — asserted by test, and observed live (§4). +- **Triggers:** NAS add-success (`runNetAdd` done) + remove (`handleNetStorageRemove`) now call + `SyncFileBrowserMounts()` (same debounced/H5-mutexed path as the drive flows). Removal drops + source + mount next sync; F2 change detection forces the recreate. +- Renderers unchanged: `RenderFileBrowserCompose` already takes fully-formed mount lines + (propagation rides in the line), `RenderFileBrowserConfig` already names sources by `Label`. ## 3. Tests + red-proofs -`stacks/guestnet_test.go`: pinned pure parsers (default route incl. garbage/IPv6-gw refusals; -interface merge with docker-plumbing filtering; resolv.conf), `GuestGateway` fail-quiet contract, -and the B1 best-effort matrix (scripted per-argv exec fake: healthy / resolv.conf-only failure / -door fully closed). `web/network_card_test.go`: A1 (all rows render), A2 (Hálózati név ABSENT when -sharing off), A3 („—" + „nem állapítható meg", no crash), per-render freshness counter (2 renders ⇒ -2 resolves — the anti-memoization gate), B1 web-half (dump shape + in-place error + dump -completeness), C1/C2/C3 + category containment + IPv6/empty lexical edges. +`filebrowser_network_test.go`: **A** (drive line byte-identical with/without the share — the B′ +invariant; share ROOT `:rslave` line exact; both sources in config; skeleton called for the drive +ONLY), **B** (stub ⇒ absent from mounts + sources, drive intact, warn logged), **C** (autofs / +network / unknown / nil classifier all include — idle is not a detached drive), **D** (removal +leaves no trace; `fbNeedsRecreate` sees the change). -**Red-proof A2** (enabled-gate dropped in `systemPageData`): FAIL — -`Hálózati név row rendered while Megosztás is disabled — a wrong promise`. Restored, green. -**Red-proof C2** (lexical check inverted): FAIL — the hint visibly nagged the IP user: -`hint wrongly present for an IP: "… Tipp: a(z) »192.168.0.50« Windows-hálózati névnek tűnik …"` -(C1/C3 + edge assertions also went red). Restored, green. +**Red-proof A** (network paths routed through the drive branch): FAIL — +`skeleton calls = [/mnt/felhom-drives/hdd_1 /mnt/felhom-drives/Felhom-Share]` — the NAS path +visibly receiving a skeleton — plus the share line degrading to a `userdata` subtree without +`:rslave`. Restored, green. +**Red-proof B** (stub gate dropped): FAIL — the stub share leaked into mounts AND the source +list, and the warn disappeared. Restored, green. -Full green gate: `go build ./... && go vet ./... && go test ./...` — clean. All six template gates -(template_id / emoji / native_confirm / offbox_rename / app_row_dedup / mojibake) — OK. +Green gate (`go build ./... && go vet ./... && go test ./...`): clean. No template changes → no +template gates in play. -## 4. Live validation (endpoint-level — the exact endpoints the UI invokes; no browser on DooPlex) +## 4. Live leg (real surfaces; methods named) -**demo-felhom 9201 (sharing ON — the open-door path).** Deploy via the bootstrap flow; `docker ps` -→ `0.159.0 Up (healthy)`; startup log clean (`controller_started (0.159.0)`, hub report pushed). -Authed session against container-IP:8080 + Host header (credentials sourced, never echoed): -- `GET /settings` — the card renders with **real live values**: `192.168.0.104` (the guest's - actual DHCP lease), gateway `192.168.0.1`, `\\FELHOM`, footer present. ASCII-safe greps per the - accent-mangling trap. -- `GET /api/debug/dump` → `network` = all four sub-keys: - `lan_address 192.168.0.104` · `default_route {gateway 192.168.0.1, interface eth0}` · - `dns_servers [192.168.0.1]` · `interfaces [lo up 127.0.0.1/8, eth0 up 192.168.0.104/24]` — - docker plumbing correctly absent. -- `GET /storage/network` — the Szerver helper text present. +**demo-hp (the mounting side):** +- Startup sync recreated FileBrowser: compose carries + `/mnt/felhom-drives/Felhom-Share:/srv/Felhom-Share:rslave`, config carries + `path: "/srv/Felhom-Share"` named „Hálózati tárhely: Felhom-Share"; log: + `FileBrowser mounts synced (recreated) — 2 storage path(s)`; container healthy. +- **Content:** `docker exec filebrowser ls /srv/Felhom-Share` → demo-felhom's real content + (`teszt/` + dotfiles), uid/gid 1000, group-writable. +- **Upload round-trip:** wrote `r67-test.txt` as **uid 1000 inside the filebrowser container** + (the exact mount namespace + process identity FileBrowser uses) → the file appeared on + demo-felhom at `/mnt/felhom-drives/hdd_1/shares/Share/r67-test.txt` (19 B, uid 1000) → deleted + from demo-hp → gone on demo-felhom. **Residual:** FileBrowser's HTTP/auth layer was NOT driven — + its admin credential is customer/day-0-held (admin/admin and the operator demo password both + 401); the browser click-through is the operator's residual, as is a look at the sidebar. +- **Dead-NAS:** samba stack stopped on demo-felhom → demo-hp's in-container access returned a + clean `ls: /srv/Felhom-Share: Host is down` in ~11 s wall (incl. ssh) — an error, not a hang + (the soft/retry=0 recipe's promise held). `docker compose start` on samba → demo-hp listed the + share again immediately, **zero intervention** (the `:rslave` + automount recovery working as + designed). -**demo-hp 9201 (sharing OFF — the closed-door path, live).** Access via the hub-vaulted G1 -break-glass (file→file, env-var use, credential + hub-DB copy **shredded after**; demo-hp remains -key-less). 0.156.0 → 0.159.0, `docker ps` → healthy. Same authed method (customer domain -`felhom.enkisfelhom.hu` — direct container-IP probing 404s without the right Host, worth knowing): -the card renders with „—" rows (×2 „nem állapítható meg"), **no** `\\FELHOM` row — Leg A's A2/A3 -behaviors observed in production; the dump's `network` section carries all four keys with in-place -`error: docker exec …` strings for interfaces/route/dns and an empty `lan_address`. The dump -stayed complete. +**demo-felhom (drives-only, the regression side):** startup sync logged +`no config/compose change, ensured running without recreate (1 storage path(s))` — the render was +byte-identical, FileBrowser untouched (Up 22 hours), zero `:rslave` lines in its compose. The B′ +invariant held in production. -**Screenshots by description** (rendering is the only untested residual): the card sits between the -version card and the RAM card; three `settings-row` lines — „Helyi cím (LAN)" with a mono address -(or „— nem állapítható meg" muted in `--text-3`), „Hálózati név" with mono `\\FELHOM` (only while -sharing is on), „Átjáró" with the mono gateway — and a muted one-line footer under the grid. On -demo-hp the same card shows two dash-rows and no name row. +**Screenshots by description:** demo-hp's FileBrowser sidebar now has a second entry „Hálózati +tárhely: Felhom-Share" beside the NVMe drive source; entering it shows the `teszt` folder from +demo-felhom. During the dead-NAS minute the source errors instead of spinning. + +**Access notes:** demo-hp via the hub-vaulted G1 break-glass again (file→file, shredded after, +incl. the hub-DB copy). Probe + all execs left nothing behind (`r67-test.txt` deleted; alpine +image remains in the guest's docker cache — inert). ## 5. Follow-ups -- **R-64** (Felhom↔Felhom pairing blessed — evidence drill pending) and **R-65** (buddy-box - replication, post-alpha spike-first) minted in `felhom.eu` ROADMAP; **R-66 marked SHIPPED** there. -- `documentation/controller/network-storage-nas.md` gained the NetBIOS naming-caveat paragraph. -- Operator note: a strict end-to-end UI pass of the card is a manual click-through (no browser on - DooPlex). +- Operator: FileBrowser click-through on demo-hp (sidebar + upload via the UI) — CC lacks the + FileBrowser admin credential by design. +- ROADMAP: **R-67 → SHIPPED**, coupled to R-64 (browsing was its missing UX half); + `network-storage-nas.md` gained the „Browsing (FileBrowser)" paragraph.