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.
160 lines
4 KiB
Go
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())
|
|
}
|