From 0c4886958e0fe55c7a72c6b1ec3a13d66e420a64 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 20 Jul 2026 10:11:58 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20DIAG=20=E2=80=94=20Megoszt=C3=A1s=20rel?= =?UTF-8?q?oad=20loop=20root-caused=20(b5d78d1=20phase=20coercion)=20+=20S?= =?UTF-8?q?MB=20endpoint=20is=20the=20guest,=20not=20.162?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Nhk3eBHT8Mg5L8c2aj57aU --- REPORT-diag-sharing-2026-07-20.md | 53 ++++ .../audits/DIAG-sharing-2026-07-20.md | 267 ++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 REPORT-diag-sharing-2026-07-20.md create mode 100644 documentation/audits/DIAG-sharing-2026-07-20.md diff --git a/REPORT-diag-sharing-2026-07-20.md b/REPORT-diag-sharing-2026-07-20.md new file mode 100644 index 0000000..5e575a3 --- /dev/null +++ b/REPORT-diag-sharing-2026-07-20.md @@ -0,0 +1,53 @@ +# REPORT — Megosztás diagnosis (2026-07-20) + +Topic-scoped report (parallel-session rule: shared `REPORT.md` untouched). + +**Run:** RUNBOOK "Megosztás diagnosis — SMB unreachable from Mac + sharing-page reload loop". +Read-only diagnosis. **No code changes, no version bumps, no builds, no restarts.** + +**Deliverable:** `documentation/audits/DIAG-sharing-2026-07-20.md`. + +## Verdicts + +* **Reload loop — ROOT-CAUSED (HIGH).** `sharingStatusHandler` (`sharing_handlers.go:246`, added in + `b5d78d1`, controller v0.147.0, 2026-07-19) coerces `idle` → `running` whenever the samba + container is alive. `sharing.html` L320–326 treats `running` as a one-shot job-success edge and + calls `location.reload()` 1.2 s later. The first `tick()` fires synchronously on every page load, + so the page reloads forever. Unconditional for any customer with sharing enabled — the Megosztás + page is currently unusable. Proven live: 6 consecutive `/sharing/status` polls all returned + `{"phase":"running","running":true}`, and the controller log shows **no ensure job ran at all**, + so the phase is manufactured by that line rather than left over from a stuck job. +* **`smb://192.168.0.162` — ROOT-CAUSED.** `.162` is the Proxmox host and never was an SMB endpoint. + smbd runs in guest 9201 and binds `192.168.0.104:445`. `nc` from the host: `.104:445` **open**, + `.162:445` **refused**. Wrong target, stale Finder favourite. +* **`smb://FELHOM` — OPEN, narrowed.** NetBIOS resolution works on the wire + (`nmblookup -B 192.168.0.255 FELHOM` → `192.168.0.104 FELHOM<00>`), but the stack advertises **no + mDNS/Bonjour** (nothing on udp/5353; the R-6 spike selected `smbd + nmbd + wsdd` only) — the + mechanism macOS Finder prefers. Closing this needs one probe from the Mac (`smbutil lookup FELHOM` + / `dns-sd -B _smb._tcp`), listed in the audit's Mac test matrix. +* **H2 (container down/crash-looping) — RULED OUT** at the first probe: `felhom-samba` Up 3 h, clean + logs, smbd/nmbd/wsdd all bound as `infra/samba.go` intends, live `smb.conf` matches the renderer + with no baked address literal, no PVE firewall in the path. + +## Findings (full table + evidence in the audit doc) + +S-1 HIGH reload loop (XS fix: latch a `sawInFlight` flag in the JS; red-proof required) · +S-2 MED the UI never shows the connect address, only `\\FELHOM` — customers guess IPs · +S-3 MED no mDNS advertisement (image slice, needs republish) · +S-4 LOW ensure-job phase never resets — fold into the async-job-feedback roadmap item · +S-5 INFO the guest's LAN IP is DHCP, so any displayed address must be read live, never cached. + +Recommended packaging: S-1 as an immediate patch task (it bricks a shipped page), S-2 alongside it +if the guest IP is reachable from the sharing handler, S-3 as its own slice. + +## Also noticed + +The **remote site's LAN is `192.168.0.0/24` — the same prefix as the DooPlex home LAN** that the +Tailscale subnet router advertises (`192.168.0.180` shows `FAILED` in felhom-pve's neighbour table). +A successful `ping 192.168.0.162` therefore does not by itself prove the Mac is on the remote +segment; the Mac matrix starts by confirming which network it is on. + +## Actions taken + +None. Every command was a read, except a `POST /login` to obtain a session for the status polls. +No secrets are recorded in either document. diff --git a/documentation/audits/DIAG-sharing-2026-07-20.md b/documentation/audits/DIAG-sharing-2026-07-20.md new file mode 100644 index 0000000..51043b2 --- /dev/null +++ b/documentation/audits/DIAG-sharing-2026-07-20.md @@ -0,0 +1,267 @@ +# DIAG — Megosztás: SMB unreachable from Mac + `/sharing` reload loop (2026-07-20) + +**Class:** diagnosis run (RUNBOOK). Read-only. **No code changes, no version bumps, no builds, no +sanctioned restart was needed** (H2 was ruled out at the first probe — the container was already up). + +**Baselines at run time** +| Repo | `main` @ | Deployed | +|---|---|---| +| felhom-controller | `8db9232` | `gitea.dooplex.hu/admin/felhom-controller:0.150.0` on 9201 — Up 23 min (healthy) | +| felhom-samba | — | `gitea.dooplex.hu/admin/felhom-samba:1.0.0` — Up 3 hours | + +Site: felhom-pve at the remote location, `vmbr0` static `192.168.0.162/24`, gw `192.168.0.1`. + +--- + +## Verdicts + +| Symptom | Verdict | Root cause | +|---|---|---| +| 1. `smb://192.168.0.162` fails from the Mac | **ROOT-CAUSED** | `.162` is the **Proxmox host**, never an SMB endpoint. smbd lives in guest 9201 and binds `192.168.0.104:445`. `.162:445` = connection refused, by design. | +| 1b. `smb://FELHOM` also fails | **OPEN — narrowed** | NetBIOS name resolution *works on the wire* (`nmblookup` → `FELHOM<00> = 192.168.0.104`), but the stack advertises **no mDNS/Bonjour** (no avahi, nothing on udp/5353) — the mechanism macOS Finder actually prefers. Cannot be closed without one test from the Mac (§ Mac matrix). | +| 2. `/sharing` full-page reloads ~1× per second | **ROOT-CAUSED** | `GET /sharing/status` returns `phase:"running"` on *every* load (liveness coercion added in `b5d78d1`, v0.147.0, 2026-07-19); the page's JS treats `running` as "the job just finished" and fires `location.reload()` 1.2 s later. Unconditional loop whenever sharing is up. | + +**Hypothesis outcome:** **H1 confirmed, in a stronger form than stated** (not a *sticky in-memory +job* — there was no job at all this process; the handler *manufactures* the terminal phase). +**H2 ruled out.** **H3 confirmed for the `.162` half of symptom 1**; the `\\FELHOM` half is a new, +separate defect (no Bonjour advertisement) and stays OPEN pending the Mac-side probe. + +--- + +## Phase A — Live state snapshot (guest 9201) + +``` +$ docker ps -a +felhom-samba Up 3 hours gitea.dooplex.hu/admin/felhom-samba:1.0.0 +felhom-controller Up 23 minutes (healthy) …/felhom-controller:0.150.0 +``` +No `Exited`, no `Restarting`, no restart churn → **H2 dead on arrival.** + +``` +$ docker logs felhom-samba --tail 80 +[felhom-samba] launching nmbd + wsdd + smbd (server=FELHOM iface=eth0 uid=1000) +[felhom-samba] launching nmbd + wsdd + smbd (server=FELHOM iface=eth0 uid=1000) +``` +(Two lines = the two lifetimes since creation. Clean, no errors.) + +``` +$ ip -4 addr +2: eth0@if7 … inet 192.168.0.104/24 brd 192.168.0.255 scope global dynamic eth0 +``` +**The guest's LAN IP is `192.168.0.104`** (DHCP lease, ~54 min remaining at probe time). + +``` +$ ss -tlnp | grep ':445' +LISTEN 192.168.0.104:445 users:(("smbd",pid=2137,fd=28)) +LISTEN 127.0.0.1:445 users:(("smbd",pid=2137,fd=29)) +LISTEN [::1]:445 users:(("smbd",pid=2137,fd=30)) +$ ss -ulnp | grep -E ':137|:138' +UNCONN 192.168.0.255:137 / 192.168.0.104:137 / 0.0.0.0:137 nmbd pid=2146 +UNCONN 192.168.0.255:138 / 192.168.0.104:138 / 0.0.0.0:138 nmbd pid=2146 +$ ss -tlnp | grep ':5357' +LISTEN 192.168.0.104:5357 users:(("python3",pid=2147,fd=10)) # wsdd +``` +All three daemons up and bound exactly as `infra/samba.go` intends. **Nothing on udp/5353** — no +mDNS responder anywhere in the guest. + +Reachability from the Proxmox host (same L2 segment as the Mac): +``` +$ nc -zv -w3 192.168.0.104 445 → (UNKNOWN) [192.168.0.104] 445 (microsoft-ds) open rc=0 +$ nc -zv -w3 192.168.0.162 445 → demo-felhom.local [192.168.0.162] 445 : Connection refused rc=1 +``` +**SMB is reachable on the LAN at `192.168.0.104:445`.** `.162:445` refused — documents that the +host IP was never the SMB endpoint. + +No packet filter in the path: `/etc/pve/firewall/cluster.fw` and `/etc/pve/firewall/9201.fw` do not +exist; `pct config 9201` shows `net0: name=eth0,bridge=vmbr0,ip=dhcp` with no firewall flag. + +Service-level proof from inside the container: +``` +$ docker exec felhom-samba smbclient -L 127.0.0.1 -N +Anonymous login successful +$ docker exec felhom-samba nmblookup -B 192.168.0.255 FELHOM +querying FELHOM on 192.168.0.255 +192.168.0.104 FELHOM<00> rc=0 +``` +**NetBIOS name resolution answers correctly on the LAN broadcast.** + +--- + +## Phase B — The reload loop + +### The decisive code + +`controller/internal/web/sharing_handlers.go` — `sharingStatusHandler` (blame `b5d78d1`, +"v0.147.0 — feedback slice 1", 2026-07-19): + +```go +phase := sambaPhaseIdle +if job := s.sambaEnsure.snapshot(); job != nil { phase, errMsg = job.Phase, job.Error } +running := s.stackMgr != nil && s.stackMgr.SambaRunning() +// A stale `idle`/`running` job must never contradict reality: liveness wins on a fresh page. +if phase == sambaPhaseIdle && running { + phase = sambaPhaseRunning // ← line 246 +} +``` + +`controller/internal/web/templates/sharing.html` L313–335, the 1.5 s tick: + +```js +if (ph === 'running') { + stop(); + show('alert-success', 'A megosztási szolgáltatás fut.'); + /* Repaint the „Állapot" badge, which was rendered server-side as „áll". */ + setTimeout(function(){ location.reload(); }, 1200); + return; +} +… +tick(); // ← fires IMMEDIATELY on load, before the interval +timer = setInterval(tick, 1500); +``` + +**The defect.** The two halves were written for different worlds and contradict each other: + +* The JS reads `running` as an **edge** — "the ensure job I was watching has just succeeded, so the + server-rendered badge is stale; reload once to repaint it." +* Since `b5d78d1` the handler reports `running` as a **level** — "the container is alive", true on + every page load forever after, with no job in sight. + +`tick()` is called synchronously at script end, so the very first poll of every fresh page load +returns `running` → reload scheduled at +1200 ms → new page → new `tick()` → reload. Period ≈ +1.2 s + load time, matching Viktor's "roughly every second". The `stop()` is irrelevant: the timer +dies, but the page it lived on is destroyed 1.2 s later anyway. + +It is **unconditional** — any customer with sharing enabled and the container up cannot use the +Megosztás page at all. `setTimeout` also survives `stop()`, so there is no in-page escape. + +### The live signal (6 polls, 2 s apart, authenticated) + +``` +{"data":{"error":"","phase":"running","running":true},"ok":true} ×6, identical +``` +Constant terminal phase, never `idle`, never flapping ⇒ **H1**, and the constancy is structural +rather than a leftover job. + +### Onset correlation + +``` +$ docker logs felhom-controller --since 3h | grep -iE 'samba|sharing|megoszt' +(no output) +``` +**No ensure job ran in this controller process at all** — `s.sambaEnsure.snapshot()` is `nil`, so +`phase` starts at `idle` and the reported `running` comes *purely* from line 246. This rules out +"today's deploy restarts kicked a job that got stuck": there is nothing stuck; the loop needs only a +running container and a page load. + +Onset is therefore **v0.147.0 (2026-07-19)**, the commit that added line 246 — one day before the +report, consistent with "didn't do this before". Today's 0.148→0.149→0.150 deploys are innocent. + +--- + +## Phase C — SMB service correctness + +Live `smb.conf` inside the container matches `RenderSambaConfig` byte-for-byte in the global block: + +``` +[global] + workgroup = WORKGROUP + netbios name = FELHOM + security = user + map to guest = never + server min protocol = SMB2 + disable netbios = no + bind interfaces only = yes + interfaces = lo eth0 + smb ports = 445 + … +[Share] + path = /mnt/felhom-drives/hdd_1/shares/Share + read only = no +``` + +* **No baked address literal.** `interfaces = lo eth0` is *interface*-scoped, not IP-scoped — it + follows the DHCP lease automatically. The `.162` mismatch is not config drift; `.162` is simply a + different machine. +* One share is exported: `Share`. +* Discovery daemons: nmbd ✅ (answers NBNS, § Phase A), wsdd ✅ (tcp/5357 bound — the Windows path), + **avahi/mDNS ❌ — absent by design of the R-6 spike verdict, which selected `smbd + nmbd + wsdd`.** + +--- + +## The subnet-collision hazard (noticed while mapping the LAN) + +``` +$ ip route (felhom-pve) +default via 192.168.0.1 dev vmbr0 +192.168.0.0/24 dev vmbr0 proto kernel scope link src 192.168.0.162 +$ ip neigh show dev vmbr0 +192.168.0.1 REACHABLE 192.168.0.104 REACHABLE (the guest) +192.168.0.230 STALE 192.168.0.11 STALE +192.168.0.180 FAILED ← DooPlex is NOT on this segment +``` + +**The remote site's LAN is also `192.168.0.0/24`** — the same prefix as the DooPlex home LAN, which +the Tailscale subnet router advertises. Consequences to keep in mind while the box is away: + +* A successful `ping 192.168.0.162` **does not prove** the Mac is on the remote segment — if the Mac + were at home, the same address could resolve to a home device or route over the tailnet. +* Any 192.168.0.x address is ambiguous across the two sites for the duration of the trip. + +This does not change the verdicts above, but it is the reason the Mac-side probe in the next section +starts with "which network am I actually on". + +--- + +## Findings + +| # | Sev | Finding | Evidence | Proposed fix | GO needed? | +|---|---|---|---|---|---| +| **S-1** | **HIGH** | `/sharing` reload-loops forever for every customer with sharing enabled — the page is unusable. `sharingStatusHandler` reports `running` as a level; the JS treats it as a one-shot success edge and calls `location.reload()`. | `sharing_handlers.go:246` (blame `b5d78d1`), `sharing.html` L320–326, 6× identical `phase:"running"` poll | **XS.** Make the JS reload only when it has *observed a transition* — latch a `sawInFlight` flag on the first tick and reload on `running` only if it is set; otherwise just paint the success alert and stop. Server-side alternative (also XS): report the coerced level as a distinct phase (`running_steady`) the JS renders but never reloads on. Prefer the JS latch — it keeps the honest "liveness wins" contract the handler comment states. **Needs a red-proof:** a template/JS test or handler test asserting a fresh page load with a live container does not schedule a reload. | no — plain bug fix | +| **S-2** | **MED** | The UI never tells the customer *where* to connect. `sharing.html` L32/L61 show only `\\FELHOM`; there is no IP anywhere on the page, so a customer who cannot resolve the name has nothing to fall back on and will guess (Viktor guessed the Proxmox host IP). | `sharing.html` L30–32, L61; symptom 1 itself | **S.** Render the guest's current LAN address beside the name: `\\FELHOM` *(vagy `smb://192.168.0.104`)*, sourced from the same place the dashboard already knows the guest IP. Add a one-line macOS hint (Finder → Ugrás → Csatlakozás a szerverhez). | no | +| **S-3** | **MED** | No mDNS/Bonjour advertisement. `smbd + nmbd + wsdd` covers Windows (WSD) and legacy NetBIOS, but macOS Finder's preferred path is Bonjour — `FELHOM.local` does not resolve and the share never appears in the Finder sidebar. Likely the whole of symptom 1b. | nothing on udp/5353 (§ Phase A); container log daemon list; `infra/samba.go` header comment citing the R-6 spike | **S–M.** Add avahi-daemon (or `wsdd`'s mDNS sibling) to the felhom-samba image publishing `_smb._tcp`, host-net so multicast reaches the LAN. Re-runs the R-6 spike's constraint set — treat as a slice, not a hotfix. Confirm with the Mac matrix first (below) so we fix the mechanism that is actually failing. | yes — image change + republish | +| **S-4** | **LOW** | Success-state semantics: the ensure job's phase never returns to `idle` after a terminal phase, and `SambaRunning()` masks that anyway. Harmless today only because S-1's fix removes the consumer that cared. Worth folding into the **unified async-job feedback** roadmap item the `samba_ensure_job.go` header already names. | `samba_ensure_job.go` (no reset path); handler line 246 | Fold into the roadmap item; no standalone task. | no | +| **S-5** | **INFO** | The guest holds its LAN address by **DHCP** (`ip=dhcp`, lease ~54 min at probe). Any printed IP is not stable across a lease change or a site move, which weakens S-2 unless the page re-reads it live (it should — render it, never persist it). | `pct config 9201`; `valid_lft 3251sec` | Render live, never cache. Consider a DHCP reservation at the customer's router as a documented install step. | no | + +**Recommended packaging:** S-1 alone as an immediate patch task (it bricks a shipped page). S-2 with +it if the guest IP is already available to the sharing handler — same page, same release. S-3 as its +own slice with an image republish. S-4 stays on the roadmap. + +--- + +## Mac test matrix (for Viktor — CC cannot drive the Mac) + +Run in Terminal, in order. First one is the important one. + +| # | Command | Expected if the diagnosis is right | What a surprise means | +|---|---|---|---| +| 1 | `ifconfig \| grep 'inet 192'` | an address on `192.168.0.0/24` **and** `route -n get 192.168.0.104` pointing at the Wi-Fi/Ethernet interface, not `utun*` | if it leaves via `utun*`, you are at home on the tailnet and the whole test is against the wrong site — see the subnet-collision section | +| 2 | `nc -zv 192.168.0.104 445` | `succeeded!` | a timeout with #1 green = something between the Mac and the guest filters 445 (AP client isolation / guest Wi-Fi) — a **new** finding, tell CC | +| 3 | Finder → Ugrás → Csatlakozás a szerverhez → `smb://192.168.0.104` | login prompt; user `felhom`, the household password (out-of-band) → the `Share` folder mounts | **this is the fix for symptom 1** | +| 4 | `smbutil lookup FELHOM` | returns `192.168.0.104` | empty/error ⇒ macOS is not doing NBNS at all ⇒ **S-3 confirmed as the cause of symptom 1b**, and the mDNS slice is the real fix | +| 5 | `dns-sd -B _smb._tcp` | **FELHOM will not appear** — predicted by S-3 | if it *does* appear, S-3 is wrong and 1b needs re-opening | +| 6 | Remove the stale `smb://…162` Finder favourite (Finder → Ugrás → Csatlakozás → select → `−`), and `Kilépés a szerverről` any half-mounted entry | stops the recurring failure dialog | — | + +Also expect the `/sharing` page to keep reloading until S-1 ships. To read it in the meantime, load +it with JavaScript disabled, or read the state from `docker ps` in the guest. + +--- + +## Actions taken + +**None.** No restart was needed (H2 excluded by the very first probe — the samba container had been +up 3 h with clean logs). No files, settings, `smb.conf`, containers, or guest network state were +modified. Every command in this document is a read, except `curl -X POST /login` to obtain a session +for the `/sharing/status` polls, which mutates nothing. + +## Observations (noticed, not acted on) + +* `nmblookup -U 127.0.0.1 -R FELHOM` mis-parses: `-R` is `--name-resolve=ORDER` in this Samba build, + not `--recursion`, so the invocation errors out. Diagnostic-tooling trivia only — `-B` works and + was used for the real proof. Worth remembering for future runbooks. +* `smbclient -L 127.0.0.1 -N` reports "Anonymous login successful" and then lists nothing — correct + and intended under `map to guest = never`, but it reads like a failure. If a health probe is ever + built on this command, it must not treat the empty list as an error. +* `192.168.0.180 FAILED` in the felhom-pve neighbour table is the clean, one-line proof that the box + is off the DooPlex segment — a handy check for the rest of the trip. +* No secrets appear in this document: the household SMB password and the controller login password + are stored out-of-band.