Skip to content

Commit

Permalink
feat: dual boot disk image
Browse files Browse the repository at this point in the history
Generate disk image with both grub and sd-boot.

Fixes: #10332

Signed-off-by: Noel Georgi <[email protected]>
  • Loading branch information
frezbo committed Feb 25, 2025
1 parent 468e318 commit b1d410c
Show file tree
Hide file tree
Showing 122 changed files with 3,000 additions and 45 deletions.
15 changes: 14 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-02-15T06:21:12Z by kres 8a48729.
# Generated on 2025-02-16T17:00:53Z by kres 8a48729.

name: default
concurrency:
Expand Down Expand Up @@ -2232,6 +2232,17 @@ jobs:
WITH_DISK_ENCRYPTION: "true"
run: |
sudo -E make e2e-qemu
- name: e2e-disk-image-bios
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image-bios
IMAGE_REGISTRY: registry.dev.siderolabs.io
SHORT_INTEGRATION_TEST: "yes"
USE_DISK_IMAGE: "true"
VIA_MAINTENANCE_MODE: "true"
WITH_DISK_ENCRYPTION: "true"
WITH_UEFI: "false"
run: |
sudo -E make e2e-qemu
- name: e2e-node-address-v2
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image
Expand Down Expand Up @@ -2856,8 +2867,10 @@ jobs:
QEMU_EXTRA_DISKS: "3"
QEMU_EXTRA_DISKS_DRIVERS: ide,nvme
QEMU_EXTRA_DISKS_SIZE: "10240"
USER_DISKS_MOUNTS: /var/lib/extra,/var/lib/p1,/var/lib/p2
WITH_CONFIG_PATCH_WORKER: '@hack/test/patches/ephemeral-nvme.yaml:@hack/test/patches/dm-raid-module.yaml'
WITH_JSON_LOGS: "true"
WITH_USER_DISK: "true"
run: |
sudo -E make e2e-qemu
- name: save artifacts
Expand Down
13 changes: 12 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 2025-02-15T06:21:12Z by kres 8a48729.
# Generated on 2025-02-15T07:52:36Z by kres 8a48729.

name: integration-misc-2-cron
concurrency:
Expand Down Expand Up @@ -125,6 +125,17 @@ jobs:
WITH_DISK_ENCRYPTION: "true"
run: |
sudo -E make e2e-qemu
- name: e2e-disk-image-bios
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image-bios
IMAGE_REGISTRY: registry.dev.siderolabs.io
SHORT_INTEGRATION_TEST: "yes"
USE_DISK_IMAGE: "true"
VIA_MAINTENANCE_MODE: "true"
WITH_DISK_ENCRYPTION: "true"
WITH_UEFI: "false"
run: |
sudo -E make e2e-qemu
- name: e2e-node-address-v2
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/integration-qemu-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-11-28T12:57:52Z by kres 232fe63.
# Generated on 2025-02-16T17:00:53Z by kres 8a48729.

name: integration-qemu-cron
concurrency:
Expand Down Expand Up @@ -84,8 +84,10 @@ jobs:
QEMU_EXTRA_DISKS: "3"
QEMU_EXTRA_DISKS_DRIVERS: ide,nvme
QEMU_EXTRA_DISKS_SIZE: "10240"
USER_DISKS_MOUNTS: /var/lib/extra,/var/lib/p1,/var/lib/p2
WITH_CONFIG_PATCH_WORKER: '@hack/test/patches/ephemeral-nvme.yaml:@hack/test/patches/dm-raid-module.yaml'
WITH_JSON_LOGS: "true"
WITH_USER_DISK: "true"
run: |
sudo -E make e2e-qemu
- name: save artifacts
Expand Down
13 changes: 13 additions & 0 deletions .kres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ spec:
QEMU_EXTRA_DISKS_DRIVERS: "ide,nvme"
WITH_CONFIG_PATCH_WORKER: "@hack/test/patches/ephemeral-nvme.yaml:@hack/test/patches/dm-raid-module.yaml"
WITH_JSON_LOGS: "true"
WITH_USER_DISK: "true"
USER_DISKS_MOUNTS: "/var/lib/extra,/var/lib/p1,/var/lib/p2"
- name: save-talos-logs
conditions:
- always
Expand Down Expand Up @@ -870,6 +872,17 @@ spec:
VIA_MAINTENANCE_MODE: true
WITH_DISK_ENCRYPTION: true
IMAGE_REGISTRY: registry.dev.siderolabs.io
- name: e2e-disk-image-bios
command: e2e-qemu
withSudo: true
environment:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-disk-image-bios
SHORT_INTEGRATION_TEST: yes
USE_DISK_IMAGE: true
VIA_MAINTENANCE_MODE: true
WITH_DISK_ENCRYPTION: true
WITH_UEFI: false
IMAGE_REGISTRY: registry.dev.siderolabs.io
- name: e2e-node-address-v2
command: e2e-qemu
withSudo: true
Expand Down
16 changes: 10 additions & 6 deletions cmd/installer/pkg/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ type Options struct {
ImageCachePath string

// Options specific for the image creation mode.
ImageSecureboot bool
Version string
BootAssets bootloaderoptions.BootAssets
Printf func(string, ...any)
MountPrefix string
ImageSecureboot bool
DiskImageBootloader string
Version string
BootAssets bootloaderoptions.BootAssets
Printf func(string, ...any)
MountPrefix string
}

