Add GRUB SBC upgrade patch: handle missing BOOT partition
All checks were successful
Build Talos CM5 Image / build (push) Successful in 3m15s
All checks were successful
Build Talos CM5 Image / build (push) Successful in 3m15s
Patch 0005 fixes talosctl upgrade on SBC layouts (RPi5/CM5) where the disk has no separate BOOT (XFS) partition — only EFI (VFAT). Falls back to mounting EFI at /boot for probe, install, and revert. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6cffb4e311
commit
ca36438d12
@ -0,0 +1,296 @@
|
|||||||
|
From f615031ee89446cf2520b7a3b458a37905785cd8 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Mathias Beaulieu-Duncan <mathias@svrnty.io>
|
||||||
|
Date: Mon, 16 Feb 2026 11:31:08 -0500
|
||||||
|
Subject: [PATCH 5/5] Handle missing BOOT partition for GRUB on SBC layouts
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
On SBC platforms like RPi5/CM5, the disk layout has no separate BOOT
|
||||||
|
(XFS) partition — all boot assets (kernel, initramfs, GRUB config,
|
||||||
|
EFI bootloader, firmware) reside on a single EFI (VFAT) partition.
|
||||||
|
|
||||||
|
The GRUB bootloader probe, install, and revert functions all assumed a
|
||||||
|
BOOT partition exists, causing talosctl upgrade to fail with "partition
|
||||||
|
with label BOOT not found" on these layouts.
|
||||||
|
|
||||||
|
Fix all three code paths to fall back to mounting the EFI partition at
|
||||||
|
/boot when the BOOT partition is absent:
|
||||||
|
|
||||||
|
- probe.go: Try EFI at /boot as fallback when BOOT is missing, so
|
||||||
|
the upgrade can detect the existing GRUB installation
|
||||||
|
- install.go: Probe for BOOT existence before including it in mount
|
||||||
|
specs; when absent, mount EFI at /boot and adjust --efi-directory
|
||||||
|
- revert.go: Fall back to EFI at /boot for rollback operations
|
||||||
|
|
||||||
|
This enables in-place upgrades via talosctl upgrade on SBC platforms
|
||||||
|
that use EFI-only disk layouts.
|
||||||
|
---
|
||||||
|
.../runtime/v1alpha1/bootloader/grub/grub.go | 3 +-
|
||||||
|
.../v1alpha1/bootloader/grub/install.go | 48 +++++++++--
|
||||||
|
.../runtime/v1alpha1/bootloader/grub/probe.go | 83 ++++++++++++-------
|
||||||
|
.../v1alpha1/bootloader/grub/revert.go | 33 ++++++--
|
||||||
|
4 files changed, 124 insertions(+), 43 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go
|
||||||
|
index 5417ffc77..94406ed3a 100644
|
||||||
|
--- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go
|
||||||
|
+++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go
|
||||||
|
@@ -29,7 +29,8 @@ type Config struct {
|
||||||
|
Entries map[BootLabel]MenuEntry
|
||||||
|
AddResetOption bool
|
||||||
|
|
||||||
|
- installEFI bool
|
||||||
|
+ installEFI bool
|
||||||
|
+ bootFromEFI bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuEntry represents a grub menu entry in the grub config file.
|
||||||
|
diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go
|
||||||
|
index 766374b3e..54d39f795 100644
|
||||||
|
--- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go
|
||||||
|
+++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go
|
||||||
|
@@ -33,12 +33,12 @@ const (
|
||||||
|
func (c *Config) Install(opts options.InstallOptions) (*options.InstallResult, error) {
|
||||||
|
var installResult *options.InstallResult
|
||||||
|
|
||||||
|
- mountSpecs := []mount.Spec{
|
||||||
|
- {
|
||||||
|
- PartitionLabel: constants.BootPartitionLabel,
|
||||||
|
- FilesystemType: partition.FilesystemTypeXFS,
|
||||||
|
- MountTarget: filepath.Join(opts.MountPrefix, constants.BootMountPoint),
|
||||||
|
- },
|
||||||
|
+ var mountSpecs []mount.Spec
|
||||||
|
+
|
||||||
|
+ bootMountSpec := mount.Spec{
|
||||||
|
+ PartitionLabel: constants.BootPartitionLabel,
|
||||||
|
+ FilesystemType: partition.FilesystemTypeXFS,
|
||||||
|
+ MountTarget: filepath.Join(opts.MountPrefix, constants.BootMountPoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
efiMountSpec := mount.Spec{
|
||||||
|
@@ -47,6 +47,23 @@ func (c *Config) Install(opts options.InstallOptions) (*options.InstallResult, e
|
||||||
|
MountTarget: filepath.Join(opts.MountPrefix, constants.EFIMountPoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // check if the BOOT partition is present
|
||||||
|
+ if err := mount.PartitionOp(
|
||||||
|
+ opts.BootDisk,
|
||||||
|
+ []mount.Spec{bootMountSpec},
|
||||||
|
+ func() error {
|
||||||
|
+ return nil
|
||||||
|
+ },
|
||||||
|
+ []blkid.ProbeOption{
|
||||||
|
+ blkid.WithSkipLocking(true),
|
||||||
|
+ },
|
||||||
|
+ nil,
|
||||||
|
+ nil,
|
||||||
|
+ opts.BlkidInfo,
|
||||||
|
+ ); err == nil {
|
||||||
|
+ mountSpecs = append(mountSpecs, bootMountSpec)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// check if the EFI partition is present
|
||||||
|
if err := mount.PartitionOp(
|
||||||
|
opts.BootDisk,
|
||||||
|
@@ -62,12 +79,20 @@ func (c *Config) Install(opts options.InstallOptions) (*options.InstallResult, e
|
||||||
|
opts.BlkidInfo,
|
||||||
|
); err == nil {
|
||||||
|
c.installEFI = true
|
||||||
|
- }
|
||||||
|
|
||||||
|
- if c.installEFI {
|
||||||
|
+ if len(mountSpecs) == 0 {
|
||||||
|
+ // No BOOT partition (SBC layout): mount EFI at /boot
|
||||||
|
+ efiMountSpec.MountTarget = filepath.Join(opts.MountPrefix, constants.BootMountPoint)
|
||||||
|
+ c.bootFromEFI = true
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
mountSpecs = append(mountSpecs, efiMountSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if len(mountSpecs) == 0 {
|
||||||
|
+ return nil, fmt.Errorf("neither BOOT nor EFI partition found on disk %s", opts.BootDisk)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
err := mount.PartitionOp(
|
||||||
|
opts.BootDisk,
|
||||||
|
mountSpecs,
|
||||||
|
@@ -193,7 +218,12 @@ func (c *Config) install(opts options.InstallOptions) (*options.InstallResult, e
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.installEFI {
|
||||||
|
- args = append(args, "--efi-directory="+filepath.Join(opts.MountPrefix, constants.EFIMountPoint))
|
||||||
|
+ efiDir := constants.EFIMountPoint
|
||||||
|
+ if c.bootFromEFI {
|
||||||
|
+ efiDir = constants.BootMountPoint
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ args = append(args, "--efi-directory="+filepath.Join(opts.MountPrefix, efiDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ImageMode || opts.Arch == arm64 {
|
||||||
|
diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/probe.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/probe.go
|
||||||
|
index f5755592f..771ca2a90 100644
|
||||||
|
--- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/probe.go
|
||||||
|
+++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/probe.go
|
||||||
|
@@ -19,6 +19,31 @@ import (
|
||||||
|
func ProbeWithCallback(disk string, options options.ProbeOptions, callback func(*Config) error) (*Config, error) {
|
||||||
|
var grubConf *Config
|
||||||
|
|
||||||
|
+ readConfig := func() error {
|
||||||
|
+ var err error
|
||||||
|
+
|
||||||
|
+ grubConf, err = Read(ConfigPath)
|
||||||
|
+ if err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if grubConf != nil && callback != nil {
|
||||||
|
+ return callback(grubConf)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if grubConf == nil {
|
||||||
|
+ options.Logf("GRUB: config not found")
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return nil
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ probeOpts := []mountv3.ManagerOption{
|
||||||
|
+ mountv3.WithSkipIfMounted(),
|
||||||
|
+ mountv3.WithReadOnly(),
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Try BOOT partition first (standard layout)
|
||||||
|
if err := mount.PartitionOp(
|
||||||
|
disk,
|
||||||
|
[]mount.Spec{
|
||||||
|
@@ -28,40 +53,42 @@ func ProbeWithCallback(disk string, options options.ProbeOptions, callback func(
|
||||||
|
MountTarget: constants.BootMountPoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
- func() error {
|
||||||
|
- var err error
|
||||||
|
-
|
||||||
|
- grubConf, err = Read(ConfigPath)
|
||||||
|
- if err != nil {
|
||||||
|
- return err
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- if grubConf != nil && callback != nil {
|
||||||
|
- return callback(grubConf)
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- if grubConf == nil {
|
||||||
|
- options.Logf("GRUB: config not found")
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- return nil
|
||||||
|
- },
|
||||||
|
+ readConfig,
|
||||||
|
options.BlockProbeOptions,
|
||||||
|
- []mountv3.ManagerOption{
|
||||||
|
- mountv3.WithSkipIfMounted(),
|
||||||
|
- mountv3.WithReadOnly(),
|
||||||
|
- },
|
||||||
|
+ probeOpts,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
- if xerrors.TagIs[mount.NotFoundTag](err) {
|
||||||
|
- // if partitions are not found, it means GRUB is not installed
|
||||||
|
- options.Logf("GRUB: BOOT partition not found, skipping probing")
|
||||||
|
-
|
||||||
|
- return nil, nil
|
||||||
|
+ if !xerrors.TagIs[mount.NotFoundTag](err) {
|
||||||
|
+ return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
- return nil, err
|
||||||
|
+ // BOOT not found, try EFI partition mounted at /boot (SBC layout)
|
||||||
|
+ options.Logf("GRUB: BOOT partition not found, trying EFI partition")
|
||||||
|
+
|
||||||
|
+ if err := mount.PartitionOp(
|
||||||
|
+ disk,
|
||||||
|
+ []mount.Spec{
|
||||||
|
+ {
|
||||||
|
+ PartitionLabel: constants.EFIPartitionLabel,
|
||||||
|
+ FilesystemType: partition.FilesystemTypeVFAT,
|
||||||
|
+ MountTarget: constants.BootMountPoint,
|
||||||
|
+ },
|
||||||
|
+ },
|
||||||
|
+ readConfig,
|
||||||
|
+ options.BlockProbeOptions,
|
||||||
|
+ probeOpts,
|
||||||
|
+ nil,
|
||||||
|
+ nil,
|
||||||
|
+ ); err != nil {
|
||||||
|
+ if xerrors.TagIs[mount.NotFoundTag](err) {
|
||||||
|
+ options.Logf("GRUB: neither BOOT nor EFI partition found, skipping probing")
|
||||||
|
+
|
||||||
|
+ return nil, nil
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
return grubConf, nil
|
||||||
|
diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go
|
||||||
|
index 3e514d65d..95628193b 100644
|
||||||
|
--- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go
|
||||||
|
+++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go
|
||||||
|
@@ -26,6 +26,10 @@ func (c *Config) Revert(disk string) error {
|
||||||
|
return fmt.Errorf("cannot revert bootloader: %w", bootloaderNotInstalledError{})
|
||||||
|
}
|
||||||
|
|
||||||
|
+ revertOpts := []mountv3.ManagerOption{
|
||||||
|
+ mountv3.WithSkipIfMounted(),
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
err := mount.PartitionOp(
|
||||||
|
disk,
|
||||||
|
[]mount.Spec{
|
||||||
|
@@ -37,14 +41,33 @@ func (c *Config) Revert(disk string) error {
|
||||||
|
},
|
||||||
|
c.revert,
|
||||||
|
nil,
|
||||||
|
- []mountv3.ManagerOption{
|
||||||
|
- mountv3.WithSkipIfMounted(),
|
||||||
|
- },
|
||||||
|
+ revertOpts,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
- if err != nil && !xerrors.TagIs[mount.NotFoundTag](err) {
|
||||||
|
- return err
|
||||||
|
+ if err != nil {
|
||||||
|
+ if !xerrors.TagIs[mount.NotFoundTag](err) {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // BOOT not found, try EFI partition mounted at /boot (SBC layout)
|
||||||
|
+ if err := mount.PartitionOp(
|
||||||
|
+ disk,
|
||||||
|
+ []mount.Spec{
|
||||||
|
+ {
|
||||||
|
+ PartitionLabel: constants.EFIPartitionLabel,
|
||||||
|
+ FilesystemType: partition.FilesystemTypeVFAT,
|
||||||
|
+ MountTarget: constants.BootMountPoint,
|
||||||
|
+ },
|
||||||
|
+ },
|
||||||
|
+ c.revert,
|
||||||
|
+ nil,
|
||||||
|
+ revertOpts,
|
||||||
|
+ nil,
|
||||||
|
+ nil,
|
||||||
|
+ ); err != nil && !xerrors.TagIs[mount.NotFoundTag](err) {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user