c48f95fe06
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>
60 lines
1.8 KiB
Go
60 lines
1.8 KiB
Go
//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)
|
|
}
|
|
}
|