diff --git a/internal/config/paths.go b/internal/config/paths.go index 7f5baaa..7b8f22d 100644 --- a/internal/config/paths.go +++ b/internal/config/paths.go @@ -31,13 +31,20 @@ func ResolvePath(envKey, defaultRel string) string { func ConfigPath() string { if strings.TrimSpace(os.Getenv("DS2API_CONFIG_PATH")) == "" && BaseDir() == "/app" { - // Official container images commonly run from /app where filesystem may be read-only. - // Prefer /data default so deployments can persist config/token state by mounting a volume. - return "/data/config.json" + return containerDefaultConfigPath() } return ResolvePath("DS2API_CONFIG_PATH", "config.json") } +func containerDefaultConfigPath() string { + // Container images run as non-root by default. Only use /data when mounted/provisioned. + // Otherwise keep /app/config.json so admin-side save does not fail on MkdirAll("/data"). + if st, err := os.Stat("/data"); err == nil && st.IsDir() { + return "/data/config.json" + } + return "/app/config.json" +} + func legacyContainerConfigPath() string { return "/app/config.json" } diff --git a/internal/config/paths_test.go b/internal/config/paths_test.go new file mode 100644 index 0000000..00fa51a --- /dev/null +++ b/internal/config/paths_test.go @@ -0,0 +1,28 @@ +package config + +import ( + "os" + "testing" +) + +func TestContainerDefaultConfigPath(t *testing.T) { + t.Run("fallback to /app when /data is missing", func(t *testing.T) { + // This test environment does not guarantee a writable/mounted /data. + // If /data is absent we must keep /app fallback to avoid persistence failures. + if _, err := os.Stat("/data"); err == nil { + t.Skip("/data exists in this environment; cannot validate missing-/data fallback") + } + if got := containerDefaultConfigPath(); got != "/app/config.json" { + t.Fatalf("containerDefaultConfigPath() = %q, want %q", got, "/app/config.json") + } + }) + + t.Run("prefer /data when /data directory exists", func(t *testing.T) { + if _, err := os.Stat("/data"); err != nil { + t.Skip("/data does not exist in this environment") + } + if got := containerDefaultConfigPath(); got != "/data/config.json" { + t.Fatalf("containerDefaultConfigPath() = %q, want %q", got, "/data/config.json") + } + }) +}