- Makefile: TALOS_VERSION v1.12.4 -> v1.13.2, PKG_VERSION v1.12.0 -> v1.13.0 - siderolabs/talos 0001 (modules-arm64.txt): removed; hack/modules-arm64.txt is a CI assertion file with no build-time references. Will be regenerated from a real RPi 6.12.47 kernel build as a follow-up. - siderolabs/talos 0005 (BOOT partition GRUB): rebased onto v1.13.2's Install/Upgrade refactor. installEFI struct field is gone upstream; ported the BOOT-partition probe + EFI-at-/boot fallback to work with the new efiFound local var and added a bootFromEFI struct field for runGrubInstall. - siderolabs/pkgs 0001: rebased onto v1.13.0. Kernel config header bumped to 6.12.47. config-arm64 not fully regenerated for RPi 6.12.47 yet -- some upstream v1.13 6.18.x symbols (LIBIE_ADMINQ, IDPF, etc) remain in the file but the kernel's Kconfig silently drops unknown options during build.
297 lines
8.8 KiB
Diff
297 lines
8.8 KiB
Diff
From cf9dfa043b21b393003ca4c2facebdd8e570f547 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] 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 | 2 +
|
|
.../v1alpha1/bootloader/grub/install.go | 49 +++++++++--
|
|
.../runtime/v1alpha1/bootloader/grub/probe.go | 83 ++++++++++++-------
|
|
.../v1alpha1/bootloader/grub/revert.go | 33 ++++++--
|
|
4 files changed, 125 insertions(+), 42 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 0cb5b1d4d..093ffecd9 100644
|
|
--- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go
|
|
+++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go
|
|
@@ -30,6 +30,8 @@ type Config struct {
|
|
Fallback BootLabel
|
|
Entries map[BootLabel]MenuEntry
|
|
AddResetOption 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 d0af22d98..f1ea13e5a 100644
|
|
--- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go
|
|
+++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go
|
|
@@ -34,12 +34,12 @@ const (
|
|
|
|
// Install validates the grub configuration and writes it to the disk.
|
|
func (c *Config) Install(opts options.InstallOptions) (*options.InstallResult, error) {
|
|
- 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{
|
|
@@ -48,6 +48,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 (absent on SBC layouts like RPi5/CM5)
|
|
+ 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)
|
|
+ }
|
|
+
|
|
var efiFound bool
|
|
|
|
// check if the EFI partition is present
|
|
@@ -65,12 +82,21 @@ func (c *Config) Install(opts options.InstallOptions) (*options.InstallResult, e
|
|
opts.BlkidInfo,
|
|
); err == nil {
|
|
efiFound = true
|
|
- }
|
|
|
|
- if efiFound {
|
|
+ if len(mountSpecs) == 0 {
|
|
+ // No BOOT partition (SBC layout): mount EFI at /boot so GRUB
|
|
+ // can find kernel/initramfs/grub.cfg in the expected place.
|
|
+ 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,
|
|
@@ -318,7 +344,12 @@ func (c *Config) runGrubInstall(ctx context.Context, opts options.InstallOptions
|
|
}
|
|
|
|
if efiMode {
|
|
- 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)
|
|
|