banger/internal/daemon/doctor.go
Thales Maciel 08ef706e3f
Add vsock-backed SSH session reminders
Remind users when a VM is still running after 	hanger vm ssh exits instead of silently dropping them back to the host shell.\n\nAttach a Firecracker vsock device to each VM, persist the host vsock path/CID,\nadd a new guest-side banger-vsock-pingd responder to the runtime bundle and both\nimage-build paths, and expose a vm.ping RPC that the CLI and TUI call after SSH\nreturns. Doctor and start/build preflight now validate the helper plus\n/dev/vhost-vsock so the feature fails early and clearly.\n\nValidated with go mod tidy, bash -n customize.sh, git diff --check, make build,\nand GOCACHE=/tmp/banger-gocache go test ./... outside the sandbox because the\ndaemon tests need real Unix/UDP sockets. Rebuild the image/rootfs used for new\nVMs so the guest ping service is present.
2026-03-18 20:14:51 -03:00

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 ssh reminder", d.vsockChecks(), "vsock reminder 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, "ssh private key", `set "ssh_key_path" or refresh the runtime bundle`)
checks.RequireExecutable(d.config.VSockPingHelperPath, "vsock ping helper", `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.VSockPingHelperPath, "vsock ping helper", `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 ""
}