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.
101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"banger/internal/config"
|
|
"banger/internal/model"
|
|
"banger/internal/paths"
|
|
"banger/internal/system"
|
|
)
|
|
|
|
func Doctor(ctx context.Context) (system.Report, error) {
|
|
layout, err := paths.Resolve()
|
|
if err != nil {
|
|
return system.Report{}, err
|
|
}
|
|
cfg, err := config.Load(layout)
|
|
if err != nil {
|
|
return system.Report{}, err
|
|
}
|
|
d := &Daemon{
|
|
layout: layout,
|
|
config: cfg,
|
|
runner: system.NewRunner(),
|
|
}
|
|
return d.doctorReport(ctx), nil
|
|
}
|
|
|
|
func (d *Daemon) doctorReport(ctx context.Context) system.Report {
|
|
report := system.Report{}
|
|
|
|
report.AddPreflight("runtime bundle", d.runtimeBundleChecks(), runtimeBundleStatus(d.config))
|
|
report.AddPreflight("core vm lifecycle", d.coreVMLifecycleChecks(), "required host tools available")
|
|
report.AddPreflight("vsock guest agent", d.vsockChecks(), "vsock agent prerequisites available")
|
|
d.addCapabilityDoctorChecks(ctx, &report)
|
|
report.AddPreflight("image build", d.imageBuildChecks(ctx), "image build prerequisites available")
|
|
|
|
return report
|
|
}
|
|
|
|
func (d *Daemon) runtimeBundleChecks() *system.Preflight {
|
|
checks := system.NewPreflight()
|
|
hint := paths.RuntimeBundleHint()
|
|
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(d.config.DefaultRootfs, "default rootfs image", `set "default_rootfs" or refresh the runtime bundle`)
|
|
checks.RequireFile(d.config.DefaultKernel, "kernel image", `set "default_kernel" or refresh the runtime bundle`)
|
|
if strings.TrimSpace(d.config.DefaultInitrd) != "" {
|
|
checks.RequireFile(d.config.DefaultInitrd, "initrd image", `set "default_initrd" or refresh the runtime bundle`)
|
|
}
|
|
if strings.TrimSpace(d.config.DefaultPackagesFile) != "" {
|
|
checks.RequireFile(d.config.DefaultPackagesFile, "package manifest", `set "default_packages_file" or refresh the runtime bundle`)
|
|
}
|
|
return checks
|
|
}
|
|
|
|
func (d *Daemon) coreVMLifecycleChecks() *system.Preflight {
|
|
checks := system.NewPreflight()
|
|
d.addBaseStartCommandPrereqs(checks)
|
|
return checks
|
|
}
|
|
|
|
func (d *Daemon) imageBuildChecks(ctx context.Context) *system.Preflight {
|
|
checks := system.NewPreflight()
|
|
d.addImageBuildPrereqs(
|
|
ctx,
|
|
checks,
|
|
firstNonEmpty(d.config.DefaultBaseRootfs, d.config.DefaultRootfs),
|
|
d.config.DefaultKernel,
|
|
d.config.DefaultInitrd,
|
|
d.config.DefaultModulesDir,
|
|
"",
|
|
)
|
|
return checks
|
|
}
|
|
|
|
func (d *Daemon) vsockChecks() *system.Preflight {
|
|
checks := system.NewPreflight()
|
|
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")
|
|
return checks
|
|
}
|
|
|
|
func runtimeBundleStatus(cfg model.DaemonConfig) string {
|
|
if strings.TrimSpace(cfg.RuntimeDir) == "" {
|
|
return "runtime dir not configured"
|
|
}
|
|
return fmt.Sprintf("runtime dir %s", cfg.RuntimeDir)
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|