Files
felhom-controller/REPORT.md
T
admin fd40b29119 docs: v0.152.0 REPORT/CONTEXT + fix an async race in TestFabUpload_GCAndIdleTimeout
The fab-upload GC test stat-ed the .part immediately after observing the slot
free, but expireIdleUpload unlinks AFTER releasing the mutex. Passed alone,
failed in the full package once this release's render tests made web heavier.
Not a production defect - a new upload mints a fresh random .part. The test now
waits for the outcome it asserts on the same deadline; red-proofed by removing
the unlink from production.
2026-07-20 13:55:03 +02:00

8.8 KiB
Raw Blame History

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://<NÉV> 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, <ip>:137 and <bcast>: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 \\<NÉV> nmbd (+ wsdd for the Network view)
macOS smb://<NÉV>.local avahi/mDNS — new in 1.1.0
Any smb://<IP> 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://<NÉV>smb://<NÉV>.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 FAILEDidle-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.