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 "" }