banger/internal/paths/paths.go
Thales Maciel fcedacba5c
Make runtime defaults portable
Stop assuming one workstation layout for runtime artifacts, mapdns, and host tooling. The daemon and shell helpers now use portable mapdns configuration, and runtime bundles can carry bundle.json metadata for their default kernel, initrd, modules, rootfs, and helper paths.

Load bundle metadata through config with a legacy layout fallback, thread mapdns_bin/mapdns_data_file through the Go and shell paths, and add command-scoped preflight checks for VM start, NAT, image build, work-disk resize, and SSH so missing tools or artifacts fail with actionable errors.

Update the runtime-bundle manifest, docs, and tests to match the new model. Verified with go test ./..., make build, and bash -n customize.sh interactive.sh dns.sh make-rootfs.sh verify.sh.
2026-03-16 15:30:08 -03:00

160 lines
4 KiB
Go

package paths
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"banger/internal/runtimebundle"
)
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
}
}
sourceRuntimeDir := filepath.Join(exeDir, "runtime")
if HasRuntimeBundle(sourceRuntimeDir) {
return sourceRuntimeDir
}
return ""
}
func HasRuntimeBundle(dir string) bool {
if strings.TrimSpace(dir) == "" {
return false
}
if _, err := runtimebundle.LoadBundleMetadata(dir); err == nil {
return true
}
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 RuntimeBundleHint() string {
return "run `make runtime-bundle` or set runtime_dir in ~/.config/banger/config.toml"
}
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())
}