package paths import ( "errors" "fmt" "os" "path/filepath" "strconv" "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 } 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") return layout, nil } func Ensure(layout Layout) error { for _, dir := range []string{layout.ConfigDir, layout.StateDir, layout.CacheDir, layout.RuntimeDir, layout.VMsDir, layout.ImagesDir} { if err := os.MkdirAll(dir, 0o755); err != nil { return err } } return nil } var executablePath = os.Executable func ResolveRuntimeDir(configuredRuntimeDir, deprecatedRepoRoot string) string { for _, candidate := range []string{ os.Getenv("BANGER_RUNTIME_DIR"), os.Getenv("BANGER_REPO_ROOT"), configuredRuntimeDir, deprecatedRepoRoot, } { if candidate = strings.TrimSpace(candidate); candidate != "" { return filepath.Clean(candidate) } } exe, err := executablePath() if err != nil { return "" } exeDir := filepath.Dir(exe) if filepath.Base(exeDir) == "bin" { installRuntimeDir := filepath.Clean(filepath.Join(exeDir, "..", "lib", "banger")) if HasRuntimeBundle(installRuntimeDir) { return installRuntimeDir } } if HasRuntimeBundle(exeDir) { return exeDir } return "" } func HasRuntimeBundle(dir string) bool { if strings.TrimSpace(dir) == "" { return false } required := []string{ "firecracker", "customize.sh", "packages.apt", "wtf/root/boot/vmlinux-6.8.0-94-generic", } for _, name := range required { if _, err := os.Stat(filepath.Join(dir, name)); err != nil { return false } } for _, name := range []string{"rootfs-docker.ext4", "rootfs.ext4"} { if _, err := os.Stat(filepath.Join(dir, name)); err == nil { return true } } return false } 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; build ./cmd/bangerd") } func getenvDefault(key, fallback string) string { if value := strings.TrimSpace(os.Getenv(key)); value != "" { return value } return fallback } func RuntimeFallbackLabel() string { return strconv.Itoa(os.Getuid()) }