# REPORT — v0.152.0 + felhom-samba 1.1.0: mDNS for macOS (S-3), and the connect card stops offering a dead form **Date:** 2026-07-20 · **Repo:** felhom-controller (v0.151.0 → **v0.152.0**) + **felhom-samba 1.0.0 → 1.1.0** · Trunk, pushed to `main`. **Origin:** operator report — `smb://FELHOM` still failing from a Mac after v0.151.0; S-3 of `felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md`. ## Baselines | Repo / artifact | start | end | |---|---|---| | felhom-controller | `5c105fb`, v0.151.0 live on 9201 | `37e12c8` (+ this docs commit), **v0.152.0** | | felhom-samba | `1.0.0` | **`1.1.0`**, digest `sha256:1c17c094…` | | felhom.eu | `a7d9837` | `1d1d60a` | ## The finding that redirected the fix My earlier conclusion — "macOS no longer does NetBIOS" — **was wrong**, and a packet capture on the box disproved it. On a bare `smb://FELHOM` from the operator's Mac (`192.168.0.11`): ``` 11:18:30.222080 .11:52844 > 192.168.0.255:137 NBNS query, 50 B name → "FELHOM" + 9 pad + suffix 0x20 (File Server Service — correct for SMB) 11:18:30.222220 .104:137 > .11:52844 NBNS response, 62 B, +140 µs flags 0x8580 = response, AUTHORITATIVE, RCODE=0 · ANCOUNT 1 TTL 259200 · NB_FLAGS 0x0000 (unique, B-node) · RDATA 192.168.0.104 ``` **macOS asked correctly, was answered correctly in 140 microseconds, and never opened a TCP connection.** It retried once, was answered again, gave up. Sixteen seconds later the same Mac queried `FELHOM.local` over mDNS and went straight to port 445. So NetBIOS on macOS feeds legacy browsing, not `smb://` URL resolution: **the bare `smb://` cannot be made to work from a Mac by any change on our side**, and nmbd — which the R-6/S4b spike was right to insist on — was never the broken part. It is precisely what serves Windows. Investigated and dismissed: the box answers **twice** per broadcast (nmbd holds `0.0.0.0:137`, `:137` and `:137`; a broadcast lands on two). Standard Samba, and a duplicated correct answer is still a correct answer that macOS declined to use. ### Proven matrix | Client | Working form | Served by | |---|---|---| | Windows | `\\` | nmbd (+ wsdd for the Network view) | | macOS | **`smb://.local`** | avahi/mDNS — new in 1.1.0 | | Any | `smb://` | direct | ## Method — spike before publish (operator's call, and it paid) avahi was installed by hand into the *running* container and configured, then proven from a second machine before any image was built. The operator confirmed `smb://FELHOM.local` connects. Only then was 1.1.0 built. The spike also chose the design: a **static avahi service file** beats smbd's own `multicast dns register`, because `smb.conf` is bind-mounted READ-ONLY and owned by the controller's renderer, and a static file additionally publishes `_device-info._tcp`. ## Changes | File | Change | |---|---| | `controller/infra-images/samba/Dockerfile` | `avahi` + `dbus` added; `rm` of packaged service files; header documents the captured NBNS proof and that sidebar discovery is NOT claimed | | `controller/infra-images/samba/entrypoint.sh` | templates `avahi-daemon.conf` + `_smb._tcp`/`_device-info._tcp` service file **from `FELHOM_SERVER_NAME`**; starts dbus + avahi, both **non-fatal** | | `controller/internal/infra/infra.go` | `SambaImage` pin → `1.1.0` (`Images()` and the golden bake follow automatically) | | `controller/internal/web/templates/sharing.html` | Mac line `smb://` → **`smb://.local`**; Windows flat name untouched | | `controller/internal/web/sharing_connect_card_test.go` | assertions updated + new `TestSharingConnectCard_MacLineIsDotLocalNotBareName` | | `controller/internal/infra/samba_test.go` | tag assertion derives from `SambaImage`; adds an explicit non-`:latest` assertion | | `controller/internal/web/handler_export_upload_test.go` | async-race fix (below) | | `CHANGELOG.md`, `controller/README.md`, `REPORT.md` | docs | ## Tests **23/23 packages green, run twice with `-count=1`.** Red-proofs, both mutated → FAILED → restored: | Mutation | Result | |---|---| | revert the template's Mac line to the bare `smb://{{.SMBServerName}}` | `TestSharingConnectCard_MacLineIsDotLocalNotBareName` **FAILED** on both the missing `.local` and the present bare form, for both configured names (`FELHOM`, `OTTHON`) | | delete `os.Remove(job.partPath)` from `expireIdleUpload` | `TestFabUpload_GCAndIdleTimeout` **FAILED** — `idle-expired .part must be deleted` | ### Two test bugs found, neither a production defect 1. **`TestRenderSambaCompose` pinned the literal tag `1.0.0`**, so a routine image bump read as a renderer regression. Now derives from `SambaImage`, plus a separate assertion for what actually matters — the tag is explicit and never `:latest`. 2. **`TestFabUpload_GCAndIdleTimeout` raced.** `expireIdleUpload` nils the slot, releases the mutex, and only *then* closes and unlinks the `.part` — so "the slot is free" does not yet mean "the file is gone", and the test stat-ed immediately. It passed in isolation and failed in the full package once this release's new render tests made `web` heavier. **Not caused by this change and not a production bug** (a new upload mints a fresh random `.part`, so the gap is harmless); the test was asserting an async post-condition synchronously. It now waits on the same 3 s deadline with the assertion unchanged. Design gates `template_id_gate` / `emoji_gate` / `native_confirm_gate` / `offbox_rename_gate`: **OK**. ## Build, publish, deploy ``` ./controller/scripts/build-samba-image.sh 1.1.0 --push → sha256:1c17c09422be… smoke test (DooPlex, DEFAULT BRIDGE — never --network host on this host, it would bind 445/5353) → smbd · nmbd · wsdd · dbus-daemon · avahi-daemon: running [SMOKETEST.local] (all 5 up) → host-name=SMOKETEST, allow-interfaces=eth0, _smb._tcp/445 in the service file anonymous pull from guest 9201 (no registry creds) → OK ./build.sh 0.152.0 --push → deployed to 9201 ``` Live: `felhom-controller:0.152.0 Up (healthy)` · `felhom-samba:1.1.0 Up` — the controller's reconcile recreated the samba container onto the real image, discarding the hand-patched spike. ## Live validation **Method: on-the-wire from a second machine (felhom-pve) + endpoint-level for the page.** ``` mDNS from felhom-pve, against the SHIPPED image: FELHOM.local (A) -> A 192.168.0.104 (from 192.168.0.104) _smb._tcp (browse) -> PTR FELHOM._smb._tcp.local (from 192.168.0.104) daemons in felhom-samba:1.1.0: smbd · nmbd · wsdd · dbus-daemon · avahi-daemon: running [FELHOM.local] GET /sharing -> smb://FELHOM.local smb://192.168.0.104 \\FELHOM GET /sharing/status ×2 -> phase:"idle", running:true (v0.151.0 contract still holding) ``` Operator, from the Mac: **`smb://FELHOM.local` connects and prompts for credentials.** ## Still open - **Finder-sidebar discovery — NOT shipped, NOT claimed.** The `_smb._tcp` record is published and answers browse queries on the wire, but FELHOM did not appear in the operator's Finder sidebar. That window showed no Network/Bonjour section at all, which points at **Finder Settings → Sidebar → Locations** rather than at the box — unverified either way. Next: that setting, and `dns-sd -B _smb._tcp` on the Mac. Zero-typing discovery was the R-6 spike's original ambition and is still not demonstrated. - **Windows was not retested** this session. `\\FELHOM` is served by nmbd, which this release does not touch, and the capture proves nmbd answers correctly — but no Windows client was exercised. ## Observations — noticed, not acted on - **The username trap.** macOS prefills the local account name (`Viktor.Nagyfenyvesi`) in the SMB credential dialog; the household account is `felhom`. The page states this in the password card, but not next to the connect addresses where the customer is looking at that moment. Worth a sentence in the connect card — deliberately not added mid-session without a design pass. - **nmbd's duplicate answer** is harmless here but would look alarming in any future capture. Noted in the DIAG so the next person does not re-investigate it. - **The spike left avahi installed by hand** in the old container. It was discarded when the reconcile recreated the container on 1.1.0 — worth remembering that hand-patching a *managed* infra container is always temporary by construction, which is a safety property, not a limitation. - **`dns-sd`/`avahi-browse` were not available** anywhere on the box side, so both the NBNS and mDNS verifications were done with small hand-written UDP probes run from felhom-pve. That turned out to be the more valuable method anyway: querying from a *second machine* is what distinguishes "the daemon answers itself" from "the daemon answers the network", and it is what proved the box innocent before any code changed.