Skip to content

systemd-boot — BLS reader + integration

github.com/go-bootloaders/systemd-boot is a pure-Go (CGO_ENABLED=0) reader and integration layer for systemd-boot, following the Boot Loader Specification.

It is a production-ready consumer of the pure-Go storage / firmware / boot stack: point it at a whole disk image and it GPT-locates the EFI System Partition, mounts its FAT32 filesystem, and returns resolved BLS boot entries — then optionally reads the UEFI boot-variable state and measures the selected kernel/initrd/cmdline into TPM PCRs, all with no cgo and no shell-outs.

What it does

Capability Backed by
Parse /loader/loader.conf + /loader/entries/*.conf → resolved Boot entries (linux/initrd/options, BLS sort order, wildcard default) this package
Disk image → ESP → resolved entries with zero manual FS plumbing go-volumes/gpt + go-filesystems/detect (fat32reg)
Read UEFI BootOrder/Boot#### LoadOptions, set BootNext/BootOrder, find which Boot#### is systemd-boot go-filesystems/uefi
Measured boot: extend kernel (PCR 4) / cmdline (PCR 8) / initrd (PCR 9) into a TPM + emit a TCG event log go-tpm2 (tpm2, attest, common)

Disk-image → BLS entries

OpenImage does the whole pipeline — GPT-locate the ESP, detect + mount its FAT32 filesystem, adapt it to the reader, parse loader.conf — behind a single call. The caller MUST Close the returned Image.

im, err := systemdboot.OpenImage("/dev/sda")     // or a disk-image file
defer im.Close()

def, _ := im.Default()        // the active entry (loader.conf default, or BLS-highest)
all, _ := im.Entries()        // every entry, newest first

Callers who already hold a mounted ESP filesystem (any go-filesystems driver) skip the disk plumbing with NewFS:

fs := systemdboot.NewFS(mountedESP)          // filesystem.Filesystem → FS
boot, _ := systemdboot.Default(fs, "/")      // espRoot within the mount

UEFI boot-variable state

store, _ := uefi.Open("OVMF_VARS.fd")
defer store.Close()

state, _ := systemdboot.ReadBootState(store)
// state.Order, state.Entries, state.SystemdBootSlots, state.BootNext

systemdboot.SetBootNext(store, state.SystemdBootSlots[0])  // boot sd-boot once
systemdboot.MakeSystemdBootFirst(store)                    // make it default

Measured boot (optional, TPM-free by default)

// nil Extender → pure event-log dry run, no TPM touched.
res, _ := systemdboot.MeasureBoot(im, def, nil, 512<<20)

// or measure into hardware over a crb/tis transport:
tpm := tpm2.New(transport)
res, _ := systemdboot.MeasureBoot(im, def, tpm, 512<<20)

The TPM dependency sits behind the injected Extender interface, so the tool builds and runs with no TPM present.

Why this exists

The conventional /boot/{vmlinuz,Image}-* glob misses every systemd-boot install — NixOS, Clear Linux, and bespoke Arch/Gentoo setups put the kernel at content-addressed /EFI/<distro>/<hash>-* paths selected through /loader/entries/*.conf. Reading the canonical BLS path covers the whole ecosystem at once.

Status

Validated on all six 64-bit Go architectures (amd64, arm64, riscv64, loong64, ppc64le, and big-endian s390x). 92.9% test coverage. All dependencies resolve from the public Go module proxy by pseudo-version — no replace => ../sibling, no local sibling clone, no vendoring. No tagged release yet.

License

BSD-3-Clause — see LICENSE.