Skip to content

Commit

Permalink
feat: generate iso's with both UKI and grub
Browse files Browse the repository at this point in the history
Starting with Talos 1.10, the default generated ISO's will use GRUB for
BIOS boot and sd-boot for EFI boot.

Fixes: siderolabs#10192

Signed-off-by: Noel Georgi <[email protected]>
  • Loading branch information
frezbo committed Jan 24, 2025
1 parent f407c88 commit 16924f6
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 131 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2025-01-22T17:37:55Z by kres 3075de9.
# Generated on 2025-01-24T14:30:35Z by kres 3075de9.

name: default
concurrency:
Expand Down Expand Up @@ -2199,6 +2199,16 @@ jobs:
WITH_UEFI: "false"
run: |
sudo -E make e2e-qemu
- name: e2e-bios-iso
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-bios-iso
IMAGE_REGISTRY: registry.dev.siderolabs.io
SHORT_INTEGRATION_TEST: "yes"
VIA_MAINTENANCE_MODE: "true"
WITH_ISO: "true"
WITH_UEFI: "false"
run: |
sudo -E make e2e-qemu
- name: e2e-disk-image
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/integration-misc-2-cron.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2024-12-18T13:55:17Z by kres b9507d6.
# Generated on 2025-01-24T14:30:35Z by kres 3075de9.

name: integration-misc-2-cron
concurrency:
Expand Down Expand Up @@ -99,6 +99,16 @@ jobs:
WITH_UEFI: "false"
run: |
sudo -E make e2e-qemu
- name: e2e-bios-iso
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-bios-iso
IMAGE_REGISTRY: registry.dev.siderolabs.io
SHORT_INTEGRATION_TEST: "yes"
VIA_MAINTENANCE_MODE: "true"
WITH_ISO: "true"
WITH_UEFI: "false"
run: |
sudo -E make e2e-qemu
- name: e2e-disk-image
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image
Expand Down
10 changes: 10 additions & 0 deletions .kres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,16 @@ spec:
SHORT_INTEGRATION_TEST: yes
WITH_UEFI: false
IMAGE_REGISTRY: registry.dev.siderolabs.io
- name: e2e-bios-iso
command: e2e-qemu
withSudo: true
environment:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-bios-iso
SHORT_INTEGRATION_TEST: yes
WITH_UEFI: false
VIA_MAINTENANCE_MODE: true
WITH_ISO: true
IMAGE_REGISTRY: registry.dev.siderolabs.io
- name: e2e-disk-image
command: e2e-qemu
withSudo: true
Expand Down
1 change: 1 addition & 0 deletions hack/test/e2e-qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ case "${WITH_ISO:-false}" in
false)
;;
*)
INSTALLER_IMAGE=${INSTALLER_IMAGE}-amd64-secureboot # we don't use secureboot part here, but this installer contains UKIs
QEMU_FLAGS+=("--iso-path=${ARTIFACTS}/metal-amd64.iso")
;;
esac
Expand Down
4 changes: 2 additions & 2 deletions pkg/imager/imager.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
if !needBuildUKI {
return "", fmt.Errorf("UKI output is not supported in this Talos version")
}
case profile.OutKindISO, profile.OutKindImage:
case profile.OutKindImage:
needBuildUKI = needBuildUKI && i.prof.SecureBootEnabled()
case profile.OutKindInstaller:
case profile.OutKindISO, profile.OutKindInstaller:
needBuildUKI = needBuildUKI || quirks.New(i.prof.Version).UseSDBootForUEFI()
case profile.OutKindCmdline, profile.OutKindKernel, profile.OutKindInitramfs:
needBuildUKI = false
Expand Down
82 changes: 18 additions & 64 deletions pkg/imager/iso/grub.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,28 @@ package iso
import (
"bytes"
_ "embed"
"fmt"
"os"
"path/filepath"
"text/template"
"time"

"github.com/siderolabs/go-cmd/pkg/cmd"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/siderolabs/talos/pkg/imager/utils"
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
)

// GRUBOptions described the input for the CreateGRUB function.
type GRUBOptions struct {
KernelPath string
InitramfsPath string
Cmdline string
Version string

ScratchDir string

OutPath string
}

//go:embed grub.cfg
var grubCfgTemplate string

