Add experimental Void guest workflow and vsock agent

Make iterating on a Firecracker-friendly Void guest practical without replacing the Debian default image path.

Add local Void rootfs build/register/verify plumbing, a language-agnostic dev package baseline, and guest SSH/work-disk hardening so new images use the runtime bundle key, keep a normal root bash environment, and repair stale nested /root layouts on restart.

Replace the guest PING/PONG responder with an HTTP /healthz agent over vsock, rename the runtime bundle and config surface from ping helper to agent while still accepting the legacy keys, and route the post-SSH reminder through the new vm.health path.

Validated with GOCACHE=/tmp/banger-gocache go test ./..., make build, bash -n customize.sh make-rootfs-void.sh, and git diff --check.
This commit is contained in:
Thales Maciel 2026-03-19 14:51:25 -03:00
parent c8d9a122f9
commit 3ed78fdcfc
No known key found for this signature in database
GPG key ID: 33112E6833C34679
42 changed files with 2222 additions and 388 deletions

View file

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
@ -118,6 +119,23 @@ func TestVMCreateFlagsExist(t *testing.T) {
}
}
func TestImageRegisterFlagsExist(t *testing.T) {
root := NewBangerCommand()
image, _, err := root.Find([]string{"image"})
if err != nil {
t.Fatalf("find image: %v", err)
}
register, _, err := image.Find([]string{"register"})
if err != nil {
t.Fatalf("find register: %v", err)
}
for _, flagName := range []string{"name", "rootfs", "work-seed", "kernel", "initrd", "modules", "packages", "docker"} {
if register.Flags().Lookup(flagName) == nil {
t.Fatalf("missing flag %q", flagName)
}
}
}
func TestVMKillFlagsExist(t *testing.T) {
root := NewBangerCommand()
vm, _, err := root.Find([]string{"vm"})
@ -211,19 +229,58 @@ func TestVMSetParamsFromFlagsRejectsNonPositiveCPUAndMemory(t *testing.T) {
}
}
func TestRunSSHSessionPrintsReminderWhenPingAlive(t *testing.T) {
func TestAbsolutizeImageRegisterPaths(t *testing.T) {
tmp := t.TempDir()
params := api.ImageRegisterParams{
RootfsPath: filepath.Join(".", "runtime", "rootfs-void.ext4"),
WorkSeedPath: filepath.Join(".", "runtime", "rootfs-void.work-seed.ext4"),
KernelPath: filepath.Join(".", "runtime", "vmlinux"),
InitrdPath: filepath.Join(".", "runtime", "initrd.img"),
ModulesDir: filepath.Join(".", "runtime", "modules"),
PackagesPath: filepath.Join(".", "packages.void"),
}
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd: %v", err)
}
if err := os.Chdir(tmp); err != nil {
t.Fatalf("Chdir(%s): %v", tmp, err)
}
t.Cleanup(func() {
_ = os.Chdir(wd)
})
if err := absolutizeImageRegisterPaths(&params); err != nil {
t.Fatalf("absolutizeImageRegisterPaths: %v", err)
}
for _, value := range []string{
params.RootfsPath,
params.WorkSeedPath,
params.KernelPath,
params.InitrdPath,
params.ModulesDir,
params.PackagesPath,
} {
if !filepath.IsAbs(value) {
t.Fatalf("path %q is not absolute", value)
}
}
}
func TestRunSSHSessionPrintsReminderWhenHealthCheckPasses(t *testing.T) {
origSSHExec := sshExecFunc
origPing := vmPingFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmPingFunc = origPing
vmHealthFunc = origHealth
})
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return nil
}
vmPingFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMPingResult, error) {
return api.VMPingResult{Name: "devbox", Alive: true}, nil
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
return api.VMHealthResult{Name: "devbox", Healthy: true}, nil
}
var stderr bytes.Buffer
@ -235,19 +292,19 @@ func TestRunSSHSessionPrintsReminderWhenPingAlive(t *testing.T) {
}
}
func TestRunSSHSessionPreservesSSHExitStatusOnPingWarning(t *testing.T) {
func TestRunSSHSessionPreservesSSHExitStatusOnHealthWarning(t *testing.T) {
origSSHExec := sshExecFunc
origPing := vmPingFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmPingFunc = origPing
vmHealthFunc = origHealth
})
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return &exec.ExitError{}
return exitErrorWithCode(t, 1)
}
vmPingFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMPingResult, error) {
return api.VMPingResult{}, errors.New("dial failed")
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
return api.VMHealthResult{}, errors.New("dial failed")
}
var stderr bytes.Buffer
@ -261,6 +318,37 @@ func TestRunSSHSessionPreservesSSHExitStatusOnPingWarning(t *testing.T) {
}
}
func TestRunSSHSessionSkipsReminderOnSSHAuthFailure(t *testing.T) {
origSSHExec := sshExecFunc
origHealth := vmHealthFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmHealthFunc = origHealth
})
healthCalled := false
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return exitErrorWithCode(t, 255)
}
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
healthCalled = true
return api.VMHealthResult{Name: "devbox", Healthy: true}, nil
}
var stderr bytes.Buffer
err := runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"})
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) || exitErr.ExitCode() != 255 {
t.Fatalf("runSSHSession error = %v, want exit 255", err)
}
if healthCalled {
t.Fatal("vm health should not run after ssh auth failure")
}
if strings.Contains(stderr.String(), "still running") {
t.Fatalf("stderr = %q, should not contain reminder", stderr.String())
}
}
func TestResolveVMTargetsDeduplicatesAndReportsErrors(t *testing.T) {
vms := []model.VMRecord{
testCLIResolvedVM("alpha-id", "alpha"),
@ -358,7 +446,13 @@ func TestSSHCommandArgs(t *testing.T) {
t.Fatalf("sshCommandArgs: %v", err)
}
want := []string{
"-F", "/dev/null",
"-i", "/bundle/id_ed25519",
"-o", "IdentitiesOnly=yes",
"-o", "BatchMode=yes",
"-o", "PreferredAuthentications=publickey",
"-o", "PasswordAuthentication=no",
"-o", "KbdInteractiveAuthentication=no",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"root@172.16.0.2",
@ -381,6 +475,17 @@ func TestValidateSSHPrereqs(t *testing.T) {
}
}
func exitErrorWithCode(t *testing.T, code int) *exec.ExitError {
t.Helper()
cmd := exec.Command("bash", "-lc", fmt.Sprintf("exit %d", code))
err := cmd.Run()
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
t.Fatalf("exitErrorWithCode(%d) error = %v, want exit error", code, err)
}
return exitErr
}
func TestValidateSSHPrereqsFailsForMissingKey(t *testing.T) {
err := validateSSHPrereqs(model.DaemonConfig{SSHKeyPath: "/does/not/exist"})
if err == nil || !strings.Contains(err.Error(), "ssh private key") {