support UKI

This commit is contained in:
Mikaël Cluseau
2026-05-07 23:41:29 +02:00
parent e89b164581
commit 7a6310c93e
7 changed files with 230 additions and 109 deletions
+127 -40
View File
@@ -3,11 +3,13 @@ package main
import (
"archive/tar"
"bytes"
"debug/pe"
"fmt"
"io"
"log"
"os"
"novit.tech/direktil/local-server/pkg/utf16"
"path/filepath"
"time"
)
func rmTempFile(f *os.File) {
@@ -17,7 +19,124 @@ func rmTempFile(f *os.File) {
}
}
func buildBootTar(out io.Writer, ctx *renderContext) (err error) {
func buildUki(out io.Writer, ctx *renderContext, uki bool, cmdline string) error {
if !uki {
return fmt.Errorf("buildUki only builds UKI")
}
const efiStubPath = "/usr/lib/systemd/boot/efi/linuxx64.efi.stub"
stub, err := pe.Open(efiStubPath)
if err != nil {
return fmt.Errorf("efi stub open: %w", err)
}
hdr := stub.OptionalHeader.(*pe.OptionalHeader64)
align := uint64(hdr.SectionAlignment)
var offset uint64
for _, s := range stub.Sections {
end := uint64(s.VirtualAddress) + uint64(s.VirtualSize)
if end > offset {
offset = end
}
}
offset += hdr.ImageBase
stub.Close()
// kernel
kernelPath, err := distFetch("kernels", ctx.Host.Kernel)
if err != nil {
return fmt.Errorf("fetch kernel: %w", err)
}
// temp dir
tempDir, err := os.MkdirTemp(os.TempDir(), "uki-")
if err != nil {
return fmt.Errorf("mkdir temp: %w", err)
}
defer os.RemoveAll(tempDir)
// osrel
osrelPath := filepath.Join(tempDir, "osrel")
if err := os.WriteFile(osrelPath, fmt.Appendf(nil, "ID=direktil\nPRETTY_NAME='Direktil %s %s+0'\n", ctx.Host.Name,
time.Now().UTC().Format(time.DateTime)), 0o600); err != nil {
return fmt.Errorf("create osrel: %w", err)
}
// cmdline
cmdlinePath := filepath.Join(tempDir, "cmdline")
if err := os.WriteFile(cmdlinePath, append([]byte(cmdline), 0), 0o600); err != nil {
return fmt.Errorf("create cmdline: %w", err)
}
// initrd
initrdPath := filepath.Join(tempDir, "initrd")
if err := func() (err error) {
initrd, err := os.Create(initrdPath)
if err != nil {
return
}
defer initrd.Close()
return buildInitrd(initrd, ctx)
}(); err != nil {
return fmt.Errorf("create initrd: %w", err)
}
// assemble
args := make([]string, 0)
for _, i := range []struct {
section string
path string
}{
{"osrel", osrelPath},
{"cmdline", cmdlinePath},
{"initrd", initrdPath},
{"linux", kernelPath},
} {
offset += align - offset%align
args = append(args,
"--add-section", "."+i.section+"="+i.path, "--change-section-vma", fmt.Sprintf(".%s=0x%x", i.section, offset))
stat, err := os.Stat(i.path)
if err != nil {
return fmt.Errorf("stat %s: %w", i.section, err)
}
offset += uint64(stat.Size())
}
ukiPath := filepath.Join(tempDir, "uki")
args = append(args, efiStubPath, ukiPath)
if err := run("objcopy", args...); err != nil {
return fmt.Errorf("objcopy: %w", err)
}
// read
ukiBytes, err := os.ReadFile(ukiPath)
if err != nil {
return fmt.Errorf("read uki: %w", err)
}
io.Copy(out, bytes.NewBuffer(ukiBytes))
// done
return nil
}
func buildBootTar(out io.Writer, ctx *renderContext, uki bool, cmdline string) (err error) {
if uki {
return buildUkiBootTar(out, ctx, cmdline)
} else {
return buildGrubBootTar(out, ctx)
}
}
func buildGrubBootTar(out io.Writer, ctx *renderContext) (err error) {
arch := tar.NewWriter(out)
defer arch.Close()
@@ -62,7 +181,7 @@ func buildBootTar(out io.Writer, ctx *renderContext) (err error) {
return nil
}
func buildBootEFITar(out io.Writer, ctx *renderContext) (err error) {
func buildUkiBootTar(out io.Writer, ctx *renderContext, cmdline string) (err error) {
arch := tar.NewWriter(out)
defer arch.Close()
@@ -75,46 +194,14 @@ func buildBootEFITar(out io.Writer, ctx *renderContext) (err error) {
return
}
const (
prefix = "EFI/dkl/"
efiPrefix = "\\EFI\\dkl\\"
)
// boot.csv
// -> annoyingly it's UTF-16...
bootCsvBytes := utf16.FromUTF8([]byte("" +
"current_kernel.efi,dkl current,initrd=" + efiPrefix + "current_initrd.img,Direktil current\n" +
"previous_kernel.efi,dkl previous,initrd=" + efiPrefix + "previous_initrd.img,Direktil previous\n"))
err = archAdd(prefix+"BOOT.CSV", []byte(bootCsvBytes))
// UKI
uki := new(bytes.Buffer)
err = buildUki(uki, ctx, true, cmdline)
if err != nil {
return
}
// kernel
kernelPath, err := distFetch("kernels", ctx.Host.Kernel)
if err != nil {
return
}
kernelBytes, err := os.ReadFile(kernelPath)
if err != nil {
return
}
err = archAdd(prefix+"current_kernel.efi", kernelBytes)
if err != nil {
return
}
// initrd
initrd := new(bytes.Buffer)
err = buildInitrd(initrd, ctx)
if err != nil {
return
}
err = archAdd(prefix+"current_initrd.img", initrd.Bytes())
err = archAdd("EFI/BOOT/BOOTX64.EFI", uki.Bytes())
if err != nil {
return
}