package paths import ( "errors" "fmt" "os" "path/filepath" "strings" ) type Layout struct { ConfigHome string StateHome string CacheHome string RuntimeHome string ConfigDir string StateDir string CacheDir string RuntimeDir string SocketPath string DBPath string DaemonLog string VMsDir string ImagesDir string KernelsDir string } func Resolve() (Layout, error) { home, err := os.UserHomeDir() if err != nil { return Layout{}, err } configHome := getenvDefault("XDG_CONFIG_HOME", filepath.Join(home, ".config")) stateHome := getenvDefault("XDG_STATE_HOME", filepath.Join(home, ".local", "state")) cacheHome := getenvDefault("XDG_CACHE_HOME", filepath.Join(home, ".cache")) runtimeHome := os.Getenv("XDG_RUNTIME_DIR") if runtimeHome == "" { runtimeHome = filepath.Join(os.TempDir(), fmt.Sprintf("banger-runtime-%d", os.Getuid())) } layout := Layout{ ConfigHome: configHome, StateHome: stateHome, CacheHome: cacheHome, RuntimeHome: runtimeHome, ConfigDir: filepath.Join(configHome, "banger"), StateDir: filepath.Join(stateHome, "banger"), CacheDir: filepath.Join(cacheHome, "banger"), RuntimeDir: filepath.Join(runtimeHome, "banger"), } layout.SocketPath = filepath.Join(layout.RuntimeDir, "bangerd.sock") layout.DBPath = filepath.Join(layout.StateDir, "state.db") layout.DaemonLog = filepath.Join(layout.StateDir, "bangerd.log") layout.VMsDir = filepath.Join(layout.StateDir, "vms") layout.ImagesDir = filepath.Join(layout.StateDir, "images") layout.KernelsDir = filepath.Join(layout.StateDir, "kernels") return layout, nil } func Ensure(layout Layout) error { for _, dir := range []string{layout.ConfigDir, layout.StateDir, layout.CacheDir, layout.RuntimeDir, layout.VMsDir, layout.ImagesDir, layout.KernelsDir} { if err := os.MkdirAll(dir, 0o755); err != nil { return err } } return nil } var executablePath = os.Executable func BangerdPath() (string, error) { if env := os.Getenv("BANGER_DAEMON_BIN"); env != "" { return env, nil } exe, err := executablePath() if err != nil { return "", err } dir := filepath.Dir(exe) for _, candidate := range []string{ filepath.Join(dir, "bangerd"), filepath.Join(dir, "bangerd.exe"), } { if _, err := os.Stat(candidate); err == nil { return candidate, nil } } return "", errors.New("bangerd binary not found next to banger; run `make build`") } func CompanionBinaryPath(name string) (string, error) { envNames := []string{ "BANGER_" + strings.ToUpper(strings.NewReplacer("-", "_", ".", "_").Replace(name)) + "_BIN", } if trimmed, ok := strings.CutPrefix(name, "banger-"); ok { envNames = append(envNames, "BANGER_"+strings.ToUpper(strings.NewReplacer("-", "_", ".", "_").Replace(trimmed))+"_BIN") } for _, envName := range envNames { if env := strings.TrimSpace(os.Getenv(envName)); env != "" { return env, nil } } exe, err := executablePath() if err != nil { return "", err } exeDir := filepath.Dir(exe) for _, candidate := range []string{ filepath.Join(exeDir, name), filepath.Join(exeDir, "..", "lib", "banger", name), filepath.Join(exeDir, "..", "libexec", "banger", name), } { if _, err := os.Stat(candidate); err == nil { return candidate, nil } } return "", fmt.Errorf("%s companion binary not found; run `make build` or reinstall banger", name) } func getenvDefault(key, fallback string) string { if value := strings.TrimSpace(os.Getenv(key)); value != "" { return value } return fallback }