From 6083e2dde5c416aa264835aa822063ba075c5db1 Mon Sep 17 00:00:00 2001 From: Thales Maciel Date: Sat, 18 Apr 2026 15:39:53 -0300 Subject: [PATCH] Prune legacy void/alpine + customize.sh flows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The golden-image Dockerfile + catalog pipeline replaces the entire manual rootfs-build stack. With that shipped, the per-distro shell flows are dead code. Removed: - scripts/customize.sh, scripts/interactive.sh, scripts/verify.sh - scripts/make-rootfs{,-void,-alpine}.sh - scripts/register-{void,alpine}-image.sh - scripts/make-{void,alpine}-kernel.sh - internal/imagepreset/ (only consumer was `banger internal packages`, which fed customize.sh) - examples/{void,alpine}.config.toml - Makefile targets: rootfs, rootfs-void, rootfs-alpine, void-kernel, alpine-kernel, void-register, alpine-register, void-vm, alpine-vm, verify-void, verify-alpine, plus the ALPINE_RELEASE / *_IMAGE_NAME / *_VM_NAME variables The void-6.12 kernel catalog entry is also gone — golden image pairs with generic-6.12 and nothing else in the catalog depended on it. Consolidated: imagemgr now holds the small DebianBasePackages list + package-hash helper inline, so the `image build --from-image` flow (still supported) no longer pulls from a separate imagepreset package. Net: 3,815 lines deleted, 59 added. No runtime functionality removed beyond the `banger internal packages` subcommand (hidden, used only by the deleted customize.sh). Co-Authored-By: Claude Opus 4.7 (1M context) --- Makefile | 67 +-- docs/kernel-catalog.md | 42 +- examples/alpine.config.toml | 9 - examples/void.config.toml | 9 - images/golden/Dockerfile | 2 +- internal/cli/banger.go | 35 -- internal/cli/cli_test.go | 18 - internal/daemon/imagemgr/paths.go | 36 +- internal/daemon/images.go | 3 +- internal/imagepreset/preset.go | 86 ---- internal/kernelcat/catalog.json | 10 - internal/kernelcat/import.go | 5 +- scripts/customize.sh | 597 ------------------------ scripts/interactive.sh | 306 ------------- scripts/make-alpine-kernel.sh | 363 --------------- scripts/make-rootfs-alpine.sh | 722 ------------------------------ scripts/make-rootfs-void.sh | 616 ------------------------- scripts/make-rootfs.sh | 99 ---- scripts/make-void-kernel.sh | 386 ---------------- scripts/register-alpine-image.sh | 64 --- scripts/register-void-image.sh | 63 --- scripts/verify.sh | 334 -------------- todos | 15 + 23 files changed, 73 insertions(+), 3814 deletions(-) delete mode 100644 examples/alpine.config.toml delete mode 100644 examples/void.config.toml delete mode 100644 internal/imagepreset/preset.go delete mode 100755 scripts/customize.sh delete mode 100755 scripts/interactive.sh delete mode 100755 scripts/make-alpine-kernel.sh delete mode 100755 scripts/make-rootfs-alpine.sh delete mode 100755 scripts/make-rootfs-void.sh delete mode 100755 scripts/make-rootfs.sh delete mode 100755 scripts/make-void-kernel.sh delete mode 100755 scripts/register-alpine-image.sh delete mode 100755 scripts/register-void-image.sh delete mode 100755 scripts/verify.sh create mode 100644 todos diff --git a/Makefile b/Makefile index 991e0ac..03e2085 100644 --- a/Makefile +++ b/Makefile @@ -21,11 +21,6 @@ GO_SOURCES := $(shell find cmd internal -type f -name '*.go' | sort) # any redundant invocations. BUILD_INPUTS := $(shell find cmd internal -type f | sort) SHELL_SOURCES := $(shell find scripts -type f -name '*.sh' | sort) -VOID_IMAGE_NAME ?= void -VOID_VM_NAME ?= void-dev -ALPINE_RELEASE ?= 3.23.3 -ALPINE_IMAGE_NAME ?= alpine -ALPINE_VM_NAME ?= alpine-dev VERSION ?= $(shell git describe --tags --exact-match 2>/dev/null || echo dev) COMMIT ?= $(shell git rev-parse --verify HEAD 2>/dev/null || echo unknown) BUILT_AT ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) @@ -33,30 +28,19 @@ GO_LDFLAGS := -X banger/internal/buildinfo.Version=$(VERSION) -X banger/internal .DEFAULT_GOAL := help -.PHONY: help build banger bangerd test fmt tidy clean rootfs rootfs-void void-kernel void-register void-vm verify-void alpine-kernel rootfs-alpine alpine-register alpine-vm verify-alpine install bench-create lint lint-go lint-shell +.PHONY: help build banger bangerd test fmt tidy clean install bench-create lint lint-go lint-shell help: @printf '%s\n' \ 'Targets:' \ - ' make build Build ./build/bin/banger, ./build/bin/bangerd, and ./build/bin/banger-vsock-agent' \ - ' make bench-create Benchmark vm create and SSH readiness with scripts/bench-create.sh' \ - ' make install Build and install banger, bangerd, and the companion vsock helper' \ - ' make test Run go test ./...' \ - ' make lint Run gofmt + go vet + shellcheck (errors)' \ - ' make fmt Format Go sources under cmd/ and internal/' \ - ' make tidy Run go mod tidy' \ - ' make clean Remove built Go binaries' \ - ' make rootfs Rebuild the manual Debian rootfs image in ./build/manual' \ - ' make void-kernel Download and stage a Void kernel, initramfs, and modules under ./build/manual/void-kernel' \ - ' make rootfs-void Build an experimental Void Linux rootfs and work-seed in ./build/manual' \ - ' make void-register Register or update the experimental Void image as $(VOID_IMAGE_NAME)' \ - ' make void-vm Register the experimental Void image and create a VM named $(VOID_VM_NAME)' \ - ' make verify-void Register the experimental Void image and run scripts/verify.sh against it' \ - ' make alpine-kernel Download and stage an Alpine virt kernel, initramfs, and modules under ./build/manual/alpine-kernel' \ - ' make rootfs-alpine Build an experimental Alpine Linux rootfs and work-seed in ./build/manual' \ - ' make alpine-register Register or update the experimental Alpine image as $(ALPINE_IMAGE_NAME)' \ - ' make alpine-vm Register the experimental Alpine image and create a VM named $(ALPINE_VM_NAME)' \ - ' make verify-alpine Register the experimental Alpine image and run scripts/verify.sh against it' + ' make build Build ./build/bin/banger, ./build/bin/bangerd, and ./build/bin/banger-vsock-agent' \ + ' make install Build and install banger, bangerd, and the companion vsock helper' \ + ' make test Run go test ./...' \ + ' make lint Run gofmt + go vet + shellcheck (errors)' \ + ' make fmt Format Go sources under cmd/ and internal/' \ + ' make tidy Run go mod tidy' \ + ' make clean Remove built Go binaries' \ + ' make bench-create Benchmark vm create and SSH readiness with scripts/bench-create.sh' build: $(BINARIES) @@ -107,36 +91,3 @@ install: build $(INSTALL) -m 0755 "$(BANGER_BIN)" "$(DESTDIR)$(BINDIR)/banger" $(INSTALL) -m 0755 "$(BANGERD_BIN)" "$(DESTDIR)$(BINDIR)/bangerd" $(INSTALL) -m 0755 "$(VSOCK_AGENT_BIN)" "$(DESTDIR)$(LIBDIR)/banger/banger-vsock-agent" - -rootfs: - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/make-rootfs.sh $(ARGS) - -void-kernel: - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" ./scripts/make-void-kernel.sh $(ARGS) - -rootfs-void: - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/make-rootfs-void.sh $(ARGS) - -void-register: build - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" VOID_IMAGE_NAME="$(VOID_IMAGE_NAME)" BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/register-void-image.sh - -void-vm: void-register - "$(abspath $(BANGER_BIN))" vm create --image "$(VOID_IMAGE_NAME)" --name "$(VOID_VM_NAME)" - -verify-void: void-register - BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/verify.sh --image "$(VOID_IMAGE_NAME)" - -alpine-kernel: - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" ALPINE_RELEASE="$(ALPINE_RELEASE)" ./scripts/make-alpine-kernel.sh $(ARGS) - -rootfs-alpine: - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" ALPINE_RELEASE="$(ALPINE_RELEASE)" BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/make-rootfs-alpine.sh $(ARGS) - -alpine-register: build - BANGER_MANUAL_DIR="$(abspath $(BUILD_MANUAL_DIR))" ALPINE_IMAGE_NAME="$(ALPINE_IMAGE_NAME)" BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/register-alpine-image.sh - -alpine-vm: alpine-register - "$(abspath $(BANGER_BIN))" vm create --image "$(ALPINE_IMAGE_NAME)" --name "$(ALPINE_VM_NAME)" - -verify-alpine: alpine-register - BANGER_BIN="$(abspath $(BANGER_BIN))" ./scripts/verify.sh --image "$(ALPINE_IMAGE_NAME)" diff --git a/docs/kernel-catalog.md b/docs/kernel-catalog.md index b84380e..5b8c889 100644 --- a/docs/kernel-catalog.md +++ b/docs/kernel-catalog.md @@ -36,13 +36,8 @@ traversal entries and unsafe symlinks are rejected. **`generic-`** — built from upstream kernel.org sources with Firecracker's official config. All essential drivers (virtio_blk, virtio_net, ext4, vsock) compiled in — no modules, no initramfs. This -is the recommended kernel for OCI-pulled images (Debian, Ubuntu, -Fedora, etc.). Build with `scripts/make-generic-kernel.sh`. - -**`void-` / `alpine-`** — distro-specific kernels -built from Void/Alpine package repos. Include initramfs + modules. -These are for the `make rootfs-void` / `make rootfs-alpine` manual -flows where the initramfs is paired with its matching rootfs. +is the kernel the golden image pairs with and the recommended kernel +for OCI-pulled images. Build with `scripts/make-generic-kernel.sh`. ## Adding or updating an entry @@ -50,8 +45,8 @@ The repo has no CI for kernel publishing yet. Catalog updates are manual and infrequent (kernel version bumps every few weeks at most). ```bash -# 1. Build the kernel locally with the existing helper. -scripts/make-generic-kernel.sh # or: make void-kernel / make alpine-kernel +# 1. Build the kernel locally. +scripts/make-generic-kernel.sh # 2. Import it into the local catalog so the canonical layout exists. banger kernel import generic-6.12 \ @@ -129,26 +124,11 @@ If hosting ever moves, catalog entries can be migrated by reuploading the tarballs and editing the URLs in `catalog.json` — no other code changes required. -## Tech debt: kernel-build scripts +## Tech debt -`scripts/make-void-kernel.sh` and `scripts/make-alpine-kernel.sh` are -procedural bash that fetches and patches per-distro kernel sources. -Each new distro means a new bespoke script. They're "good enough" -because catalog refreshes are infrequent and only the maintainer runs -them, but they are the bottleneck if the catalog ever wants to grow -beyond two distros. - -A future iteration should: - -- Move kernel acquisition into a Go (or at least uniform) tool with a - per-distro plugin/config rather than per-distro scripts. -- Encode kernel config and required modules declaratively so a Debian - or Fedora target is a config addition, not a new script. -- Run unattended in CI once banger goes public — the manual - `scripts/publish-kernel.sh` flow scales until then. - -Until that happens, `make lint-shell` only runs at `--severity=error`. -Tightening to `--severity=warning` would surface real issues in the -legacy build scripts (mostly `sudo cat > file` redirects and -heredoc-quoting concerns); fixing those is a prerequisite to bumping -the lint floor. +- Kernel publishing is manual; there is no CI yet. `scripts/make-generic-kernel.sh` + plus `scripts/publish-kernel.sh` is fine while refreshes are + infrequent and maintainer-only. CI becomes relevant once banger + goes public. +- `make lint-shell` runs at `--severity=error` only. Tightening to + `--severity=warning` is a nice-to-have but low priority. diff --git a/examples/alpine.config.toml b/examples/alpine.config.toml deleted file mode 100644 index c4e1011..0000000 --- a/examples/alpine.config.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Experimental Alpine Linux guest profile for local testing. -# -# Register or promote a complete `alpine` image first, then point the daemon -# at it by name. Firecracker is resolved from PATH by default; set -# `firecracker_bin` only if you need an override. - -default_image_name = "alpine" -# firecracker_bin = "/usr/bin/firecracker" -# ssh_key_path = "/abs/path/to/private/key" diff --git a/examples/void.config.toml b/examples/void.config.toml deleted file mode 100644 index 85375cc..0000000 --- a/examples/void.config.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Experimental Void Linux guest profile for local testing. -# -# Register or promote a complete `void` image first, then point the daemon -# at it by name. Firecracker is resolved from PATH by default; set -# `firecracker_bin` only if you need an override. - -default_image_name = "void" -# firecracker_bin = "/usr/bin/firecracker" -# ssh_key_path = "/abs/path/to/private/key" diff --git a/images/golden/Dockerfile b/images/golden/Dockerfile index c75296c..6b15a77 100644 --- a/images/golden/Dockerfile +++ b/images/golden/Dockerfile @@ -75,7 +75,7 @@ RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ > /etc/profile.d/mise.sh \ && chmod 0644 /etc/profile.d/mise.sh -# Git default branch — matches the old customize.sh opinion. +# Default branch for any git init inside the sandbox. RUN git config --system init.defaultBranch main # `fd-find` installs as `fdfind` on Debian to avoid a long-standing name diff --git a/internal/cli/banger.go b/internal/cli/banger.go index 2852819..8545c88 100644 --- a/internal/cli/banger.go +++ b/internal/cli/banger.go @@ -30,7 +30,6 @@ import ( "banger/internal/guest" "banger/internal/hostnat" "banger/internal/imagecat" - "banger/internal/imagepreset" "banger/internal/imagepull" "banger/internal/model" "banger/internal/paths" @@ -219,7 +218,6 @@ func newInternalCommand() *cobra.Command { newInternalSSHKeyPathCommand(), newInternalFirecrackerPathCommand(), newInternalVSockAgentPathCommand(), - newInternalPackagesCommand(), newInternalMakeBundleCommand(), ) return cmd @@ -284,39 +282,6 @@ func newInternalVSockAgentPathCommand() *cobra.Command { } } -func newInternalPackagesCommand() *cobra.Command { - var docker bool - cmd := &cobra.Command{ - Use: "packages ", - Hidden: true, - Args: exactArgsUsage(1, "usage: banger internal packages [--docker]"), - RunE: func(cmd *cobra.Command, args []string) error { - var packages []string - switch strings.TrimSpace(args[0]) { - case "debian": - packages = imagepreset.DebianBasePackages() - if docker { - packages = append(packages, "docker.io") - } - case "void": - packages = imagepreset.VoidBasePackages() - case "alpine": - packages = imagepreset.AlpineBasePackages() - default: - return fmt.Errorf("unknown package preset %q", args[0]) - } - for _, pkg := range packages { - if _, err := fmt.Fprintln(cmd.OutOrStdout(), pkg); err != nil { - return err - } - } - return nil - }, - } - cmd.Flags().BoolVar(&docker, "docker", false, "include docker-specific additions") - return cmd -} - func newInternalMakeBundleCommand() *cobra.Command { var ( rootfsTarPath string diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index ae5b0f3..b8c4bb2 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -190,24 +190,6 @@ func TestInternalNATFlagsExist(t *testing.T) { } } -func TestInternalPackagesCommandSupportsAlpine(t *testing.T) { - cmd := NewBangerCommand() - var stdout bytes.Buffer - cmd.SetOut(&stdout) - cmd.SetArgs([]string{"internal", "packages", "alpine"}) - - if err := cmd.Execute(); err != nil { - t.Fatalf("Execute(): %v", err) - } - - output := stdout.String() - for _, want := range []string{"alpine-base", "docker", "libgcc", "libstdc++", "mkinitfs", "openssh"} { - if !strings.Contains(output, want+"\n") { - t.Fatalf("output = %q, want package %q", output, want) - } - } -} - func TestPSAndVMListAliasesAndFlagsExist(t *testing.T) { root := NewBangerCommand() ps, _, err := root.Find([]string{"ps"}) diff --git a/internal/daemon/imagemgr/paths.go b/internal/daemon/imagemgr/paths.go index a0f826d..5916381 100644 --- a/internal/daemon/imagemgr/paths.go +++ b/internal/daemon/imagemgr/paths.go @@ -8,14 +8,44 @@ package imagemgr import ( "context" + "crypto/sha256" + "fmt" "os" "path/filepath" "strings" - "banger/internal/imagepreset" "banger/internal/system" ) +// debianBasePackages is the apt package list applied by +// `image build --from-image` to Debian-based managed rootfses. Small +// curated set: most of the developer tooling the golden image ships +// lives in the Dockerfile, not here. +var debianBasePackages = []string{ + "make", + "git", + "less", + "tree", + "ca-certificates", + "curl", + "wget", + "iproute2", + "vim", + "tmux", +} + +// DebianBasePackages returns a copy of the base package set. +func DebianBasePackages() []string { + return append([]string(nil), debianBasePackages...) +} + +// hashPackages returns the hex sha256 of the package list, used as +// drift-detection metadata alongside a built rootfs. +func hashPackages(lines []string) string { + sum := sha256.Sum256([]byte(strings.Join(lines, "\n") + "\n")) + return fmt.Sprintf("%x", sum) +} + // ValidateRegisterPaths checks that rootfs + kernel exist and that optional // artifacts, when provided, also exist. func ValidateRegisterPaths(rootfsPath, workSeedPath, kernelPath, initrdPath, modulesDir string) error { @@ -102,7 +132,7 @@ func StageOptionalArtifactPath(artifactDir, stagedPath, name string) string { // managed image build. The #feature:docker sentinel is appended when // docker is requested. func BuildMetadataPackages(docker bool) []string { - packages := imagepreset.DebianBasePackages() + packages := DebianBasePackages() if docker { packages = append(packages, "#feature:docker") } @@ -116,5 +146,5 @@ func WritePackagesMetadata(rootfsPath string, packages []string) error { return nil } metadataPath := rootfsPath + ".packages.sha256" - return os.WriteFile(metadataPath, []byte(imagepreset.Hash(packages)+"\n"), 0o644) + return os.WriteFile(metadataPath, []byte(hashPackages(packages)+"\n"), 0o644) } diff --git a/internal/daemon/images.go b/internal/daemon/images.go index f667fed..293aa1f 100644 --- a/internal/daemon/images.go +++ b/internal/daemon/images.go @@ -11,7 +11,6 @@ import ( "banger/internal/api" "banger/internal/daemon/imagemgr" - "banger/internal/imagepreset" "banger/internal/kernelcat" "banger/internal/model" "banger/internal/system" @@ -86,7 +85,7 @@ func (d *Daemon) BuildImage(ctx context.Context, params api.ImageBuildParams) (i if err != nil { return model.Image{}, err } - packages := imagepreset.DebianBasePackages() + packages := imagemgr.DebianBasePackages() metadataPackages := imagemgr.BuildMetadataPackages(params.Docker) spec := imageBuildSpec{ ID: id, diff --git a/internal/imagepreset/preset.go b/internal/imagepreset/preset.go deleted file mode 100644 index 3f60ba7..0000000 --- a/internal/imagepreset/preset.go +++ /dev/null @@ -1,86 +0,0 @@ -package imagepreset - -import ( - "crypto/sha256" - "fmt" - "strings" -) - -var debianBase = []string{ - "make", - "git", - "less", - "tree", - "ca-certificates", - "curl", - "wget", - "iproute2", - "vim", - "tmux", -} - -var voidBase = []string{ - "base-minimal", - "base-devel", - "bash", - "ca-certificates", - "curl", - "docker", - "docker-compose", - "e2fsprogs", - "git", - "iproute2", - "less", - "make", - "openssh", - "procps-ng", - "runit", - "shadow", - "sudo", - "tmux", - "tree", - "vim", - "wget", -} - -var alpineBase = []string{ - "alpine-base", - "bash", - "ca-certificates", - "curl", - "docker", - "docker-cli-compose", - "e2fsprogs", - "git", - "iproute2", - "less", - "libgcc", - "libstdc++", - "make", - "mkinitfs", - "openssh", - "procps-ng", - "shadow", - "sudo", - "tmux", - "tree", - "vim", - "wget", -} - -func DebianBasePackages() []string { - return append([]string(nil), debianBase...) -} - -func VoidBasePackages() []string { - return append([]string(nil), voidBase...) -} - -func AlpineBasePackages() []string { - return append([]string(nil), alpineBase...) -} - -func Hash(lines []string) string { - sum := sha256.Sum256([]byte(strings.Join(lines, "\n") + "\n")) - return fmt.Sprintf("%x", sum) -} diff --git a/internal/kernelcat/catalog.json b/internal/kernelcat/catalog.json index 6cbaf18..dea4cd1 100644 --- a/internal/kernelcat/catalog.json +++ b/internal/kernelcat/catalog.json @@ -10,16 +10,6 @@ "tarball_sha256": "d6f9ba2a957260063241cf9d79ae538d0c349107d37f0bfccc33281d29bd0901", "size_bytes": 9098722, "description": "Generic Firecracker kernel 6.12.8 (all drivers built-in, no initrd needed)" - }, - { - "name": "void-6.12", - "distro": "void", - "arch": "x86_64", - "kernel_version": "6.12.81_1", - "tarball_url": "https://kernels.thaloco.com/void-6.12-x86_64.tar.zst", - "tarball_sha256": "3de6d03c4a3b5d3b8164f20049ddcb38b32a1864ea7133f01ff7fbb56c34d428", - "size_bytes": 187734807, - "description": "Void Linux 6.12 kernel for Firecracker microVMs" } ] } diff --git a/internal/kernelcat/import.go b/internal/kernelcat/import.go index c6cea0e..92fc970 100644 --- a/internal/kernelcat/import.go +++ b/internal/kernelcat/import.go @@ -18,8 +18,9 @@ type DiscoveredArtifacts struct { ModulesDir string } -// metadataFile is the JSON dropped by scripts/make-void-kernel.sh alongside -// its staged output. We read it when present to avoid guessing at filenames. +// metadataFile is the optional JSON a kernel-build script can drop +// alongside its staged output to point ReadLocal at specific filenames +// without guessing. type metadataFile struct { KernelPath string `json:"kernel_path"` InitrdPath string `json:"initrd_path"` diff --git a/scripts/customize.sh b/scripts/customize.sh deleted file mode 100755 index 23d6d50..0000000 --- a/scripts/customize.sh +++ /dev/null @@ -1,597 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[customize] %s\n' "$*" -} - -usage() { - cat <<'EOF' -Usage: ./scripts/customize.sh [--out ] [--size ] [--kernel ] [--initrd ] [--docker] [--modules ] - -Creates a copy of rootfs.ext4, optionally resizes it, boots a VM using the -copy as a writable rootfs, then applies base configuration and packages. -EOF -} - -parse_size() { - local raw="$1" - if [[ "$raw" =~ ^([0-9]+)([KMG])?$ ]]; then - local num="${BASH_REMATCH[1]}" - local unit="${BASH_REMATCH[2]}" - case "$unit" in - K) echo $((num * 1024)) ;; - M|"") echo $((num * 1024 * 1024)) ;; - G) echo $((num * 1024 * 1024 * 1024)) ;; - esac - return 0 - fi - return 1 -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -STATE="${BANGER_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/banger/image-build}" -VM_ROOT="$STATE/vms" -mkdir -p "$VM_ROOT" - -BR_DEV="br-fc" -BR_IP="172.16.0.1" -CIDR="24" -DNS_SERVER="1.1.1.1" - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; install/build banger or set BANGER_BIN" - exit 1 -} - -BANGER_BIN="$(resolve_banger_bin)" -NAT_ACTIVE=0 -FC_BIN="$("$BANGER_BIN" internal firecracker-path)" -SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)" -VSOCK_AGENT="$("$BANGER_BIN" internal vsock-agent-path)" - -banger_nat() { - local action="$1" - "$BANGER_BIN" internal nat "$action" --guest-ip "$GUEST_IP" --tap "$TAP_DEV" -} - -load_package_preset() { - local preset="$1" - local -n out="$2" - mapfile -t out < <("$BANGER_BIN" internal packages "$preset") - (( ${#out[@]} > 0 )) -} - -write_rootfs_manifest_metadata() { - local rootfs_path="$1" - local manifest_hash="$2" - printf '%s\n' "$manifest_hash" > "${rootfs_path}.packages.sha256" -} - -BASE_ROOTFS="" -OUT_ROOTFS="" -SIZE_SPEC="" -INSTALL_DOCKER=0 -KERNEL="" -INITRD="" -MISE_VERSION="v2025.12.0" -MISE_INSTALL_PATH="/usr/local/bin/mise" -MISE_ACTIVATE_LINE='eval "$(/usr/local/bin/mise activate bash)"' -NODE_TOOL="node@22" -CLAUDE_CODE_TOOL="npm:@anthropic-ai/claude-code" -PI_TOOL="npm:@mariozechner/pi-coding-agent" -TMUX_PLUGIN_DIR="/root/.tmux/plugins" -TMUX_RESURRECT_DIR="/root/.tmux/resurrect" -TMUX_TPM_REPO="https://github.com/tmux-plugins/tpm" -TMUX_RESURRECT_REPO="https://github.com/tmux-plugins/tmux-resurrect" -TMUX_CONTINUUM_REPO="https://github.com/tmux-plugins/tmux-continuum" -TMUX_MANAGED_START="# >>> banger tmux plugins >>>" -TMUX_MANAGED_END="# <<< banger tmux plugins <<<" -MODULES_DIR="" -while [[ $# -gt 0 ]]; do - case "$1" in - --out) - OUT_ROOTFS="${2:-}" - shift 2 - ;; - --size) - SIZE_SPEC="${2:-}" - shift 2 - ;; - --kernel) - KERNEL="${2:-}" - shift 2 - ;; - --initrd) - INITRD="${2:-}" - shift 2 - ;; - --docker) - INSTALL_DOCKER=1 - shift - ;; - --modules) - MODULES_DIR="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - if [[ -z "$BASE_ROOTFS" ]]; then - BASE_ROOTFS="$1" - shift - else - log "unknown option: $1" - usage - exit 1 - fi - ;; - esac -done - -if [[ -z "$BASE_ROOTFS" ]]; then - usage - exit 1 -fi - -if [[ ! -f "$BASE_ROOTFS" ]]; then - log "base rootfs not found: $BASE_ROOTFS" - exit 1 -fi - -if [[ -z "$OUT_ROOTFS" ]]; then - base_dir="$(dirname "$BASE_ROOTFS")" - base_name="$(basename "$BASE_ROOTFS")" - OUT_ROOTFS="${base_dir}/docker-${base_name}" -fi -if [[ "$OUT_ROOTFS" == *.ext4 ]]; then - WORK_SEED="${OUT_ROOTFS%.ext4}.work-seed.ext4" -else - WORK_SEED="${OUT_ROOTFS}.work-seed" -fi -if [[ -z "$KERNEL" ]]; then - log "kernel path is required; pass --kernel" - exit 1 -fi -if [[ ! -f "$KERNEL" ]]; then - log "kernel not found: $KERNEL" - exit 1 -fi -if [[ -n "$INITRD" && ! -f "$INITRD" ]]; then - log "initrd not found: $INITRD" - exit 1 -fi -if [[ -n "$MODULES_DIR" && ! -d "$MODULES_DIR" ]]; then - log "modules dir not found: $MODULES_DIR" - exit 1 -fi - -if [[ -e "$OUT_ROOTFS" ]]; then - log "output rootfs already exists: $OUT_ROOTFS" - exit 1 -fi - -if ! command -v resize2fs >/dev/null 2>&1; then - log "resize2fs required" - exit 1 -fi -if ! command -v jq >/dev/null 2>&1; then - log "jq required" - exit 1 -fi -if ! command -v sha256sum >/dev/null 2>&1; then - log "sha256sum required to record package preset metadata" - exit 1 -fi -if [[ ! -x "$VSOCK_AGENT" ]]; then - log "vsock agent not found or not executable: $VSOCK_AGENT" - log "run 'make build'" - exit 1 -fi - -APT_PACKAGES=() -if ! load_package_preset debian APT_PACKAGES; then - log "debian package preset is empty" - exit 1 -fi -if ! PACKAGES_HASH="$(printf '%s\n' "${APT_PACKAGES[@]}" | sha256sum | awk '{print $1}')"; then - log "failed to hash package preset" - exit 1 -fi -printf -v APT_PACKAGES_ESCAPED '%q ' "${APT_PACKAGES[@]}" - -log "copying base rootfs to $OUT_ROOTFS" -cp --reflink=auto "$BASE_ROOTFS" "$OUT_ROOTFS" - -if [[ -n "$SIZE_SPEC" ]]; then - SIZE_BYTES="$(parse_size "$SIZE_SPEC")" - BASE_BYTES="$(stat -c%s "$BASE_ROOTFS")" - if [[ -z "$SIZE_BYTES" || "$SIZE_BYTES" -lt "$BASE_BYTES" ]]; then - log "size must be >= base image size" - exit 1 - fi - log "resizing rootfs to $SIZE_SPEC" - truncate -s "$SIZE_BYTES" "$OUT_ROOTFS" - e2fsck -p -f "$OUT_ROOTFS" >/dev/null - resize2fs "$OUT_ROOTFS" >/dev/null -fi - -VM_ID="$(head -c 32 /dev/urandom | xxd -p -c 256)" -VM_TAG="${VM_ID:0:8}" -VM_NAME="customize-${VM_TAG}" -VM_DIR="$VM_ROOT/$VM_ID" -mkdir -p "$VM_DIR" - -API_SOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/banger/fc-$VM_TAG.sock" -LOG_FILE="$VM_DIR/firecracker.log" -TAP_DEV="tap-fc-$VM_TAG" - -# Allocate guest IP -NEXT_IP_FILE="$STATE/next_ip" -NEXT_IP="$(cat "$NEXT_IP_FILE" 2>/dev/null || echo 2)" -GUEST_IP="172.16.0.$NEXT_IP" -echo "$((NEXT_IP + 1))" > "$NEXT_IP_FILE" - -sudo -v - -cleanup() { - sudo kill "${FC_PID:-}" 2>/dev/null || true - if [[ "$NAT_ACTIVE" -eq 1 ]]; then - banger_nat down >/dev/null 2>&1 || true - fi - sudo ip link del "$TAP_DEV" 2>/dev/null || true - rm -f "$API_SOCK" - rm -rf "$VM_DIR" -} -trap cleanup EXIT - -sudo mkdir -p "$(dirname "$API_SOCK")" -sudo chown "$(id -u):$(id -g)" "$(dirname "$API_SOCK")" - -# Host bridge -if ! ip link show "$BR_DEV" >/dev/null 2>&1; then - log "creating host bridge $BR_DEV ($BR_IP/$CIDR)" - sudo ip link add name "$BR_DEV" type bridge - sudo ip addr add "${BR_IP}/${CIDR}" dev "$BR_DEV" - sudo ip link set "$BR_DEV" up -else - sudo ip link set "$BR_DEV" up -fi - -log "creating tap device $TAP_DEV" -TAP_USER="${SUDO_UID:-$(id -u)}" -TAP_GROUP="${SUDO_GID:-$(id -g)}" -sudo ip tuntap add dev "$TAP_DEV" mode tap user "$TAP_USER" group "$TAP_GROUP" -sudo ip link set "$TAP_DEV" master "$BR_DEV" -sudo ip link set "$TAP_DEV" up -sudo ip link set "$BR_DEV" up - -log "starting firecracker process" -rm -f "$API_SOCK" -nohup sudo -E "$FC_BIN" --api-sock "$API_SOCK" >"$LOG_FILE" 2>&1 & -FC_PID="$!" - -log "waiting for firecracker api socket" -for _ in $(seq 1 200); do - [[ -S "$API_SOCK" ]] && break - sleep 0.02 -done -[[ -S "$API_SOCK" ]] || { log "firecracker api socket not ready"; exit 1; } - -log "configuring machine" -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/machine-config \ - -H "Content-Type: application/json" \ - -d '{ - "vcpu_count": 2, - "mem_size_mib": 1024, - "smt": false - }' >/dev/null - -KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rootfstype=ext4 rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0:${VM_NAME}:eth0:off:${DNS_SERVER} hostname=${VM_NAME} systemd.mask=home.mount systemd.mask=var.mount" - -INITRD_JSON="" -if [[ -n "$INITRD" ]]; then - INITRD_JSON=", \"initrd_path\": \"$INITRD\"" -fi - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \ - -H "Content-Type: application/json" \ - -d "{ - \"kernel_image_path\": \"$KERNEL\", - \"boot_args\": \"$KCMD\"${INITRD_JSON} - }" >/dev/null - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/drives/rootfs \ - -H "Content-Type: application/json" \ - -d "{ - \"drive_id\": \"rootfs\", - \"path_on_host\": \"$OUT_ROOTFS\", - \"is_root_device\": true, - \"is_read_only\": false - }" >/dev/null - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/network-interfaces/eth0 \ - -H "Content-Type: application/json" \ - -d "{ - \"iface_id\": \"eth0\", - \"host_dev_name\": \"$TAP_DEV\" - }" >/dev/null - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/actions \ - -H "Content-Type: application/json" \ - -d '{ "action_type": "InstanceStart" }' >/dev/null - -SUDO_CHILD_PID="$(pgrep -n -f "$API_SOCK" || true)" -if [[ -n "$SUDO_CHILD_PID" ]]; then - FC_PID="$SUDO_CHILD_PID" -fi - -VM_CONFIG_JSON="$(sudo -E curl --unix-socket "$API_SOCK" -sS http://localhost/vm/config)" -CREATED_AT="$(date -Iseconds)" -jq -n \ - --arg id "$VM_ID" \ - --arg name "$VM_NAME" \ - --arg pid "$FC_PID" \ - --arg created_at "$CREATED_AT" \ - --arg guest_ip "$GUEST_IP" \ - --arg tap "$TAP_DEV" \ - --arg api_sock "$API_SOCK" \ - --arg log "$LOG_FILE" \ - --arg rootfs "$OUT_ROOTFS" \ - --arg kernel "$KERNEL" \ - --argjson config "$VM_CONFIG_JSON" \ - '{meta:{id:$id,name:$name,pid:$pid,created_at:$created_at,guest_ip:$guest_ip,tap:$tap,api_sock:$api_sock,log:$log,rootfs:$rootfs,kernel:$kernel},config:$config}' \ - > "$VM_DIR/vm.json" - -log "enabling NAT for customization" -banger_nat up >/dev/null -NAT_ACTIVE=1 - -log "waiting for SSH" -SSH_READY=0 -for _ in $(seq 1 60); do - if ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "root@${GUEST_IP}" "true" >/dev/null 2>&1; then - SSH_READY=1 - break - fi - sleep 1 -done -if [[ "$SSH_READY" -ne 1 ]]; then - log "ssh did not become ready on $GUEST_IP" - exit 1 -fi - -log "configuring guest" -log "installing vsock agent" -scp -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "$VSOCK_AGENT" "root@${GUEST_IP}:/usr/local/bin/banger-vsock-agent" >/dev/null - -ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "root@${GUEST_IP}" bash -lc "set -e -printf 'nameserver %s\n' \"$DNS_SERVER\" > /etc/resolv.conf -echo \"$VM_NAME\" > /etc/hostname -printf '127.0.0.1 localhost\n127.0.1.1 %s\n' \"$VM_NAME\" > /etc/hosts -touch /etc/fstab -sed -i '\|^/dev/vdb[[:space:]]\+/home[[:space:]]|d; \|^/dev/vdc[[:space:]]\+/var[[:space:]]|d' /etc/fstab -if ! grep -q '^tmpfs /run ' /etc/fstab; then - echo 'tmpfs /run tmpfs defaults,nodev,nosuid,mode=0755 0 0' >> /etc/fstab -fi -if ! grep -q '^tmpfs /tmp ' /etc/fstab; then - echo 'tmpfs /tmp tmpfs defaults,nodev,nosuid,mode=1777 0 0' >> /etc/fstab -fi -apt-get update -DEBIAN_FRONTEND=noninteractive apt-get -y upgrade -DEBIAN_FRONTEND=noninteractive apt-get -y install ${APT_PACKAGES_ESCAPED} -curl -fsSL https://mise.run | MISE_INSTALL_PATH="$MISE_INSTALL_PATH" MISE_VERSION="$MISE_VERSION" sh -"$MISE_INSTALL_PATH" use -g "$NODE_TOOL" -"$MISE_INSTALL_PATH" use -g github:anomalyco/opencode -"$MISE_INSTALL_PATH" use -g "$CLAUDE_CODE_TOOL" -"$MISE_INSTALL_PATH" use -g "$PI_TOOL" -"$MISE_INSTALL_PATH" reshim -if [[ ! -e /root/.local/share/mise/shims/node ]]; then - echo 'node shim not found after mise install' >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/npm ]]; then - echo 'npm shim not found after mise install' >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/opencode ]]; then - echo 'opencode shim not found after mise install' >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/claude ]]; then - echo 'claude shim not found after mise install' >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/pi ]]; then - echo 'pi shim not found after mise install' >&2 - exit 1 -fi -ln -snf /root/.local/share/mise/shims/node /usr/local/bin/node -ln -snf /root/.local/share/mise/shims/npm /usr/local/bin/npm -ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode -ln -snf /root/.local/share/mise/shims/claude /usr/local/bin/claude -ln -snf /root/.local/share/mise/shims/pi /usr/local/bin/pi -mkdir -p /etc/profile.d -cat > /etc/profile.d/mise.sh <<'MISEPROFILE' -if [ -n \"\${BASH_VERSION:-}\" ] && [ -x \"$MISE_INSTALL_PATH\" ]; then - eval \"\$($MISE_INSTALL_PATH activate bash)\" -fi -MISEPROFILE -chmod 0644 /etc/profile.d/mise.sh -touch /etc/bash.bashrc -if ! grep -Fqx '$MISE_ACTIVATE_LINE' /etc/bash.bashrc; then - printf '\n%s\n' '$MISE_ACTIVATE_LINE' >> /etc/bash.bashrc -fi -if [[ \"$INSTALL_DOCKER\" == \"1\" ]]; then - DEBIAN_FRONTEND=noninteractive apt-get -y remove containerd || true - if ! DEBIAN_FRONTEND=noninteractive apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then - DEBIAN_FRONTEND=noninteractive apt-get -y install docker.io - fi - if command -v systemctl >/dev/null 2>&1; then - systemctl enable --now docker || true - fi -fi -rm -f /root/get-docker /root/get-docker.sh /tmp/get-docker /tmp/get-docker.sh -chmod 0755 /usr/local/bin/banger-vsock-agent -mkdir -p /etc/modules-load.d /etc/systemd/system -cat > /etc/systemd/system/banger-opencode.service <<'EOF' -[Unit] -Description=Banger opencode server -After=network.target -RequiresMountsFor=/root - -[Service] -Type=simple -Environment=HOME=/root -WorkingDirectory=/root -ExecStart=/usr/local/bin/opencode serve --hostname 0.0.0.0 --port 4096 -Restart=on-failure -RestartSec=1 - -[Install] -WantedBy=multi-user.target -EOF -chmod 0644 /etc/systemd/system/banger-opencode.service -if command -v systemctl >/dev/null 2>&1; then - systemctl daemon-reload || true - systemctl enable --now banger-opencode.service || true -fi -cat > /etc/modules-load.d/banger-vsock.conf <<'EOF' -vsock -vmw_vsock_virtio_transport -EOF -chmod 0644 /etc/modules-load.d/banger-vsock.conf -cat > /etc/systemd/system/banger-vsock-agent.service <<'EOF' -[Unit] -Description=Banger vsock agent -After=network.target - -[Service] -Type=simple -ExecStart=/usr/local/bin/banger-vsock-agent -Restart=on-failure -RestartSec=1 - -[Install] -WantedBy=multi-user.target -EOF -chmod 0644 /etc/systemd/system/banger-vsock-agent.service -if command -v systemctl >/dev/null 2>&1; then - systemctl daemon-reload || true - systemctl enable --now banger-vsock-agent.service || true -fi -git config --system init.defaultBranch main -" - -log "configuring tmux resurrect" -ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "root@${GUEST_IP}" bash -se < "\$tmp_tmux_conf" -else - : > "\$tmp_tmux_conf" -fi -if [[ -s "\$tmp_tmux_conf" ]]; then - printf '\n' >> "\$tmp_tmux_conf" -fi -cat >> "\$tmp_tmux_conf" <<'TMUXCONF' -$TMUX_MANAGED_START -set -g @plugin 'tmux-plugins/tpm' -set -g @plugin 'tmux-plugins/tmux-resurrect' -set -g @plugin 'tmux-plugins/tmux-continuum' -set -g @continuum-save-interval '15' -set -g @continuum-restore 'off' -set -g @resurrect-dir '/root/.tmux/resurrect' -run '~/.tmux/plugins/tpm/tpm' -$TMUX_MANAGED_END -TMUXCONF -mv "\$tmp_tmux_conf" "\$TMUX_CONF" -chmod 0644 "\$TMUX_CONF" -EOF - -if [[ -n "$MODULES_DIR" ]]; then - MODULES_BASE="$(basename "$MODULES_DIR")" - log "copying kernel modules ($MODULES_BASE) into guest" - tar -C "$(dirname "$MODULES_DIR")" -cf - "$MODULES_BASE" | \ - ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "root@${GUEST_IP}" bash -lc "set -e -mkdir -p /lib/modules -tar -C /lib/modules -xf - -depmod -a \"$MODULES_BASE\" - mkdir -p /etc/modules-load.d - printf 'nf_tables\nnft_chain_nat\nveth\nbr_netfilter\noverlay\n' > /etc/modules-load.d/docker-netfilter.conf - mkdir -p /etc/sysctl.d - cat > /etc/sysctl.d/99-docker.conf <<'SYSCTL' -net.bridge.bridge-nf-call-iptables = 1 -net.bridge.bridge-nf-call-ip6tables = 1 -net.ipv4.ip_forward = 1 -SYSCTL - sysctl --system >/dev/null 2>&1 || true -sync -" -fi - -log "shutting down guest" -ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "root@${GUEST_IP}" bash -lc "sync" || true -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/actions \ - -H "Content-Type: application/json" \ - -d '{ "action_type": "SendCtrlAltDel" }' >/dev/null || true -for _ in $(seq 1 200); do - if ! ps -p "$FC_PID" >/dev/null 2>&1; then - break - fi - sleep 0.05 -done -write_rootfs_manifest_metadata "$OUT_ROOTFS" "$PACKAGES_HASH" -log "building work seed $WORK_SEED" -"$BANGER_BIN" internal work-seed --rootfs "$OUT_ROOTFS" --out "$WORK_SEED" -log "done" diff --git a/scripts/interactive.sh b/scripts/interactive.sh deleted file mode 100755 index deb262b..0000000 --- a/scripts/interactive.sh +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[interactive] %s\n' "$*" -} - -usage() { - cat <<'EOF' -Usage: ./scripts/interactive.sh --kernel [--initrd ] [--size ] - -Creates a writable copy of the base rootfs and boots a VM so you can -customize it manually over SSH. No automatic package/config changes -are applied. -EOF -} - -parse_size() { - local raw="$1" - if [[ "$raw" =~ ^([0-9]+)([KMG])?$ ]]; then - local num="${BASH_REMATCH[1]}" - local unit="${BASH_REMATCH[2]}" - case "$unit" in - K) echo $((num * 1024)) ;; - M|"") echo $((num * 1024 * 1024)) ;; - G) echo $((num * 1024 * 1024 * 1024)) ;; - esac - return 0 - fi - return 1 -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -STATE="${BANGER_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/banger/interactive}" -VM_ROOT="$STATE/vms" -mkdir -p "$VM_ROOT" - -BR_DEV="br-fc" -BR_IP="172.16.0.1" -CIDR="24" -DNS_SERVER="1.1.1.1" - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; install/build banger or set BANGER_BIN" - exit 1 -} - -BANGER_BIN="$(resolve_banger_bin)" -NAT_ACTIVE=0 -FC_BIN="$("$BANGER_BIN" internal firecracker-path)" -SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)" -KERNEL="" -INITRD="" - -banger_nat() { - local action="$1" - "$BANGER_BIN" internal nat "$action" --guest-ip "$GUEST_IP" --tap "$TAP_DEV" -} - -BASE_ROOTFS="" -OUT_ROOTFS="" -SIZE_SPEC="" -while [[ $# -gt 0 ]]; do - case "$1" in - --out) - OUT_ROOTFS="${2:-}" - shift 2 - ;; - --size) - SIZE_SPEC="${2:-}" - shift 2 - ;; - --kernel) - KERNEL="${2:-}" - shift 2 - ;; - --initrd) - INITRD="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - if [[ -z "$BASE_ROOTFS" ]]; then - BASE_ROOTFS="$1" - shift - else - log "unknown option: $1" - usage - exit 1 - fi - ;; - esac -done - -if [[ -z "$BASE_ROOTFS" ]]; then - usage - exit 1 -fi -if [[ ! -f "$BASE_ROOTFS" ]]; then - log "base rootfs not found: $BASE_ROOTFS" - exit 1 -fi -if [[ -z "$KERNEL" ]]; then - log "kernel path is required; pass --kernel" - exit 1 -fi -if [[ ! -f "$KERNEL" ]]; then - log "kernel not found: $KERNEL" - exit 1 -fi -if [[ -n "$INITRD" && ! -f "$INITRD" ]]; then - log "initrd not found: $INITRD" - exit 1 -fi - -if [[ -z "$OUT_ROOTFS" ]]; then - base_dir="$(dirname "$BASE_ROOTFS")" - base_name="$(basename "$BASE_ROOTFS")" - OUT_ROOTFS="${base_dir}/rw-${base_name}" -fi -if [[ -e "$OUT_ROOTFS" ]]; then - log "output rootfs already exists: $OUT_ROOTFS" - exit 1 -fi - -log "copying base rootfs to $OUT_ROOTFS" -cp --reflink=auto "$BASE_ROOTFS" "$OUT_ROOTFS" - -if [[ -n "$SIZE_SPEC" ]]; then - SIZE_BYTES="$(parse_size "$SIZE_SPEC")" - BASE_BYTES="$(stat -c%s "$BASE_ROOTFS")" - if [[ -z "$SIZE_BYTES" || "$SIZE_BYTES" -lt "$BASE_BYTES" ]]; then - log "size must be >= base image size" - exit 1 - fi - log "resizing rootfs to $SIZE_SPEC" - truncate -s "$SIZE_BYTES" "$OUT_ROOTFS" - e2fsck -p -f "$OUT_ROOTFS" >/dev/null - resize2fs "$OUT_ROOTFS" >/dev/null -fi - -VM_ID="$(head -c 32 /dev/urandom | xxd -p -c 256)" -VM_TAG="${VM_ID:0:8}" -VM_NAME="interactive-${VM_TAG}" -VM_DIR="$VM_ROOT/$VM_ID" -mkdir -p "$VM_DIR" - -API_SOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/banger/fc-$VM_TAG.sock" -LOG_FILE="$VM_DIR/firecracker.log" -TAP_DEV="tap-fc-$VM_TAG" - -# Allocate guest IP -NEXT_IP_FILE="$STATE/next_ip" -NEXT_IP="$(cat "$NEXT_IP_FILE" 2>/dev/null || echo 2)" -GUEST_IP="172.16.0.$NEXT_IP" -echo "$((NEXT_IP + 1))" > "$NEXT_IP_FILE" - -sudo -v - -cleanup() { - sudo kill "${FC_PID:-}" 2>/dev/null || true - if [[ "$NAT_ACTIVE" -eq 1 ]]; then - banger_nat down >/dev/null 2>&1 || true - fi - sudo ip link del "$TAP_DEV" 2>/dev/null || true - rm -f "$API_SOCK" - rm -rf "$VM_DIR" -} -trap cleanup EXIT - -sudo mkdir -p "$(dirname "$API_SOCK")" -sudo chown "$(id -u):$(id -g)" "$(dirname "$API_SOCK")" - -# Host bridge -if ! ip link show "$BR_DEV" >/dev/null 2>&1; then - log "creating host bridge $BR_DEV ($BR_IP/$CIDR)" - sudo ip link add name "$BR_DEV" type bridge - sudo ip addr add "${BR_IP}/${CIDR}" dev "$BR_DEV" - sudo ip link set "$BR_DEV" up -else - sudo ip link set "$BR_DEV" up -fi - -log "creating tap device $TAP_DEV" -TAP_USER="${SUDO_UID:-$(id -u)}" -TAP_GROUP="${SUDO_GID:-$(id -g)}" -sudo ip tuntap add dev "$TAP_DEV" mode tap user "$TAP_USER" group "$TAP_GROUP" -sudo ip link set "$TAP_DEV" master "$BR_DEV" -sudo ip link set "$TAP_DEV" up -sudo ip link set "$BR_DEV" up - -log "starting firecracker process" -rm -f "$API_SOCK" -nohup sudo -E "$FC_BIN" --api-sock "$API_SOCK" >"$LOG_FILE" 2>&1 & -FC_PID="$!" - -log "waiting for firecracker api socket" -for _ in $(seq 1 200); do - [[ -S "$API_SOCK" ]] && break - sleep 0.02 -done -[[ -S "$API_SOCK" ]] || { log "firecracker api socket not ready"; exit 1; } - -log "configuring machine" -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/machine-config \ - -H "Content-Type: application/json" \ - -d '{ - "vcpu_count": 2, - "mem_size_mib": 1024, - "smt": false - }' >/dev/null - -KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rootfstype=ext4 rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0:${VM_NAME}:eth0:off:${DNS_SERVER} hostname=${VM_NAME} systemd.mask=home.mount systemd.mask=var.mount" - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \ - -H "Content-Type: application/json" \ - -d "{ - \"kernel_image_path\": \"$KERNEL\", - \"boot_args\": \"$KCMD\", - \"initrd_path\": \"$INITRD\" - }" >/dev/null - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/drives/rootfs \ - -H "Content-Type: application/json" \ - -d "{ - \"drive_id\": \"rootfs\", - \"path_on_host\": \"$OUT_ROOTFS\", - \"is_root_device\": true, - \"is_read_only\": false - }" >/dev/null - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/network-interfaces/eth0 \ - -H "Content-Type: application/json" \ - -d "{ - \"iface_id\": \"eth0\", - \"host_dev_name\": \"$TAP_DEV\" - }" >/dev/null - -sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/actions \ - -H "Content-Type: application/json" \ - -d '{ "action_type": "InstanceStart" }' >/dev/null - -SUDO_CHILD_PID="$(pgrep -n -f "$API_SOCK" || true)" -if [[ -n "$SUDO_CHILD_PID" ]]; then - FC_PID="$SUDO_CHILD_PID" -fi - -VM_CONFIG_JSON="$(sudo -E curl --unix-socket "$API_SOCK" -sS http://localhost/vm/config)" -CREATED_AT="$(date -Iseconds)" -jq -n \ - --arg id "$VM_ID" \ - --arg name "$VM_NAME" \ - --arg pid "$FC_PID" \ - --arg created_at "$CREATED_AT" \ - --arg guest_ip "$GUEST_IP" \ - --arg tap "$TAP_DEV" \ - --arg api_sock "$API_SOCK" \ - --arg log "$LOG_FILE" \ - --arg rootfs "$OUT_ROOTFS" \ - --arg kernel "$KERNEL" \ - --argjson config "$VM_CONFIG_JSON" \ - '{meta:{id:$id,name:$name,pid:$pid,created_at:$created_at,guest_ip:$guest_ip,tap:$tap,api_sock:$api_sock,log:$log,rootfs:$rootfs,kernel:$kernel},config:$config}' \ - > "$VM_DIR/vm.json" - -log "enabling NAT for interactive session" -banger_nat up >/dev/null -NAT_ACTIVE=1 - -log "waiting for SSH" -log "guest ip: $GUEST_IP" -log "ssh: ssh -i \"$SSH_KEY\" root@${GUEST_IP}" -for _ in $(seq 1 60); do - if ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - "root@${GUEST_IP}" "true" >/dev/null 2>&1; then - log "ssh ready" - break - fi - sleep 1 -done - -log "output rootfs: $OUT_ROOTFS" -log "press Ctrl+C to stop and clean up" - -while kill -0 "$FC_PID" >/dev/null 2>&1; do - sleep 1 -done diff --git a/scripts/make-alpine-kernel.sh b/scripts/make-alpine-kernel.sh deleted file mode 100755 index 8bcf2fe..0000000 --- a/scripts/make-alpine-kernel.sh +++ /dev/null @@ -1,363 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[make-alpine-kernel] %s\n' "$*" -} - -usage() { - cat <<'EOF' -Usage: ./scripts/make-alpine-kernel.sh [--out-dir ] [--release ] [--mirror ] [--arch ] [--print-register-flags] - -Download and stage an Alpine Linux virt kernel under ./build/manual/alpine-kernel -for the experimental Alpine guest flow. - -Defaults: - --out-dir ./build/manual/alpine-kernel - --release 3.23.3 - --mirror https://dl-cdn.alpinelinux.org/alpine - --arch x86_64 - -The staged output contains: - boot/vmlinuz- Alpine virt kernel image - boot/initramfs-.img Matching Alpine initramfs - boot/config- Alpine kernel config when present - lib/modules// Matching kernel modules from modloop-virt - -If --print-register-flags is passed, the script does not download anything. It -prints the banger image register flags for an existing staged Alpine kernel. -EOF -} - -require_command() { - local name="$1" - command -v "$name" >/dev/null 2>&1 || { - log "required command not found: $name" - exit 1 - } -} - -check_elf() { - local path="$1" - readelf -h "$path" >/dev/null 2>&1 -} - -find_latest_matching() { - local dir="$1" - local pattern="$2" - if [[ ! -d "$dir" ]]; then - return 1 - fi - find "$dir" -maxdepth 1 -type f -name "$pattern" | sort | tail -n 1 -} - -find_latest_module_dir() { - local root="$1" - local dir="" - if [[ ! -d "$root" ]]; then - return 1 - fi - while IFS= read -r dir; do - if [[ -d "$dir/kernel" || -f "$dir/modules.dep" || -f "$dir/modules.dep.bin" ]]; then - printf '%s\n' "$dir" - return 0 - fi - done < <(find "$root" -mindepth 1 -maxdepth 1 -type d | sort) - return 1 -} - -find_tar_entry() { - local archive="$1" - local needle="$2" - local entry="" - - while IFS= read -r entry; do - case "$entry" in - "$needle"|*/"$needle") - printf '%s\n' "$entry" - return 0 - ;; - esac - done < <(tar -tf "$archive") - - return 1 -} - -find_tar_config_entry() { - local archive="$1" - local entry="" - - while IFS= read -r entry; do - case "$entry" in - config-*-virt|*/config-*-virt) - printf '%s\n' "$entry" - return 0 - ;; - esac - done < <(tar -tf "$archive") - - return 1 -} - -resolve_release_branch() { - local release="$1" - printf 'v%s\n' "${release%.*}" -} - -extract_vmlinux() { - local image="$1" - local out="$2" - local tmp="$TMP_DIR/vmlinux.extract" - - if check_elf "$image"; then - install -m 0644 "$image" "$out" - return 0 - fi - - try_decompress() { - local header="$1" - local marker="$2" - local command="$3" - local pos="" - - while IFS= read -r pos; do - [[ -n "$pos" ]] || continue - pos="${pos%%:*}" - tail -c+"$pos" "$image" | eval "$command" >"$tmp" 2>/dev/null || true - if check_elf "$tmp"; then - install -m 0644 "$tmp" "$out" - return 0 - fi - done < <(tr "$header\n$marker" "\n$marker=" < "$image" | grep -abo "^$marker" || true) - - return 1 - } - - try_decompress '\037\213\010' "xy" "gunzip" && return 0 - try_decompress '\3757zXZ\000' "abcde" "unxz" && return 0 - try_decompress "BZh" "xy" "bunzip2" && return 0 - try_decompress '\135\000\000\000' "xxx" "unlzma" && return 0 - try_decompress '\002!L\030' "xxx" "lz4 -d" && return 0 - try_decompress '(\265/\375' "xxx" "unzstd" && return 0 - - return 1 -} - -print_register_flags() { - local kernel="" - local initrd="" - local modules="" - - kernel="$(find_latest_matching "$OUT_DIR/boot" 'vmlinux-*' || true)" - if [[ -z "$kernel" ]]; then - kernel="$(find_latest_matching "$OUT_DIR/boot" 'vmlinuz-*' || true)" - fi - initrd="$(find_latest_matching "$OUT_DIR/boot" 'initramfs-*' || true)" - modules="$(find_latest_module_dir "$OUT_DIR/lib/modules" || true)" - - if [[ -z "$kernel" || -z "$modules" ]]; then - log "staged Alpine kernel not found under $OUT_DIR" - exit 1 - fi - - printf -- '--kernel %q ' "$kernel" - if [[ -n "$initrd" ]]; then - printf -- '--initrd %q ' "$initrd" - fi - printf -- '--modules %q\n' "$modules" -} - -cleanup() { - if [[ "${MODLOOP_MOUNTED:-0}" == "1" ]] && [[ -n "${MODLOOP_MOUNT:-}" ]]; then - sudo umount "$MODLOOP_MOUNT" || true - fi - if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then - rm -rf "$TMP_DIR" - fi -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -OUT_DIR="$MANUAL_DIR/alpine-kernel" -RELEASE="${ALPINE_RELEASE:-3.23.3}" -MIRROR="https://dl-cdn.alpinelinux.org/alpine" -ARCH="x86_64" -PRINT_REGISTER_FLAGS=0 - -while [[ $# -gt 0 ]]; do - case "$1" in - --out-dir) - OUT_DIR="${2:-}" - shift 2 - ;; - --release) - RELEASE="${2:-}" - shift 2 - ;; - --mirror) - MIRROR="${2:-}" - shift 2 - ;; - --arch) - ARCH="${2:-}" - shift 2 - ;; - --print-register-flags) - PRINT_REGISTER_FLAGS=1 - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - log "unknown option: $1" - usage - exit 1 - ;; - esac -done - -if [[ "$PRINT_REGISTER_FLAGS" == "1" ]]; then - print_register_flags - exit 0 -fi - -if [[ "$ARCH" != "x86_64" ]]; then - log "unsupported arch: $ARCH" - log "this experimental builder currently supports only x86_64" - exit 1 -fi -if [[ -d "$OUT_DIR" ]]; then - log "output directory already exists: $OUT_DIR" - log "remove it first if you want to re-stage a different Alpine kernel" - exit 1 -fi - -require_command curl -require_command tar -require_command sha256sum -require_command install -require_command find -require_command cp -require_command readelf -require_command file -require_command tail -require_command grep -require_command cut -require_command gzip -require_command xz -require_command bzip2 - -if command -v unsquashfs >/dev/null 2>&1; then - USE_UNSQUASHFS=1 -else - USE_UNSQUASHFS=0 - require_command sudo - require_command mount - require_command umount -fi - -TMP_DIR="$(mktemp -d -t banger-alpine-kernel-XXXXXX)" -EXTRACT_DIR="$TMP_DIR/extract" -MODLOOP_DIR="$TMP_DIR/modloop" -MODLOOP_MOUNT="$TMP_DIR/modloop.mount" -ARCHIVE="$TMP_DIR/alpine-netboot.tar.gz" -MODLOOP_MOUNTED=0 -trap cleanup EXIT - -mkdir -p "$EXTRACT_DIR" "$MODLOOP_DIR" "$MODLOOP_MOUNT" - -BRANCH="$(resolve_release_branch "$RELEASE")" -RELEASE_DIR="$MIRROR/$BRANCH/releases/$ARCH" -ARCHIVE_URL="$RELEASE_DIR/alpine-netboot-$RELEASE-$ARCH.tar.gz" -SHA256_URL="$ARCHIVE_URL.sha256" - -log "downloading Alpine netboot bundle from $ARCHIVE_URL" -curl -fsSL "$ARCHIVE_URL" -o "$ARCHIVE" -expected_sha="$(curl -fsSL "$SHA256_URL" | awk '{print $1}')" -actual_sha="$(sha256sum "$ARCHIVE" | awk '{print $1}')" -if [[ -z "$expected_sha" ]]; then - log "failed to read SHA256 from $SHA256_URL" - exit 1 -fi -if [[ "$expected_sha" != "$actual_sha" ]]; then - log "sha256 mismatch for $ARCHIVE_URL" - log "expected: $expected_sha" - log "actual: $actual_sha" - exit 1 -fi - -VMLINUX_ENTRY="$(find_tar_entry "$ARCHIVE" 'vmlinuz-virt' || true)" -INITRD_ENTRY="$(find_tar_entry "$ARCHIVE" 'initramfs-virt' || true)" -MODLOOP_ENTRY="$(find_tar_entry "$ARCHIVE" 'modloop-virt' || true)" -CONFIG_ENTRY="$(find_tar_config_entry "$ARCHIVE" || true)" - -if [[ -z "$VMLINUX_ENTRY" || -z "$INITRD_ENTRY" || -z "$MODLOOP_ENTRY" ]]; then - log "Alpine netboot bundle is missing expected virt boot artifacts" - exit 1 -fi - -log "extracting Alpine virt boot artifacts" -tar_args=("$VMLINUX_ENTRY" "$INITRD_ENTRY" "$MODLOOP_ENTRY") -if [[ -n "$CONFIG_ENTRY" ]]; then - tar_args+=("$CONFIG_ENTRY") -fi -tar -xf "$ARCHIVE" -C "$EXTRACT_DIR" "${tar_args[@]}" - -VMLINUX_SRC="$EXTRACT_DIR/$VMLINUX_ENTRY" -INITRD_SRC="$EXTRACT_DIR/$INITRD_ENTRY" -MODLOOP_SRC="$EXTRACT_DIR/$MODLOOP_ENTRY" -CONFIG_SRC="" -if [[ -n "$CONFIG_ENTRY" ]]; then - CONFIG_SRC="$EXTRACT_DIR/$CONFIG_ENTRY" -fi - -if [[ "$USE_UNSQUASHFS" == "1" ]]; then - log "extracting kernel modules with unsquashfs" - unsquashfs -f -d "$MODLOOP_DIR" "$MODLOOP_SRC" >/dev/null -else - log "extracting kernel modules with a read-only loop mount" - sudo mount -o loop,ro "$MODLOOP_SRC" "$MODLOOP_MOUNT" - MODLOOP_MOUNTED=1 - cp -a "$MODLOOP_MOUNT/." "$MODLOOP_DIR/" - sudo umount "$MODLOOP_MOUNT" - MODLOOP_MOUNTED=0 -fi - -MODULES_ROOT="" -if [[ -d "$MODLOOP_DIR/modules" ]]; then - MODULES_ROOT="$MODLOOP_DIR/modules" -elif [[ -d "$MODLOOP_DIR/lib/modules" ]]; then - MODULES_ROOT="$MODLOOP_DIR/lib/modules" -fi -if [[ -z "$MODULES_ROOT" ]]; then - log "extracted modloop is missing a modules directory" - exit 1 -fi - -MODULES_SRC="$(find_latest_module_dir "$MODULES_ROOT" || true)" -if [[ -z "$MODULES_SRC" ]]; then - log "failed to locate a kernel modules tree inside modloop-virt" - exit 1 -fi - -KERNEL_VERSION="$(basename "$MODULES_SRC")" -mkdir -p "$OUT_DIR/boot" "$OUT_DIR/lib/modules" -install -m 0644 "$VMLINUX_SRC" "$OUT_DIR/boot/vmlinuz-$KERNEL_VERSION" -install -m 0644 "$INITRD_SRC" "$OUT_DIR/boot/initramfs-$KERNEL_VERSION.img" -if [[ -n "$CONFIG_SRC" && -f "$CONFIG_SRC" ]]; then - install -m 0644 "$CONFIG_SRC" "$OUT_DIR/boot/config-$KERNEL_VERSION" -fi -cp -a "$MODULES_SRC" "$OUT_DIR/lib/modules/" - -log "extracting Firecracker kernel from vmlinuz-$KERNEL_VERSION" -if ! extract_vmlinux "$VMLINUX_SRC" "$OUT_DIR/boot/vmlinux-$KERNEL_VERSION"; then - log "failed to extract an uncompressed vmlinux from $VMLINUX_SRC" - log "raw kernel image type: $(file -b "$VMLINUX_SRC")" - exit 1 -fi - -log "staged Alpine kernel artifacts in $OUT_DIR" -log "kernel version: $KERNEL_VERSION" diff --git a/scripts/make-rootfs-alpine.sh b/scripts/make-rootfs-alpine.sh deleted file mode 100755 index a09d907..0000000 --- a/scripts/make-rootfs-alpine.sh +++ /dev/null @@ -1,722 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[make-rootfs-alpine] %s\n' "$*" -} - -usage() { - cat <<'EOF' -Usage: ./scripts/make-rootfs-alpine.sh [--out ] [--size ] [--release ] [--mirror ] [--arch ] - -Build an experimental Alpine Linux rootfs image plus a matching /root work-seed. - -Defaults: - --out ./build/manual/rootfs-alpine.ext4 - --size 2G - --release 3.23.3 - --mirror https://dl-cdn.alpinelinux.org/alpine - --arch x86_64 - -This path is experimental and local-only. If ./build/manual/alpine-kernel exists -it uses the staged Alpine kernel modules from that directory. It does not change -the default Debian image flow. -EOF -} - -parse_size() { - local raw="$1" - if [[ "$raw" =~ ^([0-9]+)([KMG])?$ ]]; then - local num="${BASH_REMATCH[1]}" - local unit="${BASH_REMATCH[2]}" - case "$unit" in - K) printf '%s\n' $((num * 1024)) ;; - M|"") printf '%s\n' $((num * 1024 * 1024)) ;; - G) printf '%s\n' $((num * 1024 * 1024 * 1024)) ;; - esac - return 0 - fi - return 1 -} - -require_command() { - local name="$1" - command -v "$name" >/dev/null 2>&1 || { - log "required command not found: $name" - exit 1 - } -} - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; build it first with 'make build' or set BANGER_BIN" - exit 1 -} - -find_latest_module_dir() { - local root="$1" - if [[ ! -d "$root" ]]; then - return 1 - fi - find "$root" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1 -} - -resolve_release_branch() { - local release="$1" - printf 'v%s\n' "${release%.*}" -} - -load_package_preset() { - local preset="$1" - local -n out="$2" - mapfile -t out < <("$BANGER_BIN" internal packages "$preset") - (( ${#out[@]} > 0 )) -} - -write_rootfs_manifest_metadata() { - local rootfs_path="$1" - local manifest_hash="$2" - printf '%s\n' "$manifest_hash" > "${rootfs_path}.packages.sha256" -} - -install_root_authorized_key() { - local public_key - public_key="$(ssh-keygen -y -f "$SSH_KEY")" - sudo mkdir -p "$ROOT_MOUNT/root/.ssh" - printf '%s\n' "$public_key" | sudo tee "$ROOT_MOUNT/root/.ssh/authorized_keys" >/dev/null - sudo chmod 700 "$ROOT_MOUNT/root/.ssh" - sudo chmod 600 "$ROOT_MOUNT/root/.ssh/authorized_keys" -} - -ensure_sshd_include() { - local cfg="$ROOT_MOUNT/etc/ssh/sshd_config" - local tmp_cfg="$TMP_DIR/sshd_config" - local include_line="Include /etc/ssh/sshd_config.d/*.conf" - - sudo mkdir -p "$ROOT_MOUNT/etc/ssh/sshd_config.d" - if sudo test -f "$cfg"; then - sudo cat "$cfg" > "$tmp_cfg" - else - : > "$tmp_cfg" - fi - - if ! grep -Eq '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config\.d/\*\.conf([[:space:]]|$)' "$tmp_cfg"; then - { - printf '%s\n' "$include_line" - cat "$tmp_cfg" - } > "${tmp_cfg}.new" - mv "${tmp_cfg}.new" "$tmp_cfg" - sudo install -m 0644 "$tmp_cfg" "$cfg" - fi -} - -normalize_root_shell() { - local passwd="$ROOT_MOUNT/etc/passwd" - local shells="$ROOT_MOUNT/etc/shells" - local wanted_shell="/bin/bash" - local tmp_passwd="$TMP_DIR/passwd" - local root_shell="" - - if [[ ! -x "$ROOT_MOUNT$wanted_shell" ]]; then - log "required root shell is missing from the Alpine image: $wanted_shell" - exit 1 - fi - if [[ ! -f "$shells" ]]; then - log "Alpine image is missing /etc/shells" - exit 1 - fi - if ! sudo grep -Fxq "$wanted_shell" "$shells"; then - log "Alpine image does not allow $wanted_shell in /etc/shells" - exit 1 - fi - - sudo cat "$passwd" > "$tmp_passwd" - awk -F: -v OFS=: -v shell="$wanted_shell" ' - $1 == "root" { - $7 = shell - found = 1 - } - { print } - END { - if (!found) { - exit 1 - } - } - ' "$tmp_passwd" > "${tmp_passwd}.new" || { - log "failed to rewrite root shell in /etc/passwd" - exit 1 - } - mv "${tmp_passwd}.new" "$tmp_passwd" - sudo install -m 0644 "$tmp_passwd" "$passwd" - - root_shell="$(sudo awk -F: '$1 == "root" { print $7 }' "$passwd")" - if [[ "$root_shell" != "$wanted_shell" ]]; then - log "root shell normalization failed: expected $wanted_shell, got ${root_shell:-}" - exit 1 - fi -} - -configure_root_bash_prompt() { - local bashrc="$ROOT_MOUNT/root/.bashrc" - local bash_profile="$ROOT_MOUNT/root/.bash_profile" - local profile_prompt="$ROOT_MOUNT/etc/profile.d/banger-bash-prompt.sh" - - sudo mkdir -p "$ROOT_MOUNT/root" "$ROOT_MOUNT/etc/profile.d" - cat <<'EOF' | sudo tee "$bashrc" >/dev/null -# banger: default interactive prompt for experimental Alpine guests -case "$-" in - *i*) ;; - *) return ;; -esac - -if [ -z "${BANGER_MISE_ACTIVATED:-}" ] && [ -x '/usr/local/bin/mise' ]; then - export BANGER_MISE_ACTIVATED=1 - eval "$(/usr/local/bin/mise activate bash)" -fi - -PS1='\u@\h:\w\$ ' -EOF - cat <<'EOF' | sudo tee "$bash_profile" >/dev/null -if [ -f ~/.bashrc ]; then - . ~/.bashrc -fi -EOF - cat <<'EOF' | sudo tee "$profile_prompt" >/dev/null -case "$-" in - *i*) ;; - *) return 0 2>/dev/null || exit 0 ;; -esac - -if [ -n "${BASH_VERSION:-}" ]; then - PS1='\u@\h:\w\$ ' -fi -EOF - sudo chmod 0644 "$bashrc" "$bash_profile" "$profile_prompt" -} - -install_guest_network_bootstrap() { - sudo mkdir -p "$ROOT_MOUNT/usr/local/libexec" - sudo install -m 0755 "$GUESTNET_BOOTSTRAP_SCRIPT" "$ROOT_MOUNT/usr/local/libexec/banger-network-bootstrap" -} - -install_openrc_services() { - local initd_dir="$ROOT_MOUNT/etc/init.d" - - sudo mkdir -p "$initd_dir" - - cat <<'EOF' | sudo tee "$initd_dir/banger-network" >/dev/null -#!/sbin/openrc-run -description="Banger guest network bootstrap" - -depend() { - need localmount - before sshd docker banger-opencode - provide net -} - -start() { - ebegin "Configuring guest network" - /usr/local/libexec/banger-network-bootstrap - eend $? -} -EOF - - cat <<'EOF' | sudo tee "$initd_dir/banger-docker-preflight" >/dev/null -#!/sbin/openrc-run -description="Banger Docker kernel preflight" - -depend() { - after modules - before docker -} - -start() { - ebegin "Preparing Docker kernel state" - for module in nf_tables nft_chain_nat veth br_netfilter overlay; do - modprobe "$module" 2>/dev/null || true - done - if command -v sysctl >/dev/null 2>&1; then - sysctl -p /etc/sysctl.d/99-docker.conf >/dev/null 2>&1 || true - fi - eend 0 -} -EOF - - cat <<'EOF' | sudo tee "$initd_dir/banger-vsock-agent" >/dev/null -#!/sbin/openrc-run -description="Banger vsock agent" -pidfile="/run/${RC_SVCNAME}.pid" -command="/usr/local/bin/banger-vsock-agent" - -depend() { - need localmount - before banger-network sshd docker banger-opencode -} - -start_pre() { - modprobe vsock 2>/dev/null || true - modprobe vmw_vsock_virtio_transport 2>/dev/null || true -} - -start() { - ebegin "Starting ${RC_SVCNAME}" - start-stop-daemon --start --exec "$command" --background --make-pidfile --pidfile "$pidfile" - eend $? -} - -stop() { - ebegin "Stopping ${RC_SVCNAME}" - start-stop-daemon --stop --exec "$command" --pidfile "$pidfile" - eend $? -} -EOF - - cat <<'EOF' | sudo tee "$initd_dir/banger-opencode" >/dev/null -#!/sbin/openrc-run -description="Banger opencode server" -pidfile="/run/${RC_SVCNAME}.pid" -command="/usr/local/bin/opencode" -command_args="serve --hostname 0.0.0.0 --port 4096" - -depend() { - need localmount - after banger-network -} - -start() { - ebegin "Starting ${RC_SVCNAME}" - HOME=/root start-stop-daemon --start --exec "$command" --background --make-pidfile --pidfile "$pidfile" --chdir /root -- $command_args - eend $? -} - -stop() { - ebegin "Stopping ${RC_SVCNAME}" - start-stop-daemon --stop --exec "$command" --pidfile "$pidfile" - eend $? -} -EOF - - sudo chmod 0755 \ - "$initd_dir/banger-network" \ - "$initd_dir/banger-docker-preflight" \ - "$initd_dir/banger-vsock-agent" \ - "$initd_dir/banger-opencode" -} - -configure_docker_bootstrap() { - local modules_conf="$ROOT_MOUNT/etc/modules-load.d/docker-netfilter.conf" - local sysctl_conf="$ROOT_MOUNT/etc/sysctl.d/99-docker.conf" - - sudo mkdir -p "$ROOT_MOUNT/etc/modules-load.d" "$ROOT_MOUNT/etc/sysctl.d" - cat <<'EOF' | sudo tee "$modules_conf" >/dev/null -nf_tables -nft_chain_nat -veth -br_netfilter -overlay -EOF - cat <<'EOF' | sudo tee "$sysctl_conf" >/dev/null -net.bridge.bridge-nf-call-iptables = 1 -net.bridge.bridge-nf-call-ip6tables = 1 -net.ipv4.ip_forward = 1 -EOF - sudo chmod 0644 "$modules_conf" "$sysctl_conf" -} - -configure_vsock_modules() { - local modules_conf="$ROOT_MOUNT/etc/modules-load.d/banger-vsock.conf" - - sudo mkdir -p "$ROOT_MOUNT/etc/modules-load.d" - cat <<'EOF' | sudo tee "$modules_conf" >/dev/null -vsock -vmw_vsock_virtio_transport -EOF - sudo chmod 0644 "$modules_conf" -} - -configure_apk_repositories() { - local repositories="$ROOT_MOUNT/etc/apk/repositories" - - sudo mkdir -p "$ROOT_MOUNT/etc/apk" - cat </dev/null -$APK_RELEASE_URL/main -$APK_RELEASE_URL/community -EOF - sudo chmod 0644 "$repositories" - if [[ -r /etc/resolv.conf ]]; then - sudo install -m 0644 /etc/resolv.conf "$ROOT_MOUNT/etc/resolv.conf" - fi -} - -build_alpine_initramfs() { - local kernel_version="$1" - local guest_output="/boot/initramfs-${kernel_version}.img" - local stage_output="$MANUAL_DIR/alpine-kernel/boot/initramfs-${kernel_version}.img" - local mkinitfs_dir="$ROOT_MOUNT/etc/mkinitfs" - local mkinitfs_conf="$mkinitfs_dir/mkinitfs.conf" - - sudo mkdir -p "$mkinitfs_dir" "$ROOT_MOUNT/boot" "$MANUAL_DIR/alpine-kernel/boot" - cat <<'EOF' | sudo tee "$mkinitfs_conf" >/dev/null -features="ata base ide scsi usb virtio ext4 nvme" -EOF - sudo chmod 0644 "$mkinitfs_conf" - - log "building Alpine initramfs for kernel $kernel_version" - sudo env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ - chroot "$ROOT_MOUNT" /bin/sh -se <&2 - exit 1 -fi -ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode -EOF - - cat <<'EOF' | sudo tee "$profile_mise" >/dev/null -if [ -n "${BASH_VERSION:-}" ] && [ -z "${BANGER_MISE_ACTIVATED:-}" ] && [ -x '/usr/local/bin/mise' ]; then - export BANGER_MISE_ACTIVATED=1 - eval "$(/usr/local/bin/mise activate bash)" -fi -EOF - sudo chmod 0644 "$profile_mise" -} - -enable_openrc_services() { - sudo env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin chroot "$ROOT_MOUNT" /bin/sh -se <<'EOF' -set -eu - -add_service() { - local service="$1" - local runlevel="$2" - if [ ! -x "/etc/init.d/$service" ]; then - echo "missing OpenRC service: $service" >&2 - exit 1 - fi - rc-update add "$service" "$runlevel" >/dev/null -} - -for service in devfs dmesg mdev; do - add_service "$service" sysinit -done -for service in hwdrivers modules sysctl hostname bootmisc cgroups; do - add_service "$service" boot -done -for service in banger-network sshd banger-docker-preflight docker banger-vsock-agent banger-opencode; do - add_service "$service" default -done -for service in mount-ro killprocs; do - add_service "$service" shutdown -done -EOF -} - -cleanup() { - if [[ "${SYS_MOUNTED:-0}" == "1" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT/sys"; then - sudo umount "$ROOT_MOUNT/sys" || true - fi - if [[ "${PROC_MOUNTED:-0}" == "1" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT/proc"; then - sudo umount "$ROOT_MOUNT/proc" || true - fi - if [[ "${DEVPTS_MOUNTED:-0}" == "1" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT/dev/pts"; then - sudo umount "$ROOT_MOUNT/dev/pts" || true - fi - if [[ "${DEV_MOUNTED:-0}" == "1" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT/dev"; then - sudo umount "$ROOT_MOUNT/dev" || true - fi - if [[ -n "${ROOT_MOUNT:-}" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT"; then - sudo umount "$ROOT_MOUNT" || true - fi - if [[ "${BUILD_DONE:-0}" != "1" ]]; then - rm -f "${OUT_ROOTFS:-}" "${WORK_SEED:-}" "${OUT_ROOTFS:-}.packages.sha256" - fi - if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then - rm -rf "$TMP_DIR" - fi -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -BANGER_BIN="$(resolve_banger_bin)" -SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)" -OUT_ROOTFS="$MANUAL_DIR/rootfs-alpine.ext4" -SIZE_SPEC="2G" -RELEASE="${ALPINE_RELEASE:-3.23.3}" -MIRROR="https://dl-cdn.alpinelinux.org/alpine" -ARCH="x86_64" -MISE_VERSION="v2025.12.0" -MISE_INSTALL_PATH="/usr/local/bin/mise" -OPENCODE_TOOL="github:anomalyco/opencode" -GUESTNET_BOOTSTRAP_SCRIPT="$REPO_ROOT/internal/guestnet/assets/bootstrap.sh" -MODULES_DIR="" -ALPINE_KERNEL_MODULES_DIR="$(find_latest_module_dir "$MANUAL_DIR/alpine-kernel/lib/modules" || true)" -VSOCK_AGENT="$("$BANGER_BIN" internal vsock-agent-path)" -if [[ -n "$ALPINE_KERNEL_MODULES_DIR" ]]; then - MODULES_DIR="$ALPINE_KERNEL_MODULES_DIR" -fi - -while [[ $# -gt 0 ]]; do - case "$1" in - --out) - OUT_ROOTFS="${2:-}" - shift 2 - ;; - --size) - SIZE_SPEC="${2:-}" - shift 2 - ;; - --release) - RELEASE="${2:-}" - shift 2 - ;; - --mirror) - MIRROR="${2:-}" - shift 2 - ;; - --arch) - ARCH="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - log "unknown option: $1" - usage - exit 1 - ;; - esac -done - -if [[ "$ARCH" != "x86_64" ]]; then - log "unsupported arch: $ARCH" - log "this experimental builder currently supports only x86_64" - exit 1 -fi - -if [[ -z "$MODULES_DIR" || ! -d "$MODULES_DIR" ]]; then - log "modules dir not found; run 'make alpine-kernel' first" - exit 1 -fi -if [[ ! -x "$VSOCK_AGENT" ]]; then - log "vsock agent not found or not executable: $VSOCK_AGENT" - log "run 'make build'" - exit 1 -fi -if [[ ! -f "$GUESTNET_BOOTSTRAP_SCRIPT" ]]; then - log "guest network bootstrap script not found: $GUESTNET_BOOTSTRAP_SCRIPT" - exit 1 -fi -if [[ -e "$OUT_ROOTFS" ]]; then - log "output rootfs already exists: $OUT_ROOTFS" - exit 1 -fi - -require_command curl -require_command tar -require_command sudo -require_command mkfs.ext4 -require_command ssh-keygen -require_command mount -require_command umount -require_command install -require_command find -require_command awk -require_command sed -require_command sha256sum -require_command truncate -require_command mountpoint -require_command chroot -require_command cp - -ALPINE_PACKAGES=() -if ! load_package_preset alpine ALPINE_PACKAGES; then - log "alpine package preset is empty" - exit 1 -fi -if ! PACKAGES_HASH="$(printf '%s\n' "${ALPINE_PACKAGES[@]}" | sha256sum | awk '{print $1}')"; then - log "failed to hash package preset" - exit 1 -fi -if ! SIZE_BYTES="$(parse_size "$SIZE_SPEC")"; then - log "invalid size: $SIZE_SPEC" - exit 1 -fi - -if [[ "$OUT_ROOTFS" == *.ext4 ]]; then - WORK_SEED="${OUT_ROOTFS%.ext4}.work-seed.ext4" -else - WORK_SEED="${OUT_ROOTFS}.work-seed" -fi - -BRANCH="$(resolve_release_branch "$RELEASE")" -RELEASE_DIR="$MIRROR/$BRANCH/releases/$ARCH" -MINIROOTFS_URL="$RELEASE_DIR/alpine-minirootfs-$RELEASE-$ARCH.tar.gz" -MINIROOTFS_SHA256_URL="$MINIROOTFS_URL.sha256" -APK_RELEASE_URL="$MIRROR/$BRANCH" - -TMP_DIR="$(mktemp -d -t banger-alpine-rootfs-XXXXXX)" -MINIROOTFS_ARCHIVE="$TMP_DIR/alpine-minirootfs.tar.gz" -ROOT_MOUNT="$TMP_DIR/rootfs" -BUILD_DONE=0 -DEV_MOUNTED=0 -DEVPTS_MOUNTED=0 -PROC_MOUNTED=0 -SYS_MOUNTED=0 -trap cleanup EXIT - -mkdir -p "$ROOT_MOUNT" - -log "downloading Alpine minirootfs from $MINIROOTFS_URL" -curl -fsSL "$MINIROOTFS_URL" -o "$MINIROOTFS_ARCHIVE" -expected_sha="$(curl -fsSL "$MINIROOTFS_SHA256_URL" | awk '{print $1}')" -actual_sha="$(sha256sum "$MINIROOTFS_ARCHIVE" | awk '{print $1}')" -if [[ -z "$expected_sha" ]]; then - log "failed to read SHA256 from $MINIROOTFS_SHA256_URL" - exit 1 -fi -if [[ "$expected_sha" != "$actual_sha" ]]; then - log "sha256 mismatch for $MINIROOTFS_URL" - log "expected: $expected_sha" - log "actual: $actual_sha" - exit 1 -fi - -log "creating $OUT_ROOTFS ($SIZE_SPEC)" -truncate -s "$SIZE_BYTES" "$OUT_ROOTFS" -mkfs.ext4 -F -m 0 -L banger-alpine-root "$OUT_ROOTFS" >/dev/null -sudo mount -o loop "$OUT_ROOTFS" "$ROOT_MOUNT" - -log "unpacking Alpine minirootfs" -sudo tar -xzf "$MINIROOTFS_ARCHIVE" -C "$ROOT_MOUNT" -configure_apk_repositories -mount_chroot_support - -log "installing Alpine packages into the rootfs" -sudo env HOME=/root PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin \ - chroot "$ROOT_MOUNT" /bin/sh -se <] [--size ] [--mirror ] [--arch ] - -Build an experimental Void Linux rootfs image plus a matching /root work-seed. - -Defaults: - --out ./build/manual/rootfs-void.ext4 - --size 4G - --mirror https://repo-default.voidlinux.org - --arch x86_64 - -This path is experimental and local-only. If ./build/manual/void-kernel exists -it uses the staged Void kernel modules from that directory. It does not change -the default Debian image flow. -EOF -} - -parse_size() { - local raw="$1" - if [[ "$raw" =~ ^([0-9]+)([KMG])?$ ]]; then - local num="${BASH_REMATCH[1]}" - local unit="${BASH_REMATCH[2]}" - case "$unit" in - K) printf '%s\n' $((num * 1024)) ;; - M|"") printf '%s\n' $((num * 1024 * 1024)) ;; - G) printf '%s\n' $((num * 1024 * 1024 * 1024)) ;; - esac - return 0 - fi - return 1 -} - -require_command() { - local name="$1" - command -v "$name" >/dev/null 2>&1 || { - log "required command not found: $name" - exit 1 - } -} - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; build it first with 'make build' or set BANGER_BIN" - exit 1 -} - -normalize_mirror() { - local mirror="${1%/}" - mirror="${mirror%/current}" - mirror="${mirror%/static}" - printf '%s\n' "$mirror" -} - -find_latest_module_dir() { - local root="$1" - if [[ ! -d "$root" ]]; then - return 1 - fi - find "$root" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1 -} - -find_static_binary() { - local name="$1" - find "$STATIC_DIR" -type f \( -name "$name" -o -name "$name.static" \) -perm -u+x | sort | head -n 1 -} - -find_static_keys_dir() { - find "$STATIC_DIR" -type d -path '*/var/db/xbps/keys' | sort | head -n 1 -} - -load_package_preset() { - local preset="$1" - local -n out="$2" - mapfile -t out < <("$BANGER_BIN" internal packages "$preset") - (( ${#out[@]} > 0 )) -} - -write_rootfs_manifest_metadata() { - local rootfs_path="$1" - local manifest_hash="$2" - printf '%s\n' "$manifest_hash" > "${rootfs_path}.packages.sha256" -} - -install_root_authorized_key() { - local public_key - public_key="$(ssh-keygen -y -f "$SSH_KEY")" - sudo mkdir -p "$ROOT_MOUNT/root/.ssh" - printf '%s\n' "$public_key" | sudo tee "$ROOT_MOUNT/root/.ssh/authorized_keys" >/dev/null - sudo chmod 700 "$ROOT_MOUNT/root/.ssh" - sudo chmod 600 "$ROOT_MOUNT/root/.ssh/authorized_keys" -} - -ensure_sshd_include() { - local cfg="$ROOT_MOUNT/etc/ssh/sshd_config" - local tmp_cfg="$TMP_DIR/sshd_config" - local include_line="Include /etc/ssh/sshd_config.d/*.conf" - - sudo mkdir -p "$ROOT_MOUNT/etc/ssh/sshd_config.d" - if sudo test -f "$cfg"; then - sudo cat "$cfg" > "$tmp_cfg" - else - : > "$tmp_cfg" - fi - - if ! grep -Eq '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config\.d/\*\.conf([[:space:]]|$)' "$tmp_cfg"; then - { - printf '%s\n' "$include_line" - cat "$tmp_cfg" - } > "${tmp_cfg}.new" - mv "${tmp_cfg}.new" "$tmp_cfg" - sudo install -m 0644 "$tmp_cfg" "$cfg" - fi -} - -install_vsock_service() { - local service_dir="$ROOT_MOUNT/etc/sv/banger-vsock-agent" - local run_path="$service_dir/run" - local finish_path="$service_dir/finish" - - sudo mkdir -p "$service_dir" - cat <<'EOF' | sudo tee "$run_path" >/dev/null -#!/bin/sh -modprobe vsock 2>/dev/null || true -modprobe vmw_vsock_virtio_transport 2>/dev/null || true -exec /usr/local/bin/banger-vsock-agent -EOF - cat <<'EOF' | sudo tee "$finish_path" >/dev/null -#!/bin/sh -exit 0 -EOF - sudo chmod 0755 "$run_path" "$finish_path" - sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default" - sudo ln -snf /etc/sv/banger-vsock-agent "$ROOT_MOUNT/etc/runit/runsvdir/default/banger-vsock-agent" -} - -install_opencode_service() { - local service_dir="$ROOT_MOUNT/etc/sv/banger-opencode" - local run_path="$service_dir/run" - local finish_path="$service_dir/finish" - - sudo mkdir -p "$service_dir" - cat <<'EOF' | sudo tee "$run_path" >/dev/null -#!/bin/sh -set -e -export HOME=/root -cd /root -exec /usr/local/bin/opencode serve --hostname 0.0.0.0 --port 4096 -EOF - cat <<'EOF' | sudo tee "$finish_path" >/dev/null -#!/bin/sh -exit 0 -EOF - sudo chmod 0755 "$run_path" "$finish_path" - sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default" - sudo ln -snf /etc/sv/banger-opencode "$ROOT_MOUNT/etc/runit/runsvdir/default/banger-opencode" -} - -install_guest_network_bootstrap() { - sudo mkdir -p "$ROOT_MOUNT/usr/local/libexec" "$ROOT_MOUNT/etc/runit/core-services" - sudo install -m 0755 "$GUESTNET_BOOTSTRAP_SCRIPT" "$ROOT_MOUNT/usr/local/libexec/banger-network-bootstrap" - sudo install -m 0644 "$GUESTNET_VOID_CORE_SERVICE" "$ROOT_MOUNT/etc/runit/core-services/20-banger-network.sh" -} - -configure_docker_bootstrap() { - local modules_conf="$ROOT_MOUNT/etc/modules-load.d/docker-netfilter.conf" - local sysctl_conf="$ROOT_MOUNT/etc/sysctl.d/99-docker.conf" - local service_dir="$ROOT_MOUNT/etc/sv/docker" - local run_path="$service_dir/run" - local orig_run_path="$service_dir/run.orig" - local preflight_path="$ROOT_MOUNT/usr/local/bin/banger-docker-preflight" - - sudo mkdir -p "$ROOT_MOUNT/etc/modules-load.d" "$ROOT_MOUNT/etc/sysctl.d" "$ROOT_MOUNT/usr/local/bin" - cat <<'EOF' | sudo tee "$modules_conf" >/dev/null -nf_tables -nft_chain_nat -veth -br_netfilter -overlay -EOF - cat <<'EOF' | sudo tee "$sysctl_conf" >/dev/null -net.bridge.bridge-nf-call-iptables = 1 -net.bridge.bridge-nf-call-ip6tables = 1 -net.ipv4.ip_forward = 1 -EOF - cat <<'EOF' | sudo tee "$preflight_path" >/dev/null -#!/bin/sh -for module in nf_tables nft_chain_nat veth br_netfilter overlay; do - modprobe "$module" 2>/dev/null || true -done -if command -v sysctl >/dev/null 2>&1; then - sysctl --load /etc/sysctl.d/99-docker.conf >/dev/null 2>&1 || true -fi -EOF - - if [[ ! -f "$run_path" ]]; then - log "Void rootfs is missing /etc/sv/docker/run after docker install" - exit 1 - fi - sudo install -m 0755 "$run_path" "$orig_run_path" - cat <<'EOF' | sudo tee "$run_path" >/dev/null -#!/bin/sh -set -e -/usr/local/bin/banger-docker-preflight -exec /etc/sv/docker/run.orig -EOF - sudo chmod 0644 "$modules_conf" "$sysctl_conf" - sudo chmod 0755 "$preflight_path" "$run_path" "$orig_run_path" -} - -enable_sshd_service() { - if [[ ! -d "$ROOT_MOUNT/etc/sv/sshd" ]]; then - log "Void rootfs is missing /etc/sv/sshd after openssh install" - exit 1 - fi - sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default" - sudo ln -snf /etc/sv/sshd "$ROOT_MOUNT/etc/runit/runsvdir/default/sshd" -} - -enable_docker_service() { - if [[ ! -d "$ROOT_MOUNT/etc/sv/docker" ]]; then - log "Void rootfs is missing /etc/sv/docker after docker install" - exit 1 - fi - sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default" - sudo ln -snf /etc/sv/docker "$ROOT_MOUNT/etc/runit/runsvdir/default/docker" -} - -normalize_root_shell() { - local passwd="$ROOT_MOUNT/etc/passwd" - local shells="$ROOT_MOUNT/etc/shells" - local wanted_shell="/bin/bash" - local tmp_passwd="$TMP_DIR/passwd" - local root_shell="" - - if [[ ! -x "$ROOT_MOUNT$wanted_shell" ]]; then - log "required root shell is missing from the Void image: $wanted_shell" - exit 1 - fi - if [[ ! -f "$shells" ]]; then - log "Void image is missing /etc/shells" - exit 1 - fi - if ! sudo grep -Fxq "$wanted_shell" "$shells"; then - log "Void image does not allow $wanted_shell in /etc/shells" - exit 1 - fi - - sudo cat "$passwd" > "$tmp_passwd" - awk -F: -v OFS=: -v shell="$wanted_shell" ' - $1 == "root" { - $7 = shell - found = 1 - } - { print } - END { - if (!found) { - exit 1 - } - } - ' "$tmp_passwd" > "${tmp_passwd}.new" || { - log "failed to rewrite root shell in /etc/passwd" - exit 1 - } - mv "${tmp_passwd}.new" "$tmp_passwd" - sudo install -m 0644 "$tmp_passwd" "$passwd" - - root_shell="$(sudo awk -F: '$1 == "root" { print $7 }' "$passwd")" - if [[ "$root_shell" != "$wanted_shell" ]]; then - log "root shell normalization failed: expected $wanted_shell, got ${root_shell:-}" - exit 1 - fi -} - -configure_root_bash_prompt() { - local bashrc="$ROOT_MOUNT/root/.bashrc" - local bash_profile="$ROOT_MOUNT/root/.bash_profile" - local profile_prompt="$ROOT_MOUNT/etc/profile.d/banger-bash-prompt.sh" - - sudo mkdir -p "$ROOT_MOUNT/root" "$ROOT_MOUNT/etc/profile.d" - cat <<'EOF' | sudo tee "$bashrc" >/dev/null -# banger: default interactive prompt for experimental Void guests -case "$-" in - *i*) ;; - *) return ;; -esac - -if [ -z "${BANGER_MISE_ACTIVATED:-}" ] && [ -x '/usr/local/bin/mise' ]; then - export BANGER_MISE_ACTIVATED=1 - eval "$(/usr/local/bin/mise activate bash)" -fi - -PS1='\u@\h:\w\$ ' -EOF - cat <<'EOF' | sudo tee "$bash_profile" >/dev/null -if [ -f ~/.bashrc ]; then - . ~/.bashrc -fi -EOF - cat <<'EOF' | sudo tee "$profile_prompt" >/dev/null -case "$-" in - *i*) ;; - *) return 0 2>/dev/null || exit 0 ;; -esac - -if [ -n "${BASH_VERSION:-}" ]; then - PS1='\u@\h:\w\$ ' -fi -EOF - sudo chmod 0644 "$bashrc" "$bash_profile" "$profile_prompt" -} - -install_guest_tools() { - local profile_mise="$ROOT_MOUNT/etc/profile.d/mise.sh" - - sudo mkdir -p "$ROOT_MOUNT/etc/profile.d" - if [[ -r /etc/resolv.conf ]]; then - sudo install -m 0644 /etc/resolv.conf "$ROOT_MOUNT/etc/resolv.conf" - fi - - sudo env HOME=/root PATH=/usr/local/bin:/usr/bin:/bin chroot "$ROOT_MOUNT" /bin/bash -se <&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/npm ]]; then - echo "npm shim not found after mise install" >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/opencode ]]; then - echo "opencode shim not found after mise install" >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/claude ]]; then - echo "claude shim not found after mise install" >&2 - exit 1 -fi -if [[ ! -e /root/.local/share/mise/shims/pi ]]; then - echo "pi shim not found after mise install" >&2 - exit 1 -fi -ln -snf /root/.local/share/mise/shims/node /usr/local/bin/node -ln -snf /root/.local/share/mise/shims/npm /usr/local/bin/npm -ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode -ln -snf /root/.local/share/mise/shims/claude /usr/local/bin/claude -ln -snf /root/.local/share/mise/shims/pi /usr/local/bin/pi -EOF - - cat <<'EOF' | sudo tee "$profile_mise" >/dev/null -if [ -n "${BASH_VERSION:-}" ] && [ -z "${BANGER_MISE_ACTIVATED:-}" ] && [ -x '/usr/local/bin/mise' ]; then - export BANGER_MISE_ACTIVATED=1 - eval "$(/usr/local/bin/mise activate bash)" -fi -EOF - sudo chmod 0644 "$profile_mise" -} - -cleanup() { - if [[ -n "${ROOT_MOUNT:-}" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT"; then - sudo umount "$ROOT_MOUNT" || true - fi - if [[ "${BUILD_DONE:-0}" != "1" ]]; then - rm -f "${OUT_ROOTFS:-}" "${WORK_SEED:-}" "${OUT_ROOTFS:-}.packages.sha256" - fi - if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then - rm -rf "$TMP_DIR" - fi -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -BANGER_BIN="$(resolve_banger_bin)" -SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)" -OUT_ROOTFS="$MANUAL_DIR/rootfs-void.ext4" -SIZE_SPEC="4G" -MIRROR="https://repo-default.voidlinux.org" -ARCH="x86_64" -MISE_VERSION="v2025.12.0" -MISE_INSTALL_PATH="/usr/local/bin/mise" -NODE_TOOL="node@22" -OPENCODE_TOOL="github:anomalyco/opencode" -CLAUDE_CODE_TOOL="npm:@anthropic-ai/claude-code" -PI_TOOL="npm:@mariozechner/pi-coding-agent" -GUESTNET_BOOTSTRAP_SCRIPT="$REPO_ROOT/internal/guestnet/assets/bootstrap.sh" -GUESTNET_VOID_CORE_SERVICE="$REPO_ROOT/internal/guestnet/assets/void-core-service.sh" -MODULES_DIR="" -VOID_KERNEL_MODULES_DIR="$(find_latest_module_dir "$MANUAL_DIR/void-kernel/lib/modules" || true)" -VSOCK_AGENT="$("$BANGER_BIN" internal vsock-agent-path)" -if [[ -n "$VOID_KERNEL_MODULES_DIR" ]]; then - MODULES_DIR="$VOID_KERNEL_MODULES_DIR" -fi - -while [[ $# -gt 0 ]]; do - case "$1" in - --out) - OUT_ROOTFS="${2:-}" - shift 2 - ;; - --size) - SIZE_SPEC="${2:-}" - shift 2 - ;; - --mirror) - MIRROR="${2:-}" - shift 2 - ;; - --arch) - ARCH="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - log "unknown option: $1" - usage - exit 1 - ;; - esac -done - -MIRROR="$(normalize_mirror "$MIRROR")" -REPO_URL="$MIRROR/current" -STATIC_ARCHIVE_URL="$MIRROR/static/xbps-static-latest.x86_64-musl.tar.xz" - -if [[ "$ARCH" != "x86_64" ]]; then - log "unsupported arch: $ARCH" - log "this experimental builder currently supports only x86_64-glibc" - exit 1 -fi - -if [[ -z "$MODULES_DIR" || ! -d "$MODULES_DIR" ]]; then - log "modules dir not found; run 'make void-kernel' first" - exit 1 -fi -if [[ ! -x "$VSOCK_AGENT" ]]; then - log "vsock agent not found or not executable: $VSOCK_AGENT" - log "run 'make build'" - exit 1 -fi -if [[ ! -f "$GUESTNET_BOOTSTRAP_SCRIPT" ]]; then - log "guest network bootstrap script not found: $GUESTNET_BOOTSTRAP_SCRIPT" - exit 1 -fi -if [[ ! -f "$GUESTNET_VOID_CORE_SERVICE" ]]; then - log "guest network core-service shim not found: $GUESTNET_VOID_CORE_SERVICE" - exit 1 -fi -if [[ -e "$OUT_ROOTFS" ]]; then - log "output rootfs already exists: $OUT_ROOTFS" - exit 1 -fi - -require_command curl -require_command tar -require_command sudo -require_command mkfs.ext4 -require_command ssh-keygen -require_command mount -require_command umount -require_command install -require_command find -require_command awk -require_command sed -require_command sha256sum -require_command truncate -require_command mountpoint - -VOID_PACKAGES=() -if ! load_package_preset void VOID_PACKAGES; then - log "void package preset is empty" - exit 1 -fi -if ! PACKAGES_HASH="$(printf '%s\n' "${VOID_PACKAGES[@]}" | sha256sum | awk '{print $1}')"; then - log "failed to hash package preset" - exit 1 -fi -if ! SIZE_BYTES="$(parse_size "$SIZE_SPEC")"; then - log "invalid size: $SIZE_SPEC" - exit 1 -fi - -if [[ "$OUT_ROOTFS" == *.ext4 ]]; then - WORK_SEED="${OUT_ROOTFS%.ext4}.work-seed.ext4" -else - WORK_SEED="${OUT_ROOTFS}.work-seed" -fi - -TMP_DIR="$(mktemp -d -t banger-void-rootfs-XXXXXX)" -STATIC_DIR="$TMP_DIR/static" -ROOT_MOUNT="$TMP_DIR/rootfs" -STATIC_ARCHIVE="$TMP_DIR/xbps-static.tar.xz" -BUILD_DONE=0 -trap cleanup EXIT - -mkdir -p "$STATIC_DIR" "$ROOT_MOUNT" - -log "downloading static XBPS from $STATIC_ARCHIVE_URL" -curl -fsSL "$STATIC_ARCHIVE_URL" -o "$STATIC_ARCHIVE" -tar -xf "$STATIC_ARCHIVE" -C "$STATIC_DIR" - -XBPS_INSTALL="$(find_static_binary xbps-install)" -XBPS_QUERY="$(find_static_binary xbps-query)" -STATIC_KEYS_DIR="$(find_static_keys_dir)" - -if [[ -z "$XBPS_INSTALL" || ! -x "$XBPS_INSTALL" ]]; then - log "failed to locate xbps-install in the static archive" - exit 1 -fi -if [[ -z "$STATIC_KEYS_DIR" || ! -d "$STATIC_KEYS_DIR" ]]; then - log "failed to locate Void repository keys in the static archive" - exit 1 -fi - -log "creating $OUT_ROOTFS ($SIZE_SPEC)" -truncate -s "$SIZE_BYTES" "$OUT_ROOTFS" -mkfs.ext4 -F -m 0 -L banger-void-root "$OUT_ROOTFS" >/dev/null -sudo mount -o loop "$OUT_ROOTFS" "$ROOT_MOUNT" -sudo mkdir -p "$ROOT_MOUNT/var/db/xbps/keys" -sudo cp -a "$STATIC_KEYS_DIR/." "$ROOT_MOUNT/var/db/xbps/keys/" - -log "installing Void packages into the rootfs" -sudo env XBPS_ARCH="$ARCH" "$XBPS_INSTALL" -S -y -r "$ROOT_MOUNT" -R "$REPO_URL" "${VOID_PACKAGES[@]}" - -if [[ -n "$XBPS_QUERY" && -x "$XBPS_QUERY" ]]; then - log "installed package set:" - sudo env XBPS_ARCH="$ARCH" "$XBPS_QUERY" -r "$ROOT_MOUNT" -l | awk '/^ii/ {print " " $2}' || true -fi - -if [[ -n "$VOID_KERNEL_MODULES_DIR" ]]; then - log "copying staged Void kernel modules into the guest" -else - log "copying bundled kernel modules into the guest" -fi -sudo mkdir -p "$ROOT_MOUNT/lib/modules" -sudo cp -a "$MODULES_DIR" "$ROOT_MOUNT/lib/modules/" - -log "installing the guest-side vsock agent" -sudo mkdir -p "$ROOT_MOUNT/usr/local/bin" -sudo install -m 0755 "$VSOCK_AGENT" "$ROOT_MOUNT/usr/local/bin/banger-vsock-agent" - -log "preparing SSH and runit services" -install_guest_network_bootstrap -ensure_sshd_include -enable_sshd_service -install_vsock_service -configure_docker_bootstrap -enable_docker_service -normalize_root_shell -configure_root_bash_prompt -log "installing guest tools" -install_guest_tools -install_opencode_service -install_root_authorized_key -sudo touch "$ROOT_MOUNT/etc/fstab" "$ROOT_MOUNT/etc/hostname" -sudo chroot "$ROOT_MOUNT" /usr/bin/ssh-keygen -A - -log "removing bulky caches, docs, and stale installer artifacts from the experimental image" -sudo rm -rf \ - "$ROOT_MOUNT/var/cache/xbps" \ - "$ROOT_MOUNT/usr/share/doc" \ - "$ROOT_MOUNT/usr/share/info" \ - "$ROOT_MOUNT/usr/share/man" -sudo rm -f \ - "$ROOT_MOUNT/root/get-docker" \ - "$ROOT_MOUNT/root/get-docker.sh" \ - "$ROOT_MOUNT/root/.cache/opencode" \ - "$ROOT_MOUNT/tmp/get-docker" \ - "$ROOT_MOUNT/tmp/get-docker.sh" -sudo rm -rf \ - "$ROOT_MOUNT/root/.cache/mise" \ - "$ROOT_MOUNT/root/.local/share/mise/downloads" \ - "$ROOT_MOUNT/root/.local/share/mise/tmp" - -sudo umount "$ROOT_MOUNT" - -write_rootfs_manifest_metadata "$OUT_ROOTFS" "$PACKAGES_HASH" - -log "building work-seed $WORK_SEED" -"$BANGER_BIN" internal work-seed --rootfs "$OUT_ROOTFS" --out "$WORK_SEED" - -BUILD_DONE=1 -log "built experimental Void rootfs: $OUT_ROOTFS" -log "built experimental Void work-seed: $WORK_SEED" -log "use examples/void.config.toml as the local config override template" diff --git a/scripts/make-rootfs.sh b/scripts/make-rootfs.sh deleted file mode 100755 index 2c4c405..0000000 --- a/scripts/make-rootfs.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[make-rootfs] %s\n' "$*" -} - -usage() { - cat <<'EOF' -Usage: ./scripts/make-rootfs.sh --kernel [--initrd ] [--modules ] [--size ] [--base-rootfs ] - -Builds build/manual/rootfs-docker.ext4 using scripts/customize.sh. If ---base-rootfs is omitted, the first existing file is used: - ./build/manual/rootfs-base.ext4 - ./ubuntu-noble-rootfs/rootfs.ext4 - ./ubuntu-lts/rootfs.ext4 -EOF -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -OUT_ROOTFS="$MANUAL_DIR/rootfs-docker.ext4" -SIZE_SPEC="6G" -BASE_ROOTFS="" -KERNEL_PATH="" -INITRD_PATH="" -MODULES_DIR="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --size) - SIZE_SPEC="${2:-}" - shift 2 - ;; - --base-rootfs) - BASE_ROOTFS="${2:-}" - shift 2 - ;; - --kernel) - KERNEL_PATH="${2:-}" - shift 2 - ;; - --initrd) - INITRD_PATH="${2:-}" - shift 2 - ;; - --modules) - MODULES_DIR="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - log "unknown option: $1" - usage - exit 1 - ;; - esac -done - -if [[ -z "$BASE_ROOTFS" ]]; then - if [[ -f "$MANUAL_DIR/rootfs-base.ext4" ]]; then - BASE_ROOTFS="$MANUAL_DIR/rootfs-base.ext4" - elif [[ -f "$REPO_ROOT/ubuntu-noble-rootfs/rootfs.ext4" ]]; then - BASE_ROOTFS="$REPO_ROOT/ubuntu-noble-rootfs/rootfs.ext4" - elif [[ -f "$REPO_ROOT/ubuntu-lts/rootfs.ext4" ]]; then - BASE_ROOTFS="$REPO_ROOT/ubuntu-lts/rootfs.ext4" - else - log "no base rootfs found; pass --base-rootfs" - exit 1 - fi -fi - -if [[ -z "$KERNEL_PATH" ]]; then - log "kernel path is required; pass --kernel" - exit 1 -fi - -mkdir -p "$MANUAL_DIR" - -log "building $OUT_ROOTFS from $BASE_ROOTFS" -args=( - "$SCRIPT_DIR/customize.sh" - "$BASE_ROOTFS" - --out "$OUT_ROOTFS" - --size "$SIZE_SPEC" - --kernel "$KERNEL_PATH" - --docker -) -if [[ -n "$INITRD_PATH" ]]; then - args+=(--initrd "$INITRD_PATH") -fi -if [[ -n "$MODULES_DIR" ]]; then - args+=(--modules "$MODULES_DIR") -fi -exec "${args[@]}" diff --git a/scripts/make-void-kernel.sh b/scripts/make-void-kernel.sh deleted file mode 100755 index d47d18f..0000000 --- a/scripts/make-void-kernel.sh +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[make-void-kernel] %s\n' "$*" -} - -usage() { - cat <<'EOF' -Usage: ./scripts/make-void-kernel.sh [--out-dir ] [--mirror ] [--arch ] [--kernel-package ] [--print-register-flags] - -Download and stage a Void Linux kernel under ./build/manual/void-kernel for -the -experimental Void guest flow. - -Defaults: - --out-dir ./build/manual/void-kernel - --mirror https://repo-default.voidlinux.org - --arch x86_64 - --kernel-package linux6.12 - -The staged output contains: - boot/vmlinux- Firecracker-usable kernel extracted from vmlinuz - boot/vmlinuz- Raw distro boot image from the Void package - boot/initramfs-.img Matching initramfs generated with dracut - boot/config- Void kernel config - lib/modules// Matching kernel modules tree - -If --print-register-flags is passed, the script does not download anything. It -prints the banger image register flags for an existing staged Void kernel. -EOF -} - -require_command() { - local name="$1" - command -v "$name" >/dev/null 2>&1 || { - log "required command not found: $name" - exit 1 - } -} - -normalize_mirror() { - local mirror="${1%/}" - mirror="${mirror%/current}" - mirror="${mirror%/static}" - printf '%s\n' "$mirror" -} - -find_static_binary() { - local name="$1" - find "$STATIC_DIR" -type f \( -name "$name" -o -name "$name.static" \) -perm -u+x | sort | head -n 1 -} - -find_static_keys_dir() { - find "$STATIC_DIR" -type d -path '*/var/db/xbps/keys' | sort | head -n 1 -} - -find_latest_matching() { - local dir="$1" - local pattern="$2" - if [[ ! -d "$dir" ]]; then - return 1 - fi - find "$dir" -maxdepth 1 -type f -name "$pattern" | sort | tail -n 1 -} - -find_latest_module_dir() { - local root="$1" - if [[ ! -d "$root" ]]; then - return 1 - fi - find "$root" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1 -} - -print_register_flags() { - local kernel="" - local initrd="" - local modules="" - - kernel="$(find_latest_matching "$OUT_DIR/boot" 'vmlinux-*' || true)" - initrd="$(find_latest_matching "$OUT_DIR/boot" 'initramfs-*' || true)" - modules="$(find_latest_module_dir "$OUT_DIR/lib/modules" || true)" - - if [[ -z "$kernel" || -z "$modules" ]]; then - log "staged Void kernel not found under $OUT_DIR" - exit 1 - fi - - printf -- '--kernel %q ' "$kernel" - if [[ -n "$initrd" ]]; then - printf -- '--initrd %q ' "$initrd" - fi - printf -- '--modules %q\n' "$modules" -} - -check_elf() { - local path="$1" - readelf -h "$path" >/dev/null 2>&1 -} - -ensure_stage_root_layout() { - mkdir -p "$STAGE_ROOT/usr" - - if [[ ! -e "$STAGE_ROOT/bin" ]]; then - ln -snf usr/bin "$STAGE_ROOT/bin" - fi - if [[ ! -e "$STAGE_ROOT/sbin" ]]; then - ln -snf usr/bin "$STAGE_ROOT/sbin" - fi - if [[ ! -e "$STAGE_ROOT/usr/sbin" ]]; then - ln -snf bin "$STAGE_ROOT/usr/sbin" - fi - if [[ ! -e "$STAGE_ROOT/lib" ]]; then - ln -snf usr/lib "$STAGE_ROOT/lib" - fi - if [[ ! -e "$STAGE_ROOT/lib64" ]]; then - ln -snf usr/lib "$STAGE_ROOT/lib64" - fi - if [[ ! -e "$STAGE_ROOT/usr/lib64" ]]; then - ln -snf lib "$STAGE_ROOT/usr/lib64" - fi - if [[ -x "$STAGE_ROOT/usr/bin/udevd" ]]; then - mkdir -p "$STAGE_ROOT/usr/lib/udev" "$STAGE_ROOT/usr/lib/systemd" - if [[ ! -e "$STAGE_ROOT/usr/lib/udev/udevd" ]]; then - ln -snf ../../bin/udevd "$STAGE_ROOT/usr/lib/udev/udevd" - fi - if [[ ! -e "$STAGE_ROOT/usr/lib/systemd/systemd-udevd" ]]; then - ln -snf ../../bin/udevd "$STAGE_ROOT/usr/lib/systemd/systemd-udevd" - fi - fi -} - -sync_host_dracut_tree() { - if [[ ! -d /usr/lib/dracut ]]; then - log "host dracut support files not found under /usr/lib/dracut" - exit 1 - fi - rm -rf "$STAGE_ROOT/usr/lib/dracut" - mkdir -p "$STAGE_ROOT/usr/lib" - cp -a /usr/lib/dracut "$STAGE_ROOT/usr/lib/dracut" -} - -build_initramfs() { - local kver="$1" - local modules_dir="$2" - local out="$3" - local config_dir="$TMP_DIR/dracut.conf.d" - local tmpdir="$TMP_DIR/dracut-tmp" - local force_drivers="virtio virtio_ring virtio_mmio virtio_blk virtio_net virtio_console ext4 vsock vmw_vsock_virtio_transport" - - mkdir -p "$config_dir" "$tmpdir" - ensure_stage_root_layout - sync_host_dracut_tree - - log "generating initramfs for kernel $kver with host dracut against the staged Void sysroot" - env dracutbasedir="/usr/lib/dracut" dracut \ - --force \ - --kver "$kver" \ - --sysroot "$STAGE_ROOT" \ - --kmoddir "$modules_dir" \ - --conf /dev/null \ - --confdir "$config_dir" \ - --tmpdir "$tmpdir" \ - --no-hostonly \ - --filesystems "ext4" \ - --force-drivers "$force_drivers" \ - --gzip \ - "$out" -} - -extract_vmlinux() { - local image="$1" - local out="$2" - local tmp="$TMP_DIR/vmlinux.extract" - - if check_elf "$image"; then - install -m 0644 "$image" "$out" - return 0 - fi - - try_decompress() { - local header="$1" - local marker="$2" - local command="$3" - local pos="" - - while IFS= read -r pos; do - [[ -n "$pos" ]] || continue - pos="${pos%%:*}" - tail -c+"$pos" "$image" | eval "$command" >"$tmp" 2>/dev/null || true - if check_elf "$tmp"; then - install -m 0644 "$tmp" "$out" - return 0 - fi - done < <(tr "$header\n$marker" "\n$marker=" < "$image" | grep -abo "^$marker" || true) - - return 1 - } - - try_decompress '\037\213\010' "xy" "gunzip" && return 0 - try_decompress '\3757zXZ\000' "abcde" "unxz" && return 0 - try_decompress "BZh" "xy" "bunzip2" && return 0 - try_decompress '\135\000\000\000' "xxx" "unlzma" && return 0 - try_decompress '\002!L\030' "xxx" "lz4 -d" && return 0 - try_decompress '(\265/\375' "xxx" "unzstd" && return 0 - - return 1 -} - -resolve_kernel_package_file() { - local escaped_name="" - escaped_name="$(printf '%s\n' "$KERNEL_PACKAGE" | sed 's/[.[\*^$()+?{|]/\\&/g')" - - curl -fsSL "$REPO_URL/" | - grep -o "${escaped_name}-[0-9][^\" >]*\\.${ARCH}\\.xbps" | - sort -u | - tail -n 1 -} - -cleanup() { - if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then - rm -rf "$TMP_DIR" - fi -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -OUT_DIR="$MANUAL_DIR/void-kernel" -MIRROR="https://repo-default.voidlinux.org" -ARCH="x86_64" -KERNEL_PACKAGE="linux6.12" -PRINT_REGISTER_FLAGS=0 - -while [[ $# -gt 0 ]]; do - case "$1" in - --out-dir) - OUT_DIR="${2:-}" - shift 2 - ;; - --mirror) - MIRROR="${2:-}" - shift 2 - ;; - --arch) - ARCH="${2:-}" - shift 2 - ;; - --kernel-package) - KERNEL_PACKAGE="${2:-}" - shift 2 - ;; - --print-register-flags) - PRINT_REGISTER_FLAGS=1 - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - log "unknown option: $1" - usage - exit 1 - ;; - esac -done - -MIRROR="$(normalize_mirror "$MIRROR")" -REPO_URL="$MIRROR/current" -STATIC_ARCHIVE_URL="$MIRROR/static/xbps-static-latest.x86_64-musl.tar.xz" - -if [[ "$PRINT_REGISTER_FLAGS" == "1" ]]; then - print_register_flags - exit 0 -fi - -if [[ "$ARCH" != "x86_64" ]]; then - log "unsupported arch: $ARCH" - log "this experimental downloader currently supports only x86_64" - exit 1 -fi -mkdir -p "$(dirname "$OUT_DIR")" -if [[ -e "$OUT_DIR" ]]; then - log "output directory already exists: $OUT_DIR" - log "remove it first if you want to re-stage a different Void kernel" - exit 1 -fi - -require_command curl -require_command tar -require_command cp -require_command find -require_command grep -require_command cut -require_command readelf -require_command file -require_command install -require_command tail -require_command xz -require_command gzip -require_command bzip2 -require_command dracut - -TMP_DIR="$(mktemp -d -t banger-void-kernel-XXXXXX)" -STATIC_DIR="$TMP_DIR/static" -STAGE_ROOT="$TMP_DIR/root" -STAGE_OUT="$TMP_DIR/out" -STATIC_ARCHIVE="$TMP_DIR/xbps-static.tar.xz" -trap cleanup EXIT - -mkdir -p "$STATIC_DIR" "$STAGE_ROOT/var/db/xbps/keys" "$STAGE_OUT/boot" "$STAGE_OUT/lib/modules" - -log "downloading static XBPS from $STATIC_ARCHIVE_URL" -curl -fsSL "$STATIC_ARCHIVE_URL" -o "$STATIC_ARCHIVE" -tar -xf "$STATIC_ARCHIVE" -C "$STATIC_DIR" - -XBPS_INSTALL="$(find_static_binary xbps-install)" -STATIC_KEYS_DIR="$(find_static_keys_dir)" -if [[ -z "$XBPS_INSTALL" || ! -x "$XBPS_INSTALL" ]]; then - log "failed to locate xbps-install in the static archive" - exit 1 -fi -if [[ -z "$STATIC_KEYS_DIR" || ! -d "$STATIC_KEYS_DIR" ]]; then - log "failed to locate Void repository keys in the static archive" - exit 1 -fi - -cp -a "$STATIC_KEYS_DIR/." "$STAGE_ROOT/var/db/xbps/keys/" - -KERNEL_PACKAGE_FILE="$(resolve_kernel_package_file)" -if [[ -z "$KERNEL_PACKAGE_FILE" ]]; then - log "failed to resolve a package file for $KERNEL_PACKAGE in $REPO_URL" - exit 1 -fi - -log "staging $KERNEL_PACKAGE_FILE into a temporary root" -env XBPS_ARCH="$ARCH" "$XBPS_INSTALL" -S -y -U -r "$STAGE_ROOT" -R "$REPO_URL" linux-base "$KERNEL_PACKAGE" dracut eudev >/dev/null - -VMLINUX_RAW="$(find_latest_matching "$STAGE_ROOT/boot" 'vmlinuz-*' || true)" -KERNEL_CONFIG="$(find_latest_matching "$STAGE_ROOT/boot" 'config-*' || true)" -MODULES_DIR="$(find_latest_module_dir "$STAGE_ROOT/usr/lib/modules" || true)" -KERNEL_VERSION="$(basename "$MODULES_DIR")" -INITRAMFS_NAME="initramfs-${KERNEL_VERSION}.img" -INITRAMFS_RAW="$STAGE_OUT/boot/$INITRAMFS_NAME" - -if [[ -z "$VMLINUX_RAW" || -z "$KERNEL_CONFIG" || -z "$MODULES_DIR" ]]; then - log "staged Void kernel is missing expected boot artifacts" - exit 1 -fi -if [[ ! -x "$STAGE_ROOT/usr/bin/udevd" ]]; then - log "staged Void sysroot is missing /usr/bin/udevd after package install" - exit 1 -fi - -VMLINUX_BASE="$(basename "$VMLINUX_RAW")" -VMLINUX_OUT="$STAGE_OUT/boot/vmlinux-${VMLINUX_BASE#vmlinuz-}" -install -m 0644 "$VMLINUX_RAW" "$STAGE_OUT/boot/$VMLINUX_BASE" -install -m 0644 "$KERNEL_CONFIG" "$STAGE_OUT/boot/$(basename "$KERNEL_CONFIG")" -build_initramfs "$KERNEL_VERSION" "$MODULES_DIR" "$INITRAMFS_RAW" -cp -a "$MODULES_DIR" "$STAGE_OUT/lib/modules/" - -log "extracting Firecracker kernel from $(basename "$VMLINUX_RAW")" -if ! extract_vmlinux "$VMLINUX_RAW" "$VMLINUX_OUT"; then - log "failed to extract an uncompressed vmlinux from $VMLINUX_RAW" - log "raw kernel image type: $(file -b "$VMLINUX_RAW")" - exit 1 -fi - -cat >"$STAGE_OUT/metadata.json" <&2 -} - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; build it first with 'make build' or set BANGER_BIN" - exit 1 -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -RUNTIME_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -IMAGE_NAME="${ALPINE_IMAGE_NAME:-alpine}" -KERNEL_REF="${ALPINE_KERNEL_REF:-$IMAGE_NAME}" -BANGER_BIN="$(resolve_banger_bin)" -ROOTFS="$RUNTIME_DIR/rootfs-alpine.ext4" -WORK_SEED="$RUNTIME_DIR/rootfs-alpine.work-seed.ext4" - -if [[ ! -f "$ROOTFS" ]]; then - log "missing Alpine rootfs: $ROOTFS" - exit 1 -fi -if [[ ! -f "$WORK_SEED" ]]; then - log "missing Alpine work-seed: $WORK_SEED" - exit 1 -fi -if [[ ! -d "$RUNTIME_DIR/alpine-kernel" ]]; then - log "missing staged Alpine kernel artifacts: $RUNTIME_DIR/alpine-kernel" - log "run 'make alpine-kernel' before registering $IMAGE_NAME" - exit 1 -fi - -log "importing Alpine kernel from $RUNTIME_DIR/alpine-kernel as $KERNEL_REF" -"$BANGER_BIN" kernel import "$KERNEL_REF" \ - --from "$RUNTIME_DIR/alpine-kernel" \ - --distro alpine \ - --arch x86_64 - -log "registering image $IMAGE_NAME with kernel-ref $KERNEL_REF" -"$BANGER_BIN" image register \ - --name "$IMAGE_NAME" \ - --rootfs "$ROOTFS" \ - --work-seed "$WORK_SEED" \ - --docker \ - --kernel-ref "$KERNEL_REF" diff --git a/scripts/register-void-image.sh b/scripts/register-void-image.sh deleted file mode 100755 index 64de6d7..0000000 --- a/scripts/register-void-image.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[register-void-image] %s\n' "$*" >&2 -} - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; build it first with 'make build' or set BANGER_BIN" - exit 1 -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -RUNTIME_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" -IMAGE_NAME="${VOID_IMAGE_NAME:-void}" -KERNEL_REF="${VOID_KERNEL_REF:-$IMAGE_NAME}" -BANGER_BIN="$(resolve_banger_bin)" -ROOTFS="$RUNTIME_DIR/rootfs-void.ext4" -WORK_SEED="$RUNTIME_DIR/rootfs-void.work-seed.ext4" - -if [[ ! -f "$ROOTFS" ]]; then - log "missing Void rootfs: $ROOTFS" - exit 1 -fi -if [[ ! -f "$WORK_SEED" ]]; then - log "missing Void work-seed: $WORK_SEED" - exit 1 -fi -if [[ ! -d "$RUNTIME_DIR/void-kernel" ]]; then - log "missing staged Void kernel artifacts: $RUNTIME_DIR/void-kernel" - log "run 'make void-kernel' before registering $IMAGE_NAME" - exit 1 -fi - -log "importing Void kernel from $RUNTIME_DIR/void-kernel as $KERNEL_REF" -"$BANGER_BIN" kernel import "$KERNEL_REF" \ - --from "$RUNTIME_DIR/void-kernel" \ - --distro void \ - --arch x86_64 - -log "registering image $IMAGE_NAME with kernel-ref $KERNEL_REF" -"$BANGER_BIN" image register \ - --name "$IMAGE_NAME" \ - --rootfs "$ROOTFS" \ - --work-seed "$WORK_SEED" \ - --kernel-ref "$KERNEL_REF" diff --git a/scripts/verify.sh b/scripts/verify.sh deleted file mode 100755 index 64a20dd..0000000 --- a/scripts/verify.sh +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -log() { - printf '[verify] %s\n' "$*" -} - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -DAEMON_LOG="${XDG_STATE_HOME:-$HOME/.local/state}/banger/bangerd.log" -OPENCODE_PORT=4096 - -resolve_banger_bin() { - if [[ -n "${BANGER_BIN:-}" ]]; then - printf '%s\n' "$BANGER_BIN" - return - fi - if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then - printf '%s\n' "$REPO_ROOT/build/bin/banger" - return - fi - if [[ -x "$REPO_ROOT/banger" ]]; then - printf '%s\n' "$REPO_ROOT/banger" - return - fi - if command -v banger >/dev/null 2>&1; then - command -v banger - return - fi - log "banger binary not found; run 'make build' or set BANGER_BIN" - exit 1 -} - -BANGER_BIN="$(resolve_banger_bin)" -SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)" -if [[ ! -f "$SSH_KEY" ]]; then - log "ssh key not found: $SSH_KEY" - exit 1 -fi -SSH_COMMON_ARGS=( - -F /dev/null - -i "$SSH_KEY" - -o IdentitiesOnly=yes - -o BatchMode=yes - -o PreferredAuthentications=publickey - -o PasswordAuthentication=no - -o KbdInteractiveAuthentication=no - -o StrictHostKeyChecking=no - -o UserKnownHostsFile=/dev/null -) - -firecracker_running() { - local pid="$1" - local api_sock="$2" - local cmdline="" - - if [[ -z "$pid" || "$pid" -le 0 || -z "$api_sock" ]]; then - return 1 - fi - if [[ ! -r "/proc/$pid/cmdline" ]]; then - return 1 - fi - cmdline="$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ' || true)" - [[ "$cmdline" == *firecracker* && "$cmdline" == *"$api_sock"* ]] -} - -pooled_tap() { - local tap="$1" - [[ "$tap" == tap-pool-* ]] -} - -wait_for_ssh() { - local guest_ip="$1" - local deadline="$2" - - while ((SECONDS < deadline)); do - if ssh "${SSH_COMMON_ARGS[@]}" -o ConnectTimeout=2 "root@${guest_ip}" "true" >/dev/null 2>&1; then - return 0 - fi - sleep 1 - done - - return 1 -} - -wait_for_tcp() { - local host="$1" - local port="$2" - local deadline="$3" - - while ((SECONDS < deadline)); do - if (exec 3<>/dev/tcp/"$host"/"$port") >/dev/null 2>&1; then - return 0 - fi - sleep 1 - done - - return 1 -} - -refresh_vm_metadata() { - if ! VM_JSON="$("$BANGER_BIN" vm show "$VM_NAME" 2>/dev/null)"; then - return 1 - fi - TAP="$(printf '%s\n' "$VM_JSON" | jq -r '.runtime.tap_device // empty')" - VM_DIR="$(printf '%s\n' "$VM_JSON" | jq -r '.runtime.vm_dir // empty')" - GUEST_IP="$(printf '%s\n' "$VM_JSON" | jq -r '.runtime.guest_ip // empty')" - API_SOCK="$(printf '%s\n' "$VM_JSON" | jq -r '.runtime.api_sock_path // empty')" - PID="$(printf '%s\n' "$VM_JSON" | jq -r '.runtime.pid // 0')" - VM_STATE="$(printf '%s\n' "$VM_JSON" | jq -r '.state // empty')" - LAST_ERROR="$(printf '%s\n' "$VM_JSON" | jq -r '.runtime.last_error // empty')" - return 0 -} - -wait_for_vm_ready() { - local deadline="$1" - - while ((SECONDS < deadline)); do - if ! refresh_vm_metadata; then - sleep 1 - continue - fi - if [[ "$VM_STATE" == "error" || -n "$LAST_ERROR" ]]; then - return 2 - fi - if [[ -n "$API_SOCK" && "${PID:-0}" -gt 0 ]] && ! firecracker_running "$PID" "$API_SOCK"; then - return 3 - fi - if [[ "$VM_STATE" == "running" && -n "$GUEST_IP" && -n "$TAP" && -n "$VM_DIR" && -n "$API_SOCK" && "${PID:-0}" -gt 0 ]]; then - if [[ -S "$API_SOCK" ]] && ip link show "$TAP" >/dev/null 2>&1; then - return 0 - fi - fi - sleep 1 - done - - return 1 -} - -dump_diagnostics() { - log "diagnostics for $VM_NAME" - "$BANGER_BIN" vm show "$VM_NAME" || true - if [[ "${PID:-0}" -gt 0 ]]; then - log "process state for pid $PID" - ps -fp "$PID" || true - fi - log "recent firecracker log" - "$BANGER_BIN" vm logs "$VM_NAME" 2>/dev/null | tail -n 200 || true - if [[ -f "$DAEMON_LOG" ]]; then - log "recent daemon log" - tail -n 200 "$DAEMON_LOG" || true - fi - if [[ -n "${TAP:-}" ]]; then - log "tap state for $TAP" - ip link show "$TAP" || true - fi - if [[ -n "${API_SOCK:-}" ]]; then - log "api socket $API_SOCK" - ls -l "$API_SOCK" 2>/dev/null || true - fi - if (( NAT_ENABLED )) && [[ -n "${UPLINK:-}" && -n "${GUEST_IP:-}" && -n "${TAP:-}" ]]; then - log "nat rules for ${GUEST_IP} via ${UPLINK}" - sudo iptables -t nat -S POSTROUTING | grep "${GUEST_IP}/32" || true - sudo iptables -S FORWARD | grep "$TAP" || true - fi -} - -usage() { - cat <<'EOF' -Usage: ./scripts/verify.sh [--nat] [--image ] - -Run a basic smoke test for the Go VM workflow. -Use --nat to additionally verify outbound NAT and host rule cleanup. -Use --image to verify a non-default image such as void. -EOF -} - -NAT_ENABLED=0 -IMAGE_NAME="" -BOOT_TIMEOUT_SECS="${VERIFY_BOOT_TIMEOUT_SECS:-90}" -while [[ $# -gt 0 ]]; do - case "$1" in - --nat) - NAT_ENABLED=1 - shift - ;; - --image) - IMAGE_NAME="${2:-}" - if [[ -z "$IMAGE_NAME" ]]; then - usage - exit 1 - fi - shift 2 - ;; - *) - usage - exit 1 - ;; - esac -done - -VM_NAME="verify-$(date +%s)" -VM_JSON="" -TAP="" -VM_DIR="" -GUEST_IP="" -UPLINK="" -API_SOCK="" -PID="0" -VM_STATE="" -LAST_ERROR="" - -delete_vm() { - if [[ -n "${VM_NAME:-}" ]]; then - "$BANGER_BIN" vm delete "$VM_NAME" - fi -} - -cleanup() { - if [[ -n "${VM_NAME:-}" ]]; then - "$BANGER_BIN" vm delete "$VM_NAME" >/dev/null 2>&1 || true - fi -} - -trap cleanup EXIT - -log "starting VM" -CREATE_ARGS=("$BANGER_BIN" vm create --name "$VM_NAME") -if [[ -n "$IMAGE_NAME" ]]; then - CREATE_ARGS+=(--image "$IMAGE_NAME") -fi -if (( NAT_ENABLED )); then - CREATE_ARGS+=(--nat) -fi -"${CREATE_ARGS[@]}" >/dev/null - -BOOT_DEADLINE=$((SECONDS + BOOT_TIMEOUT_SECS)) - -log "waiting for VM runtime readiness" -if wait_for_vm_ready "$BOOT_DEADLINE"; then - : -else - status=$? - case "$status" in - 2) log "vm entered an error state before becoming ready" ;; - 3) log "firecracker exited before the guest became ready" ;; - *) log "vm did not become ready before timeout" ;; - esac - dump_diagnostics - exit 1 -fi - -if (( NAT_ENABLED )); then - UPLINK="$(ip route show default 2>/dev/null | awk '/default/ {print $5; exit}')" - if [[ -z "$UPLINK" ]]; then - log "failed to detect uplink interface" - exit 1 - fi - log "asserting NAT rules are installed" - sudo iptables -t nat -C POSTROUTING -s "${GUEST_IP}/32" -o "$UPLINK" -j MASQUERADE - sudo iptables -C FORWARD -i "$TAP" -o "$UPLINK" -j ACCEPT - sudo iptables -C FORWARD -i "$UPLINK" -o "$TAP" -m state --state RELATED,ESTABLISHED -j ACCEPT -fi - -log "asserting VM is reachable via SSH" -if ! wait_for_ssh "$GUEST_IP" "$BOOT_DEADLINE"; then - log "ssh did not become ready for ${GUEST_IP}" - dump_diagnostics - exit 1 -fi -ssh "${SSH_COMMON_ARGS[@]}" "root@${GUEST_IP}" "uname -a" >/dev/null - -log "asserting opencode is available and listening in the guest" -ssh "${SSH_COMMON_ARGS[@]}" "root@${GUEST_IP}" "command -v opencode >/dev/null 2>&1 && ss -H -lntp | awk '\$4 ~ /:${OPENCODE_PORT}\$/ { found = 1 } END { exit found ? 0 : 1 }'" >/dev/null - -log "asserting opencode server is reachable from the host" -if ! wait_for_tcp "$GUEST_IP" "$OPENCODE_PORT" "$BOOT_DEADLINE"; then - log "opencode server did not become reachable at ${GUEST_IP}:${OPENCODE_PORT}" - dump_diagnostics - exit 1 -fi - -log "asserting opencode port is reported by banger vm ports" -if ! "$BANGER_BIN" vm ports "$VM_NAME" | grep -F ":${OPENCODE_PORT}" >/dev/null 2>&1; then - log "banger vm ports did not report ${OPENCODE_PORT}" - dump_diagnostics - exit 1 -fi - -if (( NAT_ENABLED )); then - log "asserting VM has outbound network access" - ssh "${SSH_COMMON_ARGS[@]}" "root@${GUEST_IP}" "curl -fsS https://example.com >/dev/null" >/dev/null -fi - -log "cleaning up VM" -if ! delete_vm; then - log "vm delete failed for $VM_NAME" - dump_diagnostics - exit 1 -fi - -log "asserting cleanup success" -if "$BANGER_BIN" vm show "$VM_NAME" >/dev/null 2>&1; then - log "vm still exists after delete: $VM_NAME" - exit 1 -fi -if ip link show "$TAP" >/dev/null 2>&1; then - if pooled_tap "$TAP"; then - log "tap returned to idle pool: $TAP" - else - log "tap still exists: $TAP" - exit 1 - fi -fi -if [[ -d "$VM_DIR" ]]; then - log "vm dir still exists: $VM_DIR" - exit 1 -fi -if (( NAT_ENABLED )); then - if sudo iptables -t nat -C POSTROUTING -s "${GUEST_IP}/32" -o "$UPLINK" -j MASQUERADE 2>/dev/null; then - log "nat rule still exists for ${GUEST_IP}" - exit 1 - fi - if sudo iptables -C FORWARD -i "$TAP" -o "$UPLINK" -j ACCEPT 2>/dev/null; then - log "forward-out rule still exists for ${TAP}" - exit 1 - fi - if sudo iptables -C FORWARD -i "$UPLINK" -o "$TAP" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then - log "forward-in rule still exists for ${TAP}" - exit 1 - fi -fi - -log "ok" diff --git a/todos b/todos new file mode 100644 index 0000000..e5c22a9 --- /dev/null +++ b/todos @@ -0,0 +1,15 @@ +when developing, vm creation may fail, and firecracker logs need to be manually looked into, we should add a convenient way of digging through it. or perhaps log the last lines of it when vm creation fails + +`banger vm run` can hang waiting for ssh if things go south with sshd inside the vm. I think we should have a timeout instead of hanging forever. And also log what the user can do in such scenario. + +some commands are expected to take a while, it'd be good to at least show an indicator that banger is not hanging but rather doing something expected + +perhaps add an "interactive flag"? + +my computer is not the usual computer that users may have. perhaps it would be a good idea to screen the hardware we're working on so that we can set reasonable defaults for people when installing? + +versioning and releasing could use some love + +coverage would be somewhat nice to have + +regular users have no idea how to point their machine DNS to use the banger dns server. they need a tutorial/docs for this