controller/sharing.md (code-verified vs controller v0.144.0 + felhom-samba 1.0.0); capability map 'Files from Windows Explorer / Mac Finder (SMB server)' MISSING -> IMPLEMENTED (PROVEN-LIVE pending Viktor's Explorer leg); ROADMAP R-7 -> shipped-slice-1 with the slice-2 remainder, and the backup design fork split out as R-7b (shares are classified but not in any live backup run yet).
9.2 KiB
Controller — LAN file sharing over SMB („Megosztás")
Code-verified feature doc. Matches
felhom-controllerv0.144.0 (R-7 slice 1) and the infra imagegitea.dooplex.hu/admin/felhom-samba:1.0.0. Transport/discovery verdicts come fromdocumentation/audits/SPIKE-lan-discovery-2026-07-18.md(R-6). Where this doc and the code disagree, the code wins — update this file.
What the customer gets
The box behaves like a NAS on the home network. On „Megosztás" → „Hálózati megosztás" the customer
turns sharing on, sets ONE household SMB password, and exports folders. The box then appears in
Windows Explorer's Network view (and in Mac Finder) as \\FELHOM; opening a share and writing to it
works from any household device. Sharing is LAN-only — it is never exposed through the tunnel.
Why it is NOT a catalog app
SMB ships as an embedded controller feature — the fourth protected infra stack (traefik / cloudflared / filebrowser / samba). Three reasons, all structural:
- Host networking is mandatory. The R-6 spike proved the default Docker bridge is categorically deaf to the LAN multicast that WSD and mDNS discovery need; only a container in the guest's own network namespace can send and receive it.
- Its config is a generated share list, rendered into
smb.conf— not a handful of env vars a catalog template could carry. - Its share roots must ride the backup classification, which catalog metadata cannot express for absolute, customer-chosen paths.
The image (felhom-samba:1.0.0)
Built from controller/infra-images/samba/ by controller/scripts/build-samba-image.sh. Pinned
alpine 3.21 (by digest) running three daemons under tini:
| Daemon | Port(s) | Why |
|---|---|---|
smbd |
445 | the SMB/CIFS server itself |
nmbd |
137/138 | NetBIOS flat-name resolution — without it the box is visible in Explorer but the double-click fails 0x80070035 (S4b of the spike: WSD hands Explorer an icon, not a name→IP mapping) |
wsdd |
3702 / 5357 | WS-Discovery, so the box appears in Explorer's Network view at all |
The image is deliberately dumb: /etc/samba/smb.conf is bind-mounted READ-ONLY by the controller,
nothing is templated inside, and no share name or password is ever baked in. The passdb lives on
a named volume (samba-passdb) so the household password survives container recreation. Never
tagged :latest; the controller pins the exact tag in the compose it generates.
Configuration model
Persisted in settings.json (internal/settings/smb.go):
smb: {enabled, server_name, user_set}—server_nameis the NetBIOS name (≤15 chars, letters/digits/hyphen/underscore; defaultFELHOM).smb_shares: [{name, path, read_only, offsite, created_at}]— the share registry.
The SMB password is never persisted. Only the boolean
user_setis stored. The secret is passed tosmbpasswdon STDIN (never argv, which is world-readable via/proc), is never logged, and never appears insmb.conf, the compose file, orsettings.json.
Generated smb.conf
Global block (hardened, all controller-managed):
server min protocol = SMB2 # no SMB1
security = user # named auth only
map to guest = never # no anonymous access, ever
disable netbios = no # nmbd on → \\NAME resolves
bind interfaces only = yes
interfaces = lo eth0 # LAN + loopback ONLY, never the docker bridges
Each share adds a section with force user/force group = felhom (uid:gid 1000), so files written
over SMB carry the same ownership the apps and both backup tiers expect:
[dokumentumok]
path = /mnt/felhom-drives/hdd_1/shares/dokumentumok
read only = no
valid users = felhom
force user = felhom
force group = felhom
A read-only share gets read only = yes and a :ro bind in the compose file — defence in depth,
so a samba misconfiguration alone cannot make it writable.
Lifecycle
ensureSamba runs inside EnsureBaseStack (boot + every health tick) after filebrowser, gated on
smb.enabled — the same conditional-deploy shape cloudflared uses. ReconcileSamba runs after every
share/settings mutation: re-render → atomic write (tmp + fsync + rename) → compose up -d.
- Idempotent: unchanged config plus a running container performs zero compose calls.
- No password, no deploy: with sharing on but no household password set, the stack stays
undeployed and the UI blocks with „először adj meg jelszót". (Setting the password brings the stack
up as part of that action — safe, because
security = user+map to guest = nevermeans nothing is reachable until the password lands.) - Dead mounts are never exported: a share whose drive is disconnected or decommissioned is
rendered ABSENT from
smb.conf(publishing a missing mountpoint would show an empty share and let a write land on the underlying root instead of the drive). Its configuration is retained, and the UI row shows „A meghajtó nem elérhető."
This feature never deletes customer data
There is no file-removal path in it at all. Disabling sharing is compose down (the passdb volume and
every shared folder are kept). Deleting a share is a config-only edit — the UI says so explicitly:
„A megosztás törölve — a mappa és a fájlok megmaradtak." The only directory creation is the guarded
new-share-folder create.
Choosing what to share — the picker guard
Shares can be a new folder under <storage>/shares/, or any existing folder, with one
exception class: system data. Every customer-supplied path goes through sharingResolvePath:
- must be absolute;
EvalSymlinksbefore the containment check — a symlink planted inside a storage root cannot point outside it;- must resolve inside a registered, live storage root (decommissioned roots are out);
- must not be inside a deny-listed system subtree —
appdata/(sharing a live app database writable over SMB is a corruption foot-gun),backups/, the legacyfelhom-data/nest; - the drive root itself is refused (a whole drive is never shareable) — an exact-match rule, so ordinary user-data folders under it stay shareable;
- must be a directory.
Every refusal returns the same message („Ez a mappa nem osztható meg.") so the picker can never be used as an oracle for what exists outside the customer's storage.
The deny-list is derived from stacks.ProtectedHDDPaths (each entry is emitted only if that guard
already contains it), so it can only ever shrink relative to the delete guard — it can never drift
into a stale second source of truth. media/ and Dokumentumok/ are protected there as delete
targets but are customer data and remain shareable.
The new-folder flow uses a separate, strictly tighter check (sharingResolveStorageRoot) that accepts
exactly a registered live storage root — because there the root is the parent, not the share.
Backup classification
ClassifiedBinds("samba") resolves from the shares registry instead of catalog metadata (samba has no
.felhom.yml, and its binds are absolute share paths):
| Per-share „Felhőmentés" | Class | Tiers |
|---|---|---|
| ON (default for new shares) | mandatory |
offsite and tier-2 |
| OFF | optional |
tier-2 only (never offsite) |
smb.conf and the passdb are config, not customer data, and are never classified.
KNOWN GAP — share data is classified but not yet backed up. Making this seam correct does not by itself put share data into a live run:
backup.RunTier2short-circuits on a missing recovery unit before it ever callsGetStackClassifiedBinds, and the offsite runner enumeratessettings.GetOffboxApps(). Both engines are recovery-unit shaped, which a share-only infra stack has not. Teaching them about one is a structural change, so it was reported as a design fork rather than improvised inside the engines. Until it is resolved, do not tell a customer that files dropped on a share are backed up. Seefelhom-controller/REPORT.md§4.
Operator notes
- samba is protected in code (
config.alwaysProtectedStacks), becausecfg.Stacks.Protectedcomes from the golden-generatedcontroller.yamland predates the feature. Consequence:monitor.EffectiveProtecteddoes not monitor samba liveness — a dead samba container raises no "protected container missing" issue. Deliberate for slice 1 (the safe direction: no false alarms while the feature is off). - Discovery depends on the household LAN. The R-6 spike's §7d caveat applies: a customer network with
IGMP snooping enabled and no querier could prune the discovery multicast, and a BYO topology may
put the guest on a bridge with no L2 path to the household LAN. The page carries an honest hint that
the device list can lag while
\\NÉValways works. - The customer authenticates as user
felhomwith the household password.
Not in slice 1 (→ slice 2)
avahi / .local name publishing for modern + Apple clients; curated app-folder presets (one-click
paperless consume/); per-share users and guest access; a recycle bin. DLNA is a separate item (R-8).