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:
@@ -0,0 +1,59 @@
|
||||
//go:build linux
|
||||
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||
)
|
||||
|
||||
// TestWalkMerge_PreservesSetgid is the #8 fix proof: the non-app merge walk must preserve the SOURCE
|
||||
// dir's setgid bit + group, so the userdata ownership convention (2775 setgid, gid 1000) survives a
|
||||
// whole-drive migration. Companion: the pre-fix path (MkdirAll 0755 / copyFile .Perm()) drops setgid
|
||||
// → this test FAILS on the old code (mutation-proven separately).
|
||||
func TestWalkMerge_PreservesSetgid(t *testing.T) {
|
||||
lg := log.New(io.Discard, "", 0)
|
||||
src := t.TempDir()
|
||||
dst := t.TempDir()
|
||||
|
||||
// Source userdata-style tree with setgid dirs (the convention).
|
||||
media := filepath.Join(src, "userdata", "media")
|
||||
if err := os.MkdirAll(media, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, d := range []string{filepath.Join(src, "userdata"), media} {
|
||||
if err := os.Chmod(d, os.ModeSetgid|0o775); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(media, "movie.txt"), []byte("VID"), 0o664); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := walkMerge(lg, src, dst, nil, false, nil); err != nil {
|
||||
t.Fatalf("walkMerge: %v", err)
|
||||
}
|
||||
|
||||
// Target media dir must still carry setgid + the source group.
|
||||
srcFI, _ := os.Stat(media)
|
||||
dstFI, err := os.Stat(filepath.Join(dst, "userdata", "media"))
|
||||
if err != nil {
|
||||
t.Fatalf("target media dir missing: %v", err)
|
||||
}
|
||||
if dstFI.Mode()&os.ModeSetgid == 0 {
|
||||
t.Errorf("migration DROPPED the setgid bit on the dir: mode=%v", dstFI.Mode())
|
||||
}
|
||||
if dstFI.Mode().Perm() != 0o775 {
|
||||
t.Errorf("target dir perm = %o, want 0775", dstFI.Mode().Perm())
|
||||
}
|
||||
sg, _ := appbackup.StatGID(srcFI)
|
||||
dg, _ := appbackup.StatGID(dstFI)
|
||||
if sg != dg {
|
||||
t.Errorf("target dir group = %d, want source group %d", dg, sg)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user