mirror of
https://github.com/LIV2/packer-plugin-qemu.git
synced 2025-12-06 07:02:45 +00:00
builder: add EFI-specific boot values
BIOS and (U)EFI are two standard for booting machines. BIOS being legacy, which is still supported by several OSes, but is getting phased out little-by-little, in favour of EFI. EFI requires several components to boot a machine: a GPT-backed disk, a firmware to load the bootloaders, and efivars to keep persistent data in NVRAM between boots. This is translated in qemu as a collection of a code file that contains the firmware, and a vars file that holds a template efivars, which may have some keys or properties setup. This commit adds new configuration values to enable this. At its simplest, this might be just enabling `efi_enabled` to boot with EFI support. In practice, YMMV, as some protocols (typically Secure Boot) will require more setup before being able to get something to work as expected. The EFI CODE and VARS files are automatically loaded from /usr/share/OVMF if available, otherwise they need to be specified manually. The docs for Qemu are updated to reflect these changes.
This commit is contained in:
parent
2348cbe3d0
commit
4076d6da34
@ -111,6 +111,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
||||
NetBridge: b.config.NetBridge,
|
||||
},
|
||||
new(stepConfigureVNC),
|
||||
&stepPrepareEfivars{
|
||||
EFIEnabled: b.config.QemuEFIBootConfig.EnableEFI,
|
||||
OutputDir: b.config.OutputDir,
|
||||
SourcePath: b.config.QemuEFIBootConfig.OVMFVars,
|
||||
},
|
||||
&stepRun{
|
||||
DiskImage: b.config.DiskImage,
|
||||
},
|
||||
|
||||
@ -161,6 +161,50 @@ func (c QemuSMPConfig) getMaxCPUs() int {
|
||||
return totalVCPUs
|
||||
}
|
||||
|
||||
// Booting in EFI mode
|
||||
//
|
||||
// Use these options if wanting to boot on a UEFI firmware, as the options to
|
||||
// do so are different from what BIOS (default) booting will require.
|
||||
type QemuEFIBootConfig struct {
|
||||
// Boot in EFI mode instead of BIOS. This is required for more modern
|
||||
// guest OS. If either or both of `efi_firmware_code` or
|
||||
// `efi_firmware_vars` are defined, this will implicitely be set to `true`.
|
||||
//
|
||||
// NOTE: when using a Secure-Boot enabled firmware, the machine type has
|
||||
// to be q35, otherwise qemu will not boot.
|
||||
EnableEFI bool `mapstructure:"efi_boot" required:"false"`
|
||||
// Path to the CODE part of OVMF (or other compatible firmwares)
|
||||
// The OVMF_CODE.fd file contains the bootstrap code for booting in EFI
|
||||
// mode, and requires a separate VARS.fd file to be able to persist data
|
||||
// between boot cycles.
|
||||
//
|
||||
// Default: /usr/share/OVMF/OVMF_CODE.fd
|
||||
OVMFCode string `mapstructure:"efi_firmware_code" required:"false"`
|
||||
// Path to the VARS corresponding to the OVMF code file.
|
||||
//
|
||||
// Default: /usr/share/OVMF/OVMF_VARS.fd
|
||||
OVMFVars string `mapstructure:"efi_firmware_vars" required:"false"`
|
||||
}
|
||||
|
||||
func (efiCfg *QemuEFIBootConfig) loadDefaults() {
|
||||
// Auto enable EFI if either of the Code/Vars path is set
|
||||
if efiCfg.OVMFCode != "" || efiCfg.OVMFVars != "" {
|
||||
efiCfg.EnableEFI = true
|
||||
}
|
||||
|
||||
if !efiCfg.EnableEFI {
|
||||
return
|
||||
}
|
||||
|
||||
if efiCfg.OVMFCode == "" {
|
||||
efiCfg.OVMFCode = "/usr/share/OVMF/OVMF_CODE.fd"
|
||||
}
|
||||
|
||||
if efiCfg.OVMFVars == "" {
|
||||
efiCfg.OVMFVars = "/usr/share/OVMF/OVMF_VARS.fd"
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
commonsteps.HTTPConfig `mapstructure:",squash"`
|
||||
@ -171,6 +215,7 @@ type Config struct {
|
||||
commonsteps.FloppyConfig `mapstructure:",squash"`
|
||||
commonsteps.CDConfig `mapstructure:",squash"`
|
||||
QemuSMPConfig `mapstructure:",squash"`
|
||||
QemuEFIBootConfig `mapstructure:",squash"`
|
||||
// Use iso from provided url. Qemu must support
|
||||
// curl block device. This defaults to `false`.
|
||||
ISOSkipCache bool `mapstructure:"iso_skip_cache" required:"false"`
|
||||
@ -203,17 +248,21 @@ type Config struct {
|
||||
// Each additional disk uses the same disk parameters as the default disk.
|
||||
// Unset by default.
|
||||
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false"`
|
||||
// The firmware file to be used by QEMU
|
||||
// this option could be set to use EFI instead of BIOS,
|
||||
// by using "OVMF.fd" from OpenFirmware, for example.
|
||||
// The firmware file to be used by QEMU.
|
||||
// If unset, QEMU will load its default firmware.
|
||||
// Also see the QEMU documentation.
|
||||
//
|
||||
// NOTE: when booting in UEFI mode, please use the `efi_` options to
|
||||
// setup the firmware.
|
||||
Firmware string `mapstructure:"firmware" required:"false"`
|
||||
// If a firmware file option was provided, this option can be
|
||||
// used to change how qemu will get it.
|
||||
// If false (the default), then the firmware is provided through
|
||||
// the -bios option, but if true, a pflash drive will be used
|
||||
// instead.
|
||||
//
|
||||
// NOTE: when booting in UEFI mode, please use the `efi_` options to
|
||||
// setup the firmware.
|
||||
PFlash bool `mapstructure:"use_pflash" required:"false"`
|
||||
// The interface to use for the disk. Allowed values include any of `ide`,
|
||||
// `sata`, `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot
|
||||
@ -288,6 +337,11 @@ type Config struct {
|
||||
// The type of machine emulation to use. Run your qemu binary with the
|
||||
// flags `-machine help` to list available types for your system. This
|
||||
// defaults to `pc`.
|
||||
//
|
||||
// NOTE: when booting a UEFI machine with Secure Boot enabled, this has
|
||||
// to be a q35 derivative.
|
||||
// If the machine is not a q35 derivative, nothing will boot (not even
|
||||
// an EFI shell).
|
||||
MachineType string `mapstructure:"machine_type" required:"false"`
|
||||
// The amount of memory to use when building the VM
|
||||
// in megabytes. This defaults to 512 megabytes.
|
||||
@ -646,6 +700,8 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c.TPMType = "tpm-tis"
|
||||
}
|
||||
|
||||
c.QemuEFIBootConfig.loadDefaults()
|
||||
|
||||
errs = packersdk.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
|
||||
errs = packersdk.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
|
||||
errs = packersdk.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
|
||||
|
||||
@ -101,6 +101,9 @@ type FlatConfig struct {
|
||||
SocketCount *int `mapstructure:"sockets" required:"false" cty:"sockets" hcl:"sockets"`
|
||||
CoreCount *int `mapstructure:"cores" required:"false" cty:"cores" hcl:"cores"`
|
||||
ThreadCount *int `mapstructure:"threads" required:"false" cty:"threads" hcl:"threads"`
|
||||
EnableEFI *bool `mapstructure:"efi_boot" required:"false" cty:"efi_boot" hcl:"efi_boot"`
|
||||
OVMFCode *string `mapstructure:"efi_firmware_code" required:"false" cty:"efi_firmware_code" hcl:"efi_firmware_code"`
|
||||
OVMFVars *string `mapstructure:"efi_firmware_vars" required:"false" cty:"efi_firmware_vars" hcl:"efi_firmware_vars"`
|
||||
ISOSkipCache *bool `mapstructure:"iso_skip_cache" required:"false" cty:"iso_skip_cache" hcl:"iso_skip_cache"`
|
||||
Accelerator *string `mapstructure:"accelerator" required:"false" cty:"accelerator" hcl:"accelerator"`
|
||||
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
||||
@ -246,6 +249,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"sockets": &hcldec.AttrSpec{Name: "sockets", Type: cty.Number, Required: false},
|
||||
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
|
||||
"threads": &hcldec.AttrSpec{Name: "threads", Type: cty.Number, Required: false},
|
||||
"efi_boot": &hcldec.AttrSpec{Name: "efi_boot", Type: cty.Bool, Required: false},
|
||||
"efi_firmware_code": &hcldec.AttrSpec{Name: "efi_firmware_code", Type: cty.String, Required: false},
|
||||
"efi_firmware_vars": &hcldec.AttrSpec{Name: "efi_firmware_vars", Type: cty.String, Required: false},
|
||||
"iso_skip_cache": &hcldec.AttrSpec{Name: "iso_skip_cache", Type: cty.Bool, Required: false},
|
||||
"accelerator": &hcldec.AttrSpec{Name: "accelerator", Type: cty.String, Required: false},
|
||||
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.String), Required: false},
|
||||
|
||||
72
builder/qemu/step_prepare_efivars.go
Normal file
72
builder/qemu/step_prepare_efivars.go
Normal file
@ -0,0 +1,72 @@
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
// stepPrepareEfivars copies the EFIVars file to the output, so we can boot
|
||||
// and use it as a RW flash drive
|
||||
type stepPrepareEfivars struct {
|
||||
EFIEnabled bool
|
||||
OutputDir string
|
||||
SourcePath string
|
||||
}
|
||||
|
||||
const efivarStateKey string = "EFI_VARS_FILE_PATH"
|
||||
|
||||
func (s *stepPrepareEfivars) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
|
||||
if !s.EFIEnabled {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(s.OutputDir, "efivars.fd")
|
||||
outFile, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, 0660)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("failed to create local efivars file at %s: %s", dstPath, err)
|
||||
ui.Error(errMsg)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
state.Put(efivarStateKey, dstPath)
|
||||
|
||||
inFile, err := os.Open(s.SourcePath)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("failed to read from efivars file at %s: %s", s.SourcePath, err)
|
||||
ui.Error(errMsg)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, inFile)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("failed to copy efivars data: %s", err)
|
||||
ui.Error(errMsg)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepPrepareEfivars) Cleanup(state multistep.StateBag) {
|
||||
if !s.EFIEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
efiVarFile, ok := state.GetOk(efivarStateKey)
|
||||
// If the path isn't in state, we can assume it's not been created and
|
||||
// therefore we have nothing to cleanup
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
os.Remove(efiVarFile.(string))
|
||||
}
|
||||
@ -287,11 +287,21 @@ func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag
|
||||
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,index=%d,id=cdrom%d,media=cdrom", cdPath, config.CDROMInterface, i, i))
|
||||
}
|
||||
}
|
||||
|
||||
// Firmware
|
||||
if config.Firmware != "" && config.PFlash {
|
||||
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=pflash,format=raw,readonly=on", config.Firmware))
|
||||
}
|
||||
|
||||
// EFI
|
||||
if config.QemuEFIBootConfig.EnableEFI {
|
||||
// CODE binary is loaded readonly
|
||||
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=pflash,unit=0,format=raw,readonly=on", config.QemuEFIBootConfig.OVMFCode))
|
||||
efivar := state.Get(efivarStateKey)
|
||||
// the local copy of VARS is not
|
||||
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=pflash,unit=1,format=raw", efivar.(string)))
|
||||
}
|
||||
|
||||
// TPM
|
||||
if config.VTPM {
|
||||
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,tpmdev=tpm0", config.TPMType))
|
||||
|
||||
@ -32,17 +32,21 @@
|
||||
Each additional disk uses the same disk parameters as the default disk.
|
||||
Unset by default.
|
||||
|
||||
- `firmware` (string) - The firmware file to be used by QEMU
|
||||
this option could be set to use EFI instead of BIOS,
|
||||
by using "OVMF.fd" from OpenFirmware, for example.
|
||||
- `firmware` (string) - The firmware file to be used by QEMU.
|
||||
If unset, QEMU will load its default firmware.
|
||||
Also see the QEMU documentation.
|
||||
|
||||
NOTE: when booting in UEFI mode, please use the `efi_` options to
|
||||
setup the firmware.
|
||||
|
||||
- `use_pflash` (bool) - If a firmware file option was provided, this option can be
|
||||
used to change how qemu will get it.
|
||||
If false (the default), then the firmware is provided through
|
||||
the -bios option, but if true, a pflash drive will be used
|
||||
instead.
|
||||
|
||||
NOTE: when booting in UEFI mode, please use the `efi_` options to
|
||||
setup the firmware.
|
||||
|
||||
- `disk_interface` (string) - The interface to use for the disk. Allowed values include any of `ide`,
|
||||
`sata`, `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot
|
||||
@ -117,6 +121,11 @@
|
||||
- `machine_type` (string) - The type of machine emulation to use. Run your qemu binary with the
|
||||
flags `-machine help` to list available types for your system. This
|
||||
defaults to `pc`.
|
||||
|
||||
NOTE: when booting a UEFI machine with Secure Boot enabled, this has
|
||||
to be a q35 derivative.
|
||||
If the machine is not a q35 derivative, nothing will boot (not even
|
||||
an EFI shell).
|
||||
|
||||
- `memory` (int) - The amount of memory to use when building the VM
|
||||
in megabytes. This defaults to 512 megabytes.
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<!-- Code generated from the comments of the QemuEFIBootConfig struct in builder/qemu/config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `efi_boot` (bool) - Boot in EFI mode instead of BIOS. This is required for more modern
|
||||
guest OS. If either or both of `efi_firmware_code` or
|
||||
`efi_firmware_vars` are defined, this will implicitely be set to `true`.
|
||||
|
||||
- `efi_firmware_code` (string) - Path to the CODE part of OVMF (or other compatible firmwares)
|
||||
The OVMF_CODE.fd file contains the bootstrap code for booting in EFI
|
||||
mode, and requires a separate VARS.fd file to be able to persist data
|
||||
between boot cycles.
|
||||
|
||||
Default: /usr/share/OVMF/OVMF_CODE.fd
|
||||
|
||||
- `efi_firmware_vars` (string) - Path to the VARS corresponding to the OVMF code file.
|
||||
|
||||
Default: /usr/share/OVMF/OVMF_VARS.fd
|
||||
|
||||
<!-- End of code generated from the comments of the QemuEFIBootConfig struct in builder/qemu/config.go; -->
|
||||
8
docs-partials/builder/qemu/QemuEFIBootConfig.mdx
Normal file
8
docs-partials/builder/qemu/QemuEFIBootConfig.mdx
Normal file
@ -0,0 +1,8 @@
|
||||
<!-- Code generated from the comments of the QemuEFIBootConfig struct in builder/qemu/config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
Booting in EFI mode
|
||||
|
||||
Use these options if wanting to boot on a UEFI firmware, as the options to
|
||||
do so are different from what BIOS (default) booting will require.
|
||||
|
||||
<!-- End of code generated from the comments of the QemuEFIBootConfig struct in builder/qemu/config.go; -->
|
||||
52
example/efi_build/efi-debian.pkr.hcl
Normal file
52
example/efi_build/efi-debian.pkr.hcl
Normal file
@ -0,0 +1,52 @@
|
||||
source "qemu" "debian_efi" {
|
||||
iso_url = "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-11.5.0-amd64-netinst.iso"
|
||||
iso_checksum = "sha256:e307d0e583b4a8f7e5b436f8413d4707dd4242b70aea61eb08591dc0378522f3"
|
||||
communicator = "ssh"
|
||||
ssh_username = "root"
|
||||
ssh_password = "root"
|
||||
ssh_timeout = "30m"
|
||||
output_directory = "./out"
|
||||
memory = "1024"
|
||||
disk_size = "6G"
|
||||
cpus = 4
|
||||
format = "qcow2"
|
||||
accelerator = "kvm"
|
||||
vm_name = "debian_efi"
|
||||
# headless = "false" # uncomment to see the boot process in a qemu window
|
||||
machine_type = "q35" # As of now, q35 is required for secure boot to be enabled
|
||||
boot_command = [
|
||||
"<enter>FS0:<enter>EFI\\boot\\bootx64.efi<enter>",
|
||||
"<wait><down><down><enter>", # manual install
|
||||
"<wait><down><down><down><down><down><enter>", # automatic install
|
||||
"<wait30>", # wait 30s for preseed prompt
|
||||
"http://{{.HTTPIP}}:{{.HTTPPort}}/preseed.cfg<tab><enter>",
|
||||
"<wait><enter>", # select English as language/locale
|
||||
"<wait><enter>", # select English as language
|
||||
"<wait><enter>", # set English-US as keyboard layout
|
||||
"<wait><wait><wait>root<enter>", # set root password
|
||||
"<wait>root<enter>", # confirm root password
|
||||
"<wait>debian<enter>", # set machine name to debian
|
||||
"<wait><enter>", # set user to debian
|
||||
"<wait>debian<enter>", # set password to debian
|
||||
"<wait>debian<enter>", # confirm password to debian
|
||||
"<wait180>", # wait 3m for system to install
|
||||
"root<enter>root<enter>sed -Ei 's/^#.*PermitRootLogin.*$/PermitRootLogin yes/' /etc/ssh/sshd_config<enter>systemctl restart sshd<enter>exit<enter>" # configure sshd to allow root connection
|
||||
]
|
||||
http_directory = "http"
|
||||
boot_wait = "3s"
|
||||
qemuargs = [
|
||||
["-cpu", "host"],
|
||||
["-vga","virtio"] # if vga is not virtio, output is garbled for some reason
|
||||
]
|
||||
vtpm = true
|
||||
efi_firmware_code = "./efi_data/OVMF_CODE_4M.ms.fd"
|
||||
efi_firmware_vars = "./efi_data/OVMF_VARS_4M.ms.fd"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.qemu.debian_efi"]
|
||||
|
||||
provisioner "shell" {
|
||||
inline = [ "dmesg | grep -qi 'Secure boot enabled' && echo \"Secure Boot is on!\"" ]
|
||||
}
|
||||
}
|
||||
BIN
example/efi_build/efi_data/OVMF_CODE_4M.ms.fd
Normal file
BIN
example/efi_build/efi_data/OVMF_CODE_4M.ms.fd
Normal file
Binary file not shown.
BIN
example/efi_build/efi_data/OVMF_VARS_4M.ms.fd
Normal file
BIN
example/efi_build/efi_data/OVMF_VARS_4M.ms.fd
Normal file
Binary file not shown.
22
example/efi_build/http/preseed.cfg
Normal file
22
example/efi_build/http/preseed.cfg
Normal file
@ -0,0 +1,22 @@
|
||||
choose-mirror-bin mirror/http/proxy string
|
||||
d-i debian-installer/framebuffer boolean false
|
||||
d-i debconf/frontend select noninteractive
|
||||
d-i base-installer/kernel/override-image string linux-server
|
||||
d-i clock-setup/utc boolean true
|
||||
d-i clock-setup/utc-auto boolean true
|
||||
d-i finish-install/reboot_in_progress note
|
||||
d-i grub-installer/only_debian boolean true
|
||||
d-i grub-installer/with_other_os boolean true
|
||||
d-i partman-auto/method string regular
|
||||
d-i partman/choose_partition select finish
|
||||
d-i partman/confirm boolean true
|
||||
d-i partman/confirm_nooverwrite boolean true
|
||||
d-i partman/confirm_write_new_label boolean true
|
||||
d-i pkgsel/include string openssh-server
|
||||
d-i pkgsel/install-language-support boolean false
|
||||
d-i pkgsel/update-policy select none
|
||||
d-i pkgsel/upgrade select full-upgrade
|
||||
d-i time/zone string UTC
|
||||
d-i user-setup/allow-password-weak boolean true
|
||||
d-i user-setup/encrypt-home boolean false
|
||||
tasksel tasksel/first multiselect standard, ubuntu-server
|
||||
Loading…
x
Reference in New Issue
Block a user