// Mode is the install mode.
Expand Down Expand Up @@ -285,7 +286,10 @@ func (i *Installer) Install(ctx context.Context, mode Mode) (err error) {
switch mode {
case ModeImage:
// on image creation, we don't care about disk contents
bootlder = bootloader.New(i.options.ImageSecureboot, i.options.Version)
bootlder, err = bootloader.New(i.options.DiskImageBootloader, i.options.Version, i.options.Arch)
if err != nil {
return fmt.Errorf("failed to create bootloader: %w", err)
}
case ModeInstall:
if !i.options.Zero && !i.options.Force {
// verify that the disk is either empty or has an empty GPT partition table, otherwise fail the install
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ require (
github.com/siderolabs/gen v0.8.0
github.com/siderolabs/go-api-signature v0.3.6
github.com/siderolabs/go-blockdevice v0.4.8
github.com/siderolabs/go-blockdevice/v2 v2.0.14
github.com/siderolabs/go-blockdevice/v2 v2.0.15
github.com/siderolabs/go-circular v0.2.1
github.com/siderolabs/go-cmd v0.1.3
github.com/siderolabs/go-copy v0.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -647,8 +647,8 @@ github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr
github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U=
github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c=
github.com/siderolabs/go-blockdevice v0.4.8/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
github.com/siderolabs/go-blockdevice/v2 v2.0.14 h1:9Nu4ceeKpCSUhSub6RbxU2eat5IwAOR11Vdb5mPVASo=
github.com/siderolabs/go-blockdevice/v2 v2.0.14/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w=
github.com/siderolabs/go-blockdevice/v2 v2.0.15 h1:22EZ3w125OdPBobckwGZOILhAGo4xUdAn9/OQ5tR/Ys=
github.com/siderolabs/go-blockdevice/v2 v2.0.15/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w=
github.com/siderolabs/go-circular v0.2.1 h1:a++iVCn9jyhICX3POQZZX8n72p2h5JGdGU6w1ulmpcA=
github.com/siderolabs/go-circular v0.2.1/go.mod h1:ZDItzVyXK+B/XuqTBV5MtQtSv06VI+oCmWGRnNCATo8=
github.com/siderolabs/go-cmd v0.1.3 h1:JrgZwqhJQeoec3QRON0LK+fv+0y7d0DyY7zsfkO6ciw=
Expand Down
23 changes: 23 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ The NQN can be read by `talosctl read /etc/nvme/hostnqn`
title = "ISO"
description = """\
Talos starting with 1.10 will have ISO's that will use GRUB only for legacy BIOS and systemd-boot for modern UEFI systems.
"""

[notes.disk-image]
title = "Disk Image"
description = """\
Talos starting with 1.10 will have disk images that will use GRUB only for legacy BIOS and systemd-boot for modern UEFI systems.
On first boot Talos determines the boot method and will wipe the unused bootloader.
Secureboot disk-images will be sd-boot only.
For ARM64 imager will still generate GRUB bootloader for Talos < 1.10 and for Talos >= 1.10 all ARM64 boot assets will use systemd-boot.
Imager supports overwriting bootloader when generating a disk image via the Imager profile `output` option.
Eg:
```yaml
output:
kind: image
imageOptions:
bootloader: sd-boot # supported options are sd-boot, grub, dual-boot
```
"""

[notes.ethernet]
Expand Down
13 changes: 9 additions & 4 deletions hack/test/e2e-qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

set -eou pipefail

export USER_DISKS_MOUNTS="/var/lib/extra,/var/lib/p1,/var/lib/p2"

# shellcheck source=/dev/null
source ./hack/test/e2e.sh

Expand Down Expand Up @@ -248,6 +246,15 @@ case "${WITH_UKI_BOOT:-false}" in
;;
esac

case "${WITH_USER_DISK:-false}" in
false)
;;
*)
QEMU_FLAGS+=("--user-disk=/var/lib/extra:350MB")
QEMU_FLAGS+=("--user-disk=/var/lib/p1:350MB:/var/lib/p2:350MB")
;;
esac


function create_cluster {
build_registry_mirrors
Expand All @@ -268,8 +275,6 @@ function create_cluster {
--cpus="${QEMU_CPUS:-2}" \
--cpus-workers="${QEMU_CPUS_WORKERS:-2}" \
--cidr=172.20.1.0/24 \
--user-disk=/var/lib/extra:350MB \
--user-disk=/var/lib/p1:350MB:/var/lib/p2:350MB \
--install-image="${INSTALLER_IMAGE}" \
--with-init-node=false \
--cni-bundle-url="${ARTIFACTS}/talosctl-cni-bundle-\${ARCH}.tar.gz" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@
package bootloader

import (
"fmt"
"os"

"github.com/siderolabs/go-blockdevice/v2/block"
"github.com/siderolabs/go-blockdevice/v2/partitioning/gpt"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/dual"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot"
"github.com/siderolabs/talos/internal/pkg/partition"
"github.com/siderolabs/talos/pkg/imager/profile"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
)

Expand Down Expand Up @@ -67,14 +74,85 @@ func NewAuto() Bootloader {
return grub.NewConfig()
}

// New returns a new bootloader based on the secureboot flag.
func New(secureboot bool, talosVersion string) Bootloader {
if secureboot {
return sdboot.New()
// New returns a new bootloader based on the secureboot flag and architecture.
func New(bootloader, talosVersion, arch string) (Bootloader, error) {
switch bootloader {
case profile.DiskImageBootloaderGrub.String():
g := grub.NewConfig()
g.AddResetOption = quirks.New(talosVersion).SupportsResetGRUBOption()

return g, nil
case profile.DiskImageBootloaderSDBoot.String():
return sdboot.New(), nil
case profile.DiskImageBootloaderDualBoot.String():
return dual.New(), nil
default:
return nil, fmt.Errorf("unsupported bootloader %q", bootloader)
}
}

// CleanupBootloader cleans up the alternate bootloader when booting off via BIOS or UEFI.
func CleanupBootloader(disk string, sdboot bool) error {
dev, err := block.NewFromPath(disk, block.OpenForWrite())
if err != nil {
return err
}

defer dev.Close() //nolint:errcheck

if err := dev.Lock(true); err != nil {
return fmt.Errorf("failed to lock device: %w", err)
}

defer dev.Unlock() //nolint:errcheck

gptDev, err := gpt.DeviceFromBlockDevice(dev)
if err != nil {
return fmt.Errorf("failed to get GPT device: %w", err)
}

gptTable, err := gpt.Read(gptDev)
if err != nil {
return fmt.Errorf("failed to read GPT: %w", err)
}

if sdboot {
// we wipe upto 446 bytes where the protective MBR is located
if _, err := dev.WipeRange(0, 446); err != nil {
return fmt.Errorf("failed to wipe MBR: %w", err)
}

if err := deletePartitions(gptTable, constants.BIOSGrubPartitionLabel, constants.BootPartitionLabel); err != nil {
return err
}
} else {
// means we are using GRUB
if err := deletePartitions(gptTable, constants.EFIPartitionLabel); err != nil {
return err
}
}

g := grub.NewConfig()
g.AddResetOption = quirks.New(talosVersion).SupportsResetGRUBOption()
if err := gptTable.Write(); err != nil {
return fmt.Errorf("failed to write GPT: %w", err)
}

return nil
}

func deletePartitions(gptTable *gpt.Table, labels ...string) error {
for i, part := range gptTable.Partitions() {
if part == nil {
continue
}

for _, label := range labels {
if part.Name == label {
if err := gptTable.DeletePartition(i); err != nil {
return fmt.Errorf("failed to delete partition %s %d: %w", part.Name, i, err)
}
}
}
}

return g
return nil
}
Loading

0 comments on commit b1d410c

Please sign in to comment.