docs(samba): CHANGELOG v0.144.0 + REPORT + CONTEXT + README §16 + REUSE

Records the R-7 slice 1 ship, the four red-proof outcomes, live-validation
evidence, the two bugs live validation caught, and the Part-4 Step-1 enumeration
finding + the reported design fork (share data classified but not yet in a live
backup run — needs a Viktor ruling, suggested R-7b).
This commit is contained in:
2026-07-18 12:03:24 +02:00
parent b409f5eee2
commit a4c82a2651
5 changed files with 329 additions and 49 deletions
+76
View File
@@ -2305,6 +2305,82 @@ Toggle switches on the provider's deploy/settings page ("Integrációk" section,
---
### 16. Network File Sharing — SMB („Megosztás") (v0.144.0, R-7 slice 1)
LAN file sharing so the box behaves like a NAS: the customer enables sharing, sets ONE household SMB
password, and exports folders that appear in Windows Explorer / Mac Finder as `\\FELHOM\<share>`.
**Architecturally this is an EMBEDDED CONTROLLER FEATURE, not a catalog app** — three reasons:
it requires `network_mode: host` (the R-6 spike proved the default docker bridge is deaf to the LAN
multicast that WSD/mDNS discovery needs), its configuration is a dynamic share list rendered into
`smb.conf` (not env vars), and its share roots must ride the backup classification. It is therefore
the **fourth protected infra stack**: traefik / cloudflared / filebrowser / **samba**.
#### The image (`controller/infra-images/samba/`, `felhom-samba:1.0.0`)
Our own pinned image (alpine 3.21 by digest) running **three** daemons — `smbd` (445), **`nmbd`**
(NetBIOS flat-name resolution), `wsdd` (WS-Discovery), under `tini`. nmbd is not optional: the R-6
spike proved wsdd alone makes the box *visible* in Explorer while the double-click still fails
`0x80070035`, because WSD supplies an icon, not a name→IP mapping. The image is deliberately dumb —
`/etc/samba/smb.conf` is bind-mounted READ-ONLY by the controller, nothing is templated inside, no
name or password is baked, and the passdb lives on a named volume so the household password survives
container recreation. Built by `controller/scripts/build-samba-image.sh` (never `:latest`).
#### Data model (`internal/settings/smb.go`)
- `SMBSettings{Enabled, ServerName, UserSet}``ServerName` is the NetBIOS name (≤15, NetBIOS-safe).
- `SMBShare{Name, Path, ReadOnly, Offsite, CreatedAt}` — the share registry.
- **The SMB password is NEVER persisted.** Only `UserSet` (a boolean) is stored; the secret lives in
the container's passdb, applied via `smbpasswd` on STDIN.
#### Rendering + lifecycle (`internal/infra/samba.go`, `internal/stacks/samba.go`)
Pure renderers produce a hardened `smb.conf` (`server min protocol = SMB2`, `bind interfaces only`
on `lo eth0`, `disable netbios = no`, `map to guest = never`, per-share `force user/group = felhom`
so every SMB write lands as uid:gid 1000) and a compose file (`network_mode: host`, pinned image,
config `:ro`, passdb volume, one bind per share — `:ro` for read-only shares as defence in depth).
`ensureSamba` joins `EnsureBaseStack` after filebrowser, gated on `SMB.Enabled` (the cloudflared
conditional-deploy precedent); `ReconcileSamba` runs after every mutation. Both are idempotent —
unchanged config plus a running container performs **zero** compose calls. Config writes are atomic
(tmp+fsync+rename). A share whose drive is disconnected/decommissioned is rendered ABSENT from
smb.conf (never export a dead mountpoint) while its configuration is retained.
**Nothing in this feature deletes or moves customer files.** Disabling sharing is `compose down`
(passdb volume kept); deleting a share is a config-only edit. The only `os.Mkdir*` is the guarded
new-share-folder create.
#### UI + the picker guard (`internal/web/sharing_handlers.go`, `templates/sharing.html`)
Top-nav category **„Megosztás"** → **„Hálózati megosztás"**: enable/server-name card, household
password, shares table (Név · Mappa · Írásvédett · Felhőmentés · Törlés), and a create flow — either a
NEW folder under `<storage>/shares/` or an EXISTING folder chosen in a browse modal.
`sharingResolvePath` is the security gate for every customer-supplied path: absolute →
`EvalSymlinks` (before containment, so a planted symlink cannot escape) → must live inside a
registered, live storage root → must not be in a deny-listed system subtree → must be a directory.
**Refusals are uniform** (`Ez a mappa nem osztható meg.`) so the picker can never act as a filesystem
oracle. The deny-list is DERIVED from `stacks.SharingDeniedRoots`, itself provably a subset of
`ProtectedHDDPaths` — it can only shrink relative to the delete guard, never drift into a stale second
list. The drive root is an exact-match denial (a whole drive is never shareable) while user-data
folders under it stay shareable. `sharingResolveStorageRoot` is a separate, strictly tighter check
used only as the new-folder parent. The picker endpoint is `/api/sharing/browse`, registered on the
main mux behind `RequireAuth`+`CsrfProtect` (the `/api/` subtree is routed there, not in the web
`ServeHTTP` switch).
#### Backup classification (`internal/stacks/samba_classify.go`)
`ClassifiedBinds("samba")` resolves from the shares registry rather than catalog metadata (samba has
no `.felhom.yml` and its binds are absolute share paths). Per-share `Felhőmentés` ON → `mandatory`
(offsite + tier-2); OFF → `optional` (tier-2 only). smb.conf/passdb are never classified.
> **KNOWN GAP (design fork, deliberately not improvised).** The seam is correct, but share data is not
> yet in a live tier-2/offsite RUN: `backup.RunTier2` short-circuits on a missing recovery unit before
> it reaches `GetStackClassifiedBinds`, and the offsite runner enumerates `settings.GetOffboxApps()`.
> Both engines are recovery-unit shaped, which a share-only infra stack has not. See root `REPORT.md`.
---
## Repository Layout
```