package daemon import ( "context" "strings" "banger/internal/model" "banger/internal/paths" "banger/internal/system" ) var vsockHostDevicePath = "/dev/vhost-vsock" func (d *Daemon) validateStartPrereqs(ctx context.Context, vm model.VMRecord, image model.Image) error { checks := system.NewPreflight() d.addBaseStartPrereqs(checks, image) d.addCapabilityStartPrereqs(ctx, checks, vm, image) return checks.Err("vm start preflight failed") } func (d *Daemon) validateImageBuildPrereqs(ctx context.Context, baseRootfs, kernelPath, initrdPath, modulesDir, sizeSpec string) error { checks := system.NewPreflight() d.addImageBuildPrereqs(ctx, checks, baseRootfs, kernelPath, initrdPath, modulesDir, sizeSpec) return checks.Err("image build preflight failed") } func (d *Daemon) validateWorkDiskResizePrereqs() error { checks := system.NewPreflight() checks.RequireCommand("truncate", toolHint("truncate")) checks.RequireCommand("e2fsck", `install e2fsprogs`) checks.RequireCommand("resize2fs", `install e2fsprogs`) return checks.Err("work disk resize preflight failed") } func (d *Daemon) addNATPrereqs(ctx context.Context, checks *system.Preflight) { checks.RequireCommand("iptables", toolHint("iptables")) checks.RequireCommand("sysctl", toolHint("sysctl")) runner := d.runner if runner == nil { runner = system.NewRunner() } out, err := runner.Run(ctx, "ip", "route", "show", "default") if err != nil { checks.Addf("failed to inspect the default route for NAT: %v", err) return } if _, err := parseDefaultUplink(string(out)); err != nil { checks.Addf("failed to detect the uplink interface for NAT: %v", err) } } func (d *Daemon) addBaseStartPrereqs(checks *system.Preflight, image model.Image) { hint := paths.RuntimeBundleHint() d.addBaseStartCommandPrereqs(checks) checks.RequireExecutable(d.config.FirecrackerBin, "firecracker binary", hint) checks.RequireExecutable(d.config.VSockAgentPath, "vsock agent", `run 'make build' or refresh the runtime bundle`) checks.RequireFile(vsockHostDevicePath, "vsock host device", "load the vhost_vsock kernel module on the host") checks.RequireFile(image.RootfsPath, "rootfs image", "select a valid image or rebuild the runtime bundle") checks.RequireFile(image.KernelPath, "kernel image", `set "default_kernel" or refresh the runtime bundle`) if strings.TrimSpace(image.InitrdPath) != "" { checks.RequireFile(image.InitrdPath, "initrd image", `set "default_initrd" or refresh the runtime bundle`) } } func (d *Daemon) addBaseStartCommandPrereqs(checks *system.Preflight) { for _, command := range []string{"sudo", "ip", "dmsetup", "losetup", "blockdev", "truncate", "pgrep", "chown", "chmod", "kill", "e2cp", "e2rm", "debugfs"} { checks.RequireCommand(command, toolHint(command)) } } func (d *Daemon) addImageBuildPrereqs(ctx context.Context, checks *system.Preflight, baseRootfs, kernelPath, initrdPath, modulesDir, sizeSpec string) { hint := paths.RuntimeBundleHint() for _, command := range []string{"sudo", "ip", "pgrep", "chown", "chmod", "kill"} { checks.RequireCommand(command, toolHint(command)) } for _, command := range []string{"mkfs.ext4", "mount", "umount", "cp"} { checks.RequireCommand(command, toolHint(command)) } checks.RequireExecutable(d.config.FirecrackerBin, "firecracker binary", hint) checks.RequireFile(d.config.SSHKeyPath, "runtime ssh private key", `refresh the runtime bundle`) checks.RequireExecutable(d.config.VSockAgentPath, "vsock agent", `run 'make build' or refresh the runtime bundle`) checks.RequireFile(baseRootfs, "base rootfs image", `pass --base-rootfs or set "default_base_rootfs"`) checks.RequireFile(kernelPath, "kernel image", `pass --kernel or set "default_kernel"`) checks.RequireFile(d.config.DefaultPackagesFile, "package manifest", `set "default_packages_file" or refresh the runtime bundle`) if strings.TrimSpace(initrdPath) != "" { checks.RequireFile(initrdPath, "initrd image", `pass --initrd or set "default_initrd"`) } if strings.TrimSpace(modulesDir) != "" { checks.RequireDir(modulesDir, "modules directory", `pass --modules or set "default_modules_dir"`) } if strings.TrimSpace(d.config.DefaultPackagesFile) != "" { if _, err := system.ReadNormalizedLines(d.config.DefaultPackagesFile); err != nil { checks.Addf("package manifest at %s is invalid: %v", d.config.DefaultPackagesFile, err) } } if strings.TrimSpace(sizeSpec) != "" { checks.RequireCommand("e2fsck", toolHint("e2fsck")) checks.RequireCommand("resize2fs", toolHint("resize2fs")) } d.addNATPrereqs(ctx, checks) } func toolHint(command string) string { switch command { case "ip": return "install iproute2" case "iptables": return "install iptables" case "sysctl", "losetup", "blockdev", "mount", "umount": return "install util-linux" case "dmsetup": return "install device-mapper" case "pgrep", "kill": return "install procps" case "chown", "chmod", "cp", "truncate": return "install coreutils" case "e2fsck", "resize2fs", "debugfs", "mkfs.ext4": return "install e2fsprogs" case "e2cp", "e2rm": return "install e2tools" case "sudo": return "install sudo" default: return "" } }