Boot KVM guests via UEFI by default; per-image firmware setting

Run 5 on the real host bisected the guest hang: the Debian 13 image
hangs after GRUB under SeaBIOS with or without our seed and with either
CPU model, and boots under OVMF. The image's legacy boot chain is the
problem, not the provider.

images.<key>.firmware is "uefi" (default) or "bios". The domain XML
emits <os firmware="efi"> with secure boot off, so libvirt selects the
OVMF binary and manages the NVRAM file, which the existing nvram undefine
flag removes. Direct-URL images default to UEFI too. OVMF joins the
hypervisor prerequisites; sample, config table, known gaps, decision log
and the execution log record the finding.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 19:13:51 +00:00
co-authored by Claude Fable 5.1
parent 5a6c889531
commit 9a393b1df8
12 changed files with 151 additions and 26 deletions
+9 -3
View File
@@ -28,6 +28,8 @@ const (
defaultPool = "default"
formatQcow2 = "qcow2"
formatRaw = "raw"
firmwareUEFI = "uefi"
firmwareBIOS = "bios"
// undefineFlags removes everything libvirt may keep alongside a domain
// definition, so an undefine never fails on leftover state.
@@ -48,6 +50,9 @@ func withDefaults(cfg provider.KVMConfig) provider.KVMConfig {
if img.Format == "" {
img.Format = formatQcow2
}
if img.Firmware == "" {
img.Firmware = firmwareUEFI
}
images[k] = img
}
cfg.Images = images
@@ -199,7 +204,7 @@ func (p *Provider) Create(ctx context.Context, req provider.CreateRequest) (stri
if err != nil {
return fmt.Errorf("resolving seed path: %w", err)
}
dom, err = p.defineDomain(log, api, host, req, mt, diskPath, seedPath)
dom, err = p.defineDomain(log, api, host, req, mt, src.Firmware, diskPath, seedPath)
if err != nil {
return err
}
@@ -263,13 +268,14 @@ func (p *Provider) writeSeed(log logr.Logger, api libvirtAPI, pool libvirt.Stora
return vol, nil
}
func (p *Provider) defineDomain(log logr.Logger, api libvirtAPI, host provider.KVMHost, req provider.CreateRequest, mt provider.KVMMachineType, diskPath, seedPath string) (libvirt.Domain, error) {
func (p *Provider) defineDomain(log logr.Logger, api libvirtAPI, host provider.KVMHost, req provider.CreateRequest, mt provider.KVMMachineType, firmware, diskPath, seedPath string) (libvirt.Domain, error) {
xml, err := buildDomainXML(domainParams{
Name: req.Name,
UID: req.UID,
CreatedAt: p.now(),
VCPUs: mt.VCPUs,
MemoryMiB: mt.MemoryMiB,
Firmware: firmware,
Bridge: host.Bridge,
DiskPath: diskPath,
SeedPath: seedPath,
@@ -281,7 +287,7 @@ func (p *Provider) defineDomain(log logr.Logger, api libvirtAPI, host provider.K
if err != nil {
return libvirt.Domain{}, fmt.Errorf("defining domain: %w", err)
}
log.V(1).Info("domain defined", "uid", req.UID)
log.V(1).Info("domain defined", "uid", req.UID, "firmware", firmware)
return dom, nil
}