feat: podman-compose stack units and root/user execution scopes
- syncup: podman first, docker only as fallback (podman wins on name clash;
existing docker units found in podman switch to podman)
- syncup: group podman-compose containers into one `kind: stack` unit per
project (project, working_dir, compose_files, compose_bin from labels);
disable existing member units ("part of stack <project>")
- generator: compose mode (podman-compose up/stop, WorkingDirectory) with
pod start/stop fallback when compose binary/dir/files are missing
- reconcile: stacks disabled with "stack not found" when neither compose
files nor pod exist
- root: scan root + rootless users listed in `users:` (podman only)
- non-root: config ~/.config/unitdore/units.yaml, own user units only
- sysuser package: runuser with XDG_RUNTIME_DIR/DBUS env, home/linger lookup
- install: warn when linger is not enabled for user units
- testdata/stack: podman-compose test stack and captured podman ps fixture
- docs: README, generated-units, man page, AGENTS, PLAN
This commit is contained in:
@@ -144,3 +144,42 @@ func TestDisabledReason_Roundtrip(t *testing.T) {
|
||||
t.Errorf("expected disabled_reason to survive roundtrip, got: %q", loaded.Units[0].DisabledReason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveAndLoad_Stack(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "units.yaml")
|
||||
cfg := &Config{
|
||||
Users: []string{"hein"},
|
||||
Units: []Unit{{
|
||||
Name: "shop", Runtime: "podman", Kind: KindStack, Project: "shop",
|
||||
WorkingDir: "/srv/shop", ComposeFiles: []string{"compose.yaml"},
|
||||
ComposeBin: "/usr/bin/podman-compose", User: "hein", Enabled: true,
|
||||
}},
|
||||
}
|
||||
if err := cfg.Save(path); err != nil {
|
||||
t.Fatalf("Save failed: %v", err)
|
||||
}
|
||||
loaded, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
u := loaded.Units[0]
|
||||
if !u.IsStack() || u.Project != "shop" || u.WorkingDir != "/srv/shop" ||
|
||||
len(u.ComposeFiles) != 1 || u.ComposeBin != "/usr/bin/podman-compose" {
|
||||
t.Errorf("stack fields not round-tripped: %+v", u)
|
||||
}
|
||||
if len(loaded.Users) != 1 || loaded.Users[0] != "hein" {
|
||||
t.Errorf("users not round-tripped: %v", loaded.Users)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsStack_DefaultKind(t *testing.T) {
|
||||
if (Unit{Name: "x"}).IsStack() {
|
||||
t.Error("unit without kind must be a container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserConfigPath(t *testing.T) {
|
||||
if got := UserConfigPath("/home/hein"); got != "/home/hein/.config/unitdore/units.yaml" {
|
||||
t.Errorf("UserConfigPath() = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user