Make installed banger self-contained

Fix the misleading make install path where banger and bangerd still depended on a repo checkout for Firecracker, guest artifacts, image builds, and the SSH key.

Replace repo-root inference with an explicit runtime bundle model: resolve a runtime_dir from env/config/install layout, derive concrete artifact paths from it, and update the daemon, CLI, and image-build flow to use those paths. Keep repo_root only as an explicit compatibility alias instead of auto-detecting it.

Teach customize.sh to run from a read-only bundled runtime tree while writing transient state under XDG/BANGER_STATE_DIR, and make make install copy the runtime assets into PREFIX/lib/banger so installed binaries stay usable outside the repo.

Validate with go test ./..., make build, bash -n customize.sh, and make install DESTDIR=/tmp/banger-install PREFIX=/usr. An out-of-repo installed-binary smoke test was attempted, but this sandbox blocked bangerd from binding its Unix socket (setsockopt: operation not permitted).
This commit is contained in:
Thales Maciel 2026-03-16 14:26:50 -03:00
parent 375900cf65
commit ce1be52047
No known key found for this signature in database
GPG key ID: 33112E6833C34679
13 changed files with 437 additions and 107 deletions

View file

@ -267,13 +267,13 @@ func (d *Daemon) backgroundLoop() {
}
func (d *Daemon) ensureDefaultImage(ctx context.Context) error {
if d.config.DefaultImageName == "" || d.config.RepoRoot == "" {
if d.config.DefaultImageName == "" {
return nil
}
if _, err := d.store.GetImageByName(ctx, d.config.DefaultImageName); err == nil {
return nil
}
rootfs := filepath.Join(d.config.RepoRoot, "rootfs-docker.ext4")
rootfs := d.config.DefaultRootfs
kernel := d.config.DefaultKernel
initrd := d.config.DefaultInitrd
if !exists(rootfs) || !exists(kernel) {

View file

@ -0,0 +1,54 @@
package daemon
import (
"context"
"os"
"path/filepath"
"testing"
"banger/internal/model"
"banger/internal/store"
)
func TestEnsureDefaultImageUsesConfiguredDefaultRootfs(t *testing.T) {
dir := t.TempDir()
rootfs := filepath.Join(dir, "rootfs-docker.ext4")
kernel := filepath.Join(dir, "vmlinux")
for _, path := range []string{rootfs, kernel} {
if err := os.WriteFile(path, []byte("test"), 0o644); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
db, err := store.Open(filepath.Join(dir, "state.db"))
if err != nil {
t.Fatalf("open store: %v", err)
}
t.Cleanup(func() {
_ = db.Close()
})
d := &Daemon{
config: model.DaemonConfig{
DefaultImageName: "default",
DefaultRootfs: rootfs,
DefaultKernel: kernel,
},
store: db,
}
if err := d.ensureDefaultImage(context.Background()); err != nil {
t.Fatalf("ensureDefaultImage: %v", err)
}
image, err := db.GetImageByName(context.Background(), "default")
if err != nil {
t.Fatalf("GetImageByName: %v", err)
}
if image.RootfsPath != rootfs {
t.Fatalf("RootfsPath = %q, want %q", image.RootfsPath, rootfs)
}
if image.KernelPath != kernel {
t.Fatalf("KernelPath = %q, want %q", image.KernelPath, kernel)
}
}

View file

@ -22,9 +22,6 @@ func (d *Daemon) BuildImage(ctx context.Context, params api.ImageBuildParams) (m
if _, err := d.FindImage(ctx, name); err == nil {
return model.Image{}, fmt.Errorf("image name already exists: %s", name)
}
if d.config.RepoRoot == "" {
return model.Image{}, fmt.Errorf("repo root not found; set repo_root in config.toml")
}
baseRootfs := params.BaseRootfs
if baseRootfs == "" {
baseRootfs = d.config.DefaultBaseRootfs
@ -42,7 +39,10 @@ func (d *Daemon) BuildImage(ctx context.Context, params api.ImageBuildParams) (m
return model.Image{}, err
}
rootfsPath := filepath.Join(artifactDir, "rootfs.ext4")
script := filepath.Join(d.config.RepoRoot, "customize.sh")
script := d.config.CustomizeScript
if script == "" {
return model.Image{}, fmt.Errorf("customize script not configured; set runtime_dir or customize_script in config.toml")
}
if _, err := os.Stat(script); err != nil {
return model.Image{}, fmt.Errorf("customize.sh not found at %s", script)
}
@ -78,7 +78,12 @@ func (d *Daemon) BuildImage(ctx context.Context, params api.ImageBuildParams) (m
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Dir = d.config.RepoRoot
cmd.Dir = d.layout.StateDir
cmd.Env = append(
os.Environ(),
"BANGER_RUNTIME_DIR="+d.config.RuntimeDir,
"BANGER_STATE_DIR="+filepath.Join(d.layout.StateDir, "image-build"),
)
if err := cmd.Run(); err != nil {
_ = os.RemoveAll(artifactDir)
return model.Image{}, err

View file

@ -546,10 +546,10 @@ func (d *Daemon) createTap(ctx context.Context, tap string) error {
}
func (d *Daemon) firecrackerBinary() (string, error) {
if d.config.RepoRoot == "" {
return "", errors.New("repo root not detected")
if d.config.FirecrackerBin == "" {
return "", errors.New("firecracker binary not configured; set runtime_dir or firecracker_bin in config.toml")
}
path := filepath.Join(d.config.RepoRoot, "firecracker")
path := d.config.FirecrackerBin
if !exists(path) {
return "", fmt.Errorf("firecracker binary not found at %s", path)
}
@ -689,15 +689,12 @@ func (d *Daemon) requireStartPrereqs(ctx context.Context) error {
}
func (d *Daemon) generateName(ctx context.Context) (string, error) {
if d.config.RepoRoot != "" {
namegen := filepath.Join(d.config.RepoRoot, "namegen")
if exists(namegen) {
out, err := d.runner.Run(ctx, namegen)
if err == nil {
name := strings.TrimSpace(string(out))
if name != "" {
return name, nil
}
if exists(d.config.NamegenPath) {
out, err := d.runner.Run(ctx, d.config.NamegenPath)
if err == nil {
name := strings.TrimSpace(string(out))
if name != "" {
return name, nil
}
}
}