package app import ( "fmt" "os" "path/filepath" "strings" ) // ResolveConfigPath determines which controller configuration file to load. // Explicit CLI and environment paths are authoritative. Without either, the // current directory, executable directory, and container default are checked. func ResolveConfigPath(cliPath, envPath string) (string, error) { if path := strings.TrimSpace(cliPath); path != "" { return requireConfigFile(path, "-config") } if path := strings.TrimSpace(envPath); path != "" { return requireConfigFile(path, "ENV_CONTROLLER_CONFIG") } candidates := make([]string, 0, 3) if cwd, err := os.Getwd(); err == nil { candidates = append(candidates, filepath.Join(cwd, "controller.json")) } if executable, err := os.Executable(); err == nil { candidates = append(candidates, filepath.Join(filepath.Dir(executable), "controller.json")) } candidates = append(candidates, filepath.FromSlash("/config/controller.json")) candidates = uniqueCleanPaths(candidates) for _, candidate := range candidates { info, err := os.Stat(candidate) if err == nil && !info.IsDir() { absolute, absErr := filepath.Abs(candidate) if absErr == nil { return absolute, nil } return candidate, nil } } exampleHints := make([]string, 0, 2) for _, candidate := range candidates { example := filepath.Join(filepath.Dir(candidate), "controller.example.json") if info, err := os.Stat(example); err == nil && !info.IsDir() { exampleHints = append(exampleHints, example) } } message := fmt.Sprintf("controller configuration not found; searched: %s", strings.Join(candidates, ", ")) if len(exampleHints) > 0 { message += fmt.Sprintf("; create controller.json from: %s", strings.Join(uniqueCleanPaths(exampleHints), ", ")) } message += "; alternatively pass -config or set ENV_CONTROLLER_CONFIG" return "", fmt.Errorf("%s", message) } func requireConfigFile(path, source string) (string, error) { cleaned := filepath.Clean(path) info, err := os.Stat(cleaned) if err != nil { return "", fmt.Errorf("configuration path from %s %q: %w", source, cleaned, err) } if info.IsDir() { return "", fmt.Errorf("configuration path from %s %q is a directory", source, cleaned) } absolute, err := filepath.Abs(cleaned) if err == nil { return absolute, nil } return cleaned, nil } func uniqueCleanPaths(paths []string) []string { seen := make(map[string]struct{}, len(paths)) result := make([]string, 0, len(paths)) for _, path := range paths { if strings.TrimSpace(path) == "" { continue } cleaned := filepath.Clean(path) key := cleaned if filepath.Separator == '\\' { key = strings.ToLower(cleaned) } if _, exists := seen[key]; exists { continue } seen[key] = struct{}{} result = append(result, cleaned) } return result }