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:
2026-06-14 21:58:49 +02:00
parent cbaa53f565
commit c48f95fe06
17 changed files with 523 additions and 34 deletions
@@ -219,6 +219,12 @@ func (s *Server) registerStoragePath(where, label string, setDefault bool) error
if strings.TrimSpace(label) == "" {
label = settings.InferStorageLabel(where)
}
// v0.66.0: create the full userdata skeleton with the shared-storage convention (2775 setgid,
// gid 1000) the moment a drive is registered — system drive AND additional drives. Idempotent;
// best-effort (a perms hiccup shouldn't block registration).
if err := appbackup.EnsureUserdataSkeleton(where); err != nil {
s.logger.Printf("[WARN] [web] userdata skeleton on %s: %v", where, err)
}
// Change 4: re-enrolling a previously-DECOMMISSIONED drive must un-retire it. AddStoragePath
// dedups a re-register into a no-op, so without this the soft marker would persist forever and the
// apps' "missing storage" indicator would never clear.