Add an experimental Alpine image flow

Stage a complete Alpine x86_64 image stack so \	--image alpineworks like the existing manual Void path instead of relying on Debian-oriented image builds.\n\nAdd make targets plus kernel/rootfs/register helpers that download pinned Alpine artifacts, extract a Firecracker-compatible vmlinux, build a matching mkinitfs initramfs, seed OpenRC services, and register/promote a managed image named alpine.\n\nFold in the bring-up fixes discovered during boot validation: use rootfstype=ext4 in shared boot args, install libgcc/libstdc++ for the opencode binary, and give opencode more time to become ready on cold boots.\n\nValidate with go test ./..., the Alpine helper builds, image promotion, and banger vm create --image alpine --name alp --nat plus guest service and port checks.
This commit is contained in:
Thales Maciel 2026-03-21 20:25:55 -03:00
parent 572bf32424
commit a166068fab
No known key found for this signature in database
GPG key ID: 33112E6833C34679
14 changed files with 1307 additions and 9 deletions

View file

@ -175,9 +175,9 @@ func newInternalVSockAgentPathCommand() *cobra.Command {
func newInternalPackagesCommand() *cobra.Command {
var docker bool
cmd := &cobra.Command{
Use: "packages <debian|void>",
Use: "packages <debian|void|alpine>",
Hidden: true,
Args: exactArgsUsage(1, "usage: banger internal packages <debian|void> [--docker]"),
Args: exactArgsUsage(1, "usage: banger internal packages <debian|void|alpine> [--docker]"),
RunE: func(cmd *cobra.Command, args []string) error {
var packages []string
switch strings.TrimSpace(args[0]) {
@ -188,6 +188,8 @@ func newInternalPackagesCommand() *cobra.Command {
}
case "void":
packages = imagepreset.VoidBasePackages()
case "alpine":
packages = imagepreset.AlpineBasePackages()
default:
return fmt.Errorf("unknown package preset %q", args[0])
}

View file

@ -111,6 +111,24 @@ 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 TestVMCreateFlagsExist(t *testing.T) {
root := NewBangerCommand()
vm, _, err := root.Find([]string{"vm"})

View file

@ -43,6 +43,31 @@ var voidBase = []string{
"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...)
}
@ -51,6 +76,10 @@ 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)

View file

@ -17,7 +17,7 @@ const (
ShimPath = "/root/.local/share/mise/shims/opencode"
ServiceName = "banger-opencode.service"
RunitServiceName = "banger-opencode"
ReadyTimeout = 15 * time.Second
ReadyTimeout = 45 * time.Second
pollInterval = 200 * time.Millisecond
)

View file

@ -417,7 +417,7 @@ func UpdateFSTab(existing string) string {
func BuildBootArgs(vmName, guestIP, bridgeIP, dns string) string {
return fmt.Sprintf(
"console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=%s::%s:255.255.255.0:%s:eth0:off:%s hostname=%s systemd.mask=home.mount systemd.mask=var.mount",
"console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rootfstype=ext4 rw ip=%s::%s:255.255.255.0:%s:eth0:off:%s hostname=%s systemd.mask=home.mount systemd.mask=var.mount",
guestIP,
bridgeIP,
vmName,

View file

@ -171,7 +171,7 @@ func TestBuildBootArgsIncludesHostnameInIPField(t *testing.T) {
t.Parallel()
got := BuildBootArgs("devbox", "172.16.0.2", "172.16.0.1", "1.1.1.1")
want := "console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=172.16.0.2::172.16.0.1:255.255.255.0:devbox:eth0:off:1.1.1.1 hostname=devbox systemd.mask=home.mount systemd.mask=var.mount"
want := "console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rootfstype=ext4 rw ip=172.16.0.2::172.16.0.1:255.255.255.0:devbox:eth0:off:1.1.1.1 hostname=devbox systemd.mask=home.mount systemd.mask=var.mount"
if got != want {
t.Fatalf("BuildBootArgs() = %q, want %q", got, want)
}