The `image build` flow spun up a transient Firecracker VM, SSHed in, and ran a large bash provisioning script to derive a new managed image from an existing one. It overlapped heavily with the golden- image Dockerfile flow (same mise/docker/tmux/opencode install logic duplicated in Go as `imagemgr.BuildProvisionScript`) and had far more machinery: async op state, RPC begin/status/cancel, webui form + operation page, preflight checks, API types, tests. For custom images, writing a Dockerfile is simpler and more reproducible. Removed end-to-end: - CLI `image build` subcommand + `absolutizeImageBuildPaths`. - Daemon: BuildImage method, imagebuild.go (transient-VM orchestration), image_build_ops.go (async begin/status/cancel), imagemgr/build.go (the 247-line provisioning script generator and all its append* helpers), validateImageBuildPrereqs + addImageBuildPrereqs. - RPC dispatches for image.build / .begin / .status / .cancel. - opstate registry `imageBuildOps`, daemon seam `imageBuild`, background pruner call. - API types: ImageBuildParams, ImageBuildOperation, ImageBuildBeginResult, ImageBuildStatusParams, ImageBuildStatusResult; model type ImageBuildRequest. - Web UI: Backend interface methods, handlers, form, routes, template branches (images.html build form, operation.html build branch, dashboard.html Build button). - Tests that directly exercised BuildImage. Doctor polish (task C): - Drop the "image build" preflight section entirely (its raison d'être is gone). - Default-image check now accepts "not local but in imagecat" as OK: vm create auto-pulls on first use. Only flag when the image is neither locally registered nor in the catalog. Net: 24 files touched, 1,373 lines deleted, 25 added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
3 KiB
Go
90 lines
3 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"banger/internal/model"
|
|
"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) 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) {
|
|
d.addBaseStartCommandPrereqs(checks)
|
|
checks.RequireExecutable(d.config.FirecrackerBin, "firecracker binary", `install firecracker or set "firecracker_bin"`)
|
|
if helper, err := d.vsockAgentBinary(); err == nil {
|
|
checks.RequireExecutable(helper, "vsock agent helper", `run 'make build' or reinstall banger`)
|
|
} else {
|
|
checks.Addf("%v", err)
|
|
}
|
|
checks.RequireFile(vsockHostDevicePath, "vsock host device", "load the vhost_vsock kernel module on the host")
|
|
checks.RequireFile(image.RootfsPath, "rootfs image", "select a valid registered image")
|
|
checks.RequireFile(image.KernelPath, "kernel image", `re-register or rebuild the image with a valid kernel`)
|
|
if strings.TrimSpace(image.InitrdPath) != "" {
|
|
checks.RequireFile(image.InitrdPath, "initrd image", `re-register or rebuild the image with a valid initrd`)
|
|
}
|
|
}
|
|
|
|
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 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 ""
|
|
}
|
|
}
|