Files
felhom-controller/controller/internal/stacks/migrate_setgid_linux_test.go
T
admin c48f95fe06 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>
2026-06-14 21:58:49 +02:00

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)
}
}