v0.66.0: userdata layout + shared-storage ownership convention
appbackup/userdata.go: EnsureUserdataDir (MkdirAll + explicit setgid Chmod 2775 +
chown gid 1000), UserdataSkeleton, EnsureUserdataSkeleton; linux chown/StatGID +
non-linux stubs. stackEnv injects USERDATA_PATH=<HDD_PATH>/userdata. Skeleton
pre-created on register + FileBrowser sync; deploy belt (composeExecCustomEnv on
'up') pre-creates every ${USERDATA_PATH} bind source. FileBrowser mounts userdata
(was appdata) — uid 1000 can now write into 2775 setgid. #8: migrate merge walk +
copyFile preserve source setgid+group so the convention survives MigrateAll.
Non-hollow tests incl. Linux setgid assertions + migration-preserve companion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,37 @@
|
||||
## Changelog
|
||||
|
||||
### v0.66.0 — userdata layout + shared-storage ownership convention (2026-06-14)
|
||||
|
||||
Customer-facing `userdata/` tree (sibling of appdata/backups under each drive's felhom-data namespace)
|
||||
with a shared-ownership convention so FileBrowser + content apps collaborate without permission
|
||||
collisions. Spike: `felhom.eu/documentation/audits/SPIKE-userdata-layout-2026-06-14.md`. Pairs with the
|
||||
app-catalog commit that repoints media mounts to `${USERDATA_PATH}`.
|
||||
|
||||
- **Convention helper** (`internal/appbackup/userdata.go`): `EnsureUserdataDir`/`EnsureDirOwned` =
|
||||
MkdirAll → explicit `Chmod(ModeSetgid|0775)` (MkdirAll's mode is umask-masked AND drops setgid) →
|
||||
chown group to `SharedContentGID` (1000). `UserdataDir`, `UserdataSkeleton` (media/{movies,tv,music,
|
||||
audiobooks,books,comics,photos}, downloads, import/{paperless,calibre}, roms, documents),
|
||||
`EnsureUserdataSkeleton`. Linux chown via `chownGID`/`StatGID` (`userdata_linux.go`); no-op stub
|
||||
off-Linux (`userdata_other.go`).
|
||||
- **USERDATA_PATH injection** (`stackEnv`, manager.go): injects `USERDATA_PATH = <HDD_PATH>/userdata`
|
||||
(HDD_PATH is the namespace root) alongside HDD_PATH, so the catalog's `${USERDATA_PATH}/...` mounts
|
||||
resolve.
|
||||
- **Skeleton pre-create**: `registerStoragePath` + `syncFileBrowserMounts` ensure the full skeleton on
|
||||
every storage path (system + additional drives) with the convention.
|
||||
- **Deploy belt**: `composeExecCustomEnv` (gated on `up`) pre-creates every `${USERDATA_PATH}/...`
|
||||
bind source the stack declares (`ParseComposeUserdataMounts` + `ensureUserdataMounts`) so Docker
|
||||
never auto-creates a userdata dir as guest-root — covers apps not in the skeleton.
|
||||
- **FileBrowser mount switch** (`syncFileBrowserMounts`): mounts `<drive>/userdata` (was `appdata`) →
|
||||
`/srv/<name>`. FileBrowser runs as uid 1000 → can now create folders + upload into the 2775 setgid
|
||||
userdata (fixes the permission-denied); app internals (appdata/) are no longer browsable.
|
||||
- **#8 migration fix** (`migrate.go`): the non-app merge walk now preserves the SOURCE dir's full mode
|
||||
(incl. setgid via `preserveDirOwnership`) + group, and `copyFile` preserves the full file mode
|
||||
(`fi.Mode()`, not `.Perm()`) + group — so the ownership convention survives a whole-drive `MigrateAll`.
|
||||
- Non-hollow tests: `EnsureDirOwned` produces 02775+setgid+gid (Linux companion proves a plain MkdirAll
|
||||
has NO setgid); skeleton structure; `ParseComposeUserdataMounts` selectivity; deploy belt creates the
|
||||
declared dirs; **migration preserves setgid+group** (Linux; mutation-proven against the pre-fix
|
||||
0755/.Perm() path).
|
||||
|
||||
### v0.65.0 — data migration + self-serve decommission (B1+B2) (2026-06-14)
|
||||
|
||||
Customer-self-serve storage **migration** (move app data between drives) and **decommission** (retire
|
||||
|
||||
Reference in New Issue
Block a user