// CreateGRUB creates a GRUB-based ISO image.
//
// This iso supports both BIOS and UEFI booting.
func CreateGRUB(printf func(string, ...any), options GRUBOptions) error {
func (options Options) CreateGRUB(printf func(string, ...any)) (Generator, error) {
if err := utils.CopyFiles(
printf,
utils.SourceDestination(options.KernelPath, filepath.Join(options.ScratchDir, "boot", "vmlinuz")),
utils.SourceDestination(options.InitramfsPath, filepath.Join(options.ScratchDir, "boot", "initramfs.xz")),
); err != nil {
return err
return nil, err
}

printf("creating grub.cfg")
Expand All @@ -57,7 +41,7 @@ func CreateGRUB(printf func(string, ...any), options GRUBOptions) error {
}).
Parse(grubCfgTemplate)
if err != nil {
return err
return nil, err
}

if err = tmpl.Execute(&grubCfg, struct {
Expand All @@ -67,64 +51,34 @@ func CreateGRUB(printf func(string, ...any), options GRUBOptions) error {
Cmdline: options.Cmdline,
AddResetOption: quirks.New(options.Version).SupportsResetGRUBOption(),
}); err != nil {
return err
return nil, err
}

cfgPath := filepath.Join(options.ScratchDir, "boot/grub/grub.cfg")

if err = os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil {
return err
return nil, err
}

if err = os.WriteFile(cfgPath, grubCfg.Bytes(), 0o666); err != nil {
return err
return nil, err
}

if err = utils.TouchFiles(printf, options.ScratchDir); err != nil {
return err
return nil, err
}

printf("creating ISO image")

return grubMkrescue(options)
}

func grubMkrescue(options GRUBOptions) error {
args := []string{
"--compress=xz",
"--output=" + options.OutPath,
"--verbose",
options.ScratchDir,
"--",
}

if epoch, ok, err := utils.SourceDateEpoch(); err != nil {
return err
} else if ok {
// set EFI FAT image serial number
if err := os.Setenv("GRUB_FAT_SERIAL_NUMBER", fmt.Sprintf("%x", uint32(epoch))); err != nil {
return err
}

args = append(args,
"-volume_date", "all_file_dates", fmt.Sprintf("=%d", epoch),
"-volume_date", "uuid", time.Unix(epoch, 0).Format("2006010215040500"),
)
}

if quirks.New(options.Version).SupportsISOLabel() {
label := Label(options.Version, false)

args = append(args,
"-volid", VolumeID(label),
"-volset-id", label,
)
}

_, err := cmd.Run("grub-mkrescue", args...)
if err != nil {
return fmt.Errorf("failed to create ISO: %w", err)
}

return nil
return &ExecutorOptions{
Command: "grub-mkrescue",
Version: options.Version,
Arguments: []string{
"--compress=xz",
"--output=" + options.OutPath,
"--verbose",
options.ScratchDir,
"--",
},
}, nil
}
43 changes: 43 additions & 0 deletions pkg/imager/iso/hybrid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package iso

import "path/filepath"

// CreateHybrid creates an ISO image that supports both BIOS and UEFI booting.
func (options Options) CreateHybrid(printf func(string, ...any)) (Generator, error) {
if _, err := options.CreateGRUB(printf); err != nil {
return nil, err
}

if _, err := options.CreateUEFI(printf); err != nil {
return nil, err
}

efiBootImg := filepath.Join(options.ScratchDir, "efiboot.img")

return &ExecutorOptions{
Command: "grub-mkrescue",
Version: options.Version,
Arguments: []string{
"--compress=xz",
"--output=" + options.OutPath,
"--verbose",
"--directory=/usr/lib/grub/i386-pc", // only for BIOS boot
"-m", "efiboot.img", // exclude the EFI boot image from the ISO
options.ScratchDir,
"-eltorito-alt-boot",
"-e", "--interval:appended_partition_2:all::", // use appended partition 2 for EFI
"-append_partition", "2", "0xef", efiBootImg,
"-appended_part_as_gpt",
"-partition_cyl_align", // pad partition to cylinder boundary
"all",
"-partition_offset", "16", // support booting from USB
"-iso_mbr_part_type", "0x83", // just to have more clear info when doing a fdisk -l
"-no-emul-boot",
"--",
},
}, nil
}
84 changes: 83 additions & 1 deletion pkg/imager/iso/iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@
// Package iso contains functions for creating ISO images.
package iso

import "strings"
import (
"fmt"
"os"
"strings"
"time"

"github.com/siderolabs/go-cmd/pkg/cmd"

"github.com/siderolabs/talos/pkg/imager/utils"
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
)

// VolumeID returns a valid volume ID for the given label.
func VolumeID(label string) string {
Expand Down Expand Up @@ -41,3 +51,75 @@ func Label(version string, secureboot bool) string {

return label + version
}

// ExecutorOptions defines the iso generation options.
type ExecutorOptions struct {
Command string
Version string
Arguments []string
}

// Generator is an interface for executing the iso generation.
type Generator interface {
Generate() error
}

// Options describe the input generating different types of ISOs.
type Options struct {
KernelPath string
InitramfsPath string
Cmdline string

UKIPath string
SDBootPath string

Arch string
Version string

// A value in loader.conf secure-boot-enroll: off, manual, if-safe, force.
SDBootSecureBootEnrollKeys string

// UKISigningCertDer is the DER encoded UKI signing certificate.
UKISigningCertDerPath string

// optional, for auto-enrolling secureboot keys
PlatformKeyPath string
KeyExchangeKeyPath string
SignatureKeyPath string

ScratchDir string
OutPath string
}

// Generate creates an ISO image.
func (e *ExecutorOptions) Generate() error {
if epoch, ok, err := utils.SourceDateEpoch(); err != nil {
return err
} else if ok {
// set EFI FAT image serial number
if err := os.Setenv("GRUB_FAT_SERIAL_NUMBER", fmt.Sprintf("%x", uint32(epoch))); err != nil {
return err
}

e.Arguments = append(e.Arguments,
"-volume_date", "all_file_dates", fmt.Sprintf("=%d", epoch),
"-volume_date", "uuid", time.Unix(epoch, 0).Format("2006010215040500"),
)
}

if quirks.New(e.Version).SupportsISOLabel() {
label := Label(e.Version, false)

e.Arguments = append(e.Arguments,
"-volid", VolumeID(label),
"-volset-id", label,
)
}

_, err := cmd.Run(e.Command, e.Arguments...)
if err != nil {
return fmt.Errorf("failed to create ISO: %w", err)
}

return nil
}
Loading

0 comments on commit 16924f6

Please sign in to comment.