Skip to content

install mount: Select the deployment from the default boot entry - #2515

Open
cgwalters-bot wants to merge 1 commit into
bootc-dev:mainfrom
cgwalters-forge:bot/install-mount-default-entry
Open

cgwalters-bot wants to merge 1 commit into
bootc-dev:mainfrom
cgwalters-forge:bot/install-mount-default-entry

Conversation

@cgwalters-bot

Copy link
Copy Markdown
Contributor

bootc install mount --latest now mounts the deployment that the default boot entry boots, instead of requiring the sysroot to contain exactly one deployment. This is the follow-up to the review on #2483 (mount.rs:90). Design notes: https://gist.github.com/cgwalters-bot/3d295a3185c75c5a24989dda21018573

A booted system knows its deployment from the kernel command line. Offline, the equivalent is the command line of the entry the bootloader would pick. The entries come from SYSROOT/boot and the ESP, and they are ordered with the same code bootc status uses: for GRUB, the user.cfg UKI entries or else the Type 1 entries sorted by file name; otherwise the Type 1 entries in BLS order. For a composefs entry, its digest (or UKI name) picks the deployment. For an ostree= entry, it is OSTree's own default (deployments()[0], which OSTree orders from the same entries, as bootc status shows it), rather than re-sorting the entries: GRUB's file name order differs from OSTree's at ten or more deployments. The bootloader probe get_bootloader uses without EFI variables now takes the boot directories, so mount shares it. A new --esp option names the ESP, and defaults to SYSROOT/boot/efi.

Caveats:

  • There is no --boot. OSTree loads its deployments from SYSROOT/boot, so /boot has to be mounted there anyway, and the man page says so.
  • EFI variables (such as a one-time boot entry), boot counting, loader.conf's default and grubenv's saved_entry are not considered. bootc status doesn't consider them either.
  • With no boot entries at all, the old rule still applies: a sole deployment is mounted. That keeps a fresh install mountable without mounting the ESP.

Tested on a 16-core RHEL 10 devspace:

  • Plan 23 (install-outside-container) is extended. On ostree it deploys the same commit ten more times (11 deployments, so file-name order would pick the wrong one) and checks that --latest follows OSTree's default, also after ostree admin set-default. On composefs it rewrites the installed boot entries to name a missing deployment and checks that the mount fails naming it, then restores them. An earlier two-deployment version of the test, run with main's bootc (683bb05), failed with target must contain exactly one deployment (found 2 OSTree, 0 composefs).
  • With this change, plan 23 passes on ostree/grub, composefs grub/BLS/ext4 and composefs systemd-boot/UKI/ext4 (unsealed), and just validate and just unit-tests pass. The new unit tests cover entry ordering for each bootloader layout, the boot directory probe, and choosing the deployment.

The Signed-off-by: Colin Walters <walters@verbum.org> on these commits was added on cgwalters's approval of the review draft: cgwalters-forge#32 (review)

Generated-by: https://github.com/cgwalters/#llms

`--latest` counted deployments and refused anything but exactly one.
That fails on any installation that has been upgraded, and it can't
choose when a sysroot holds both OSTree and composefs deployments
(e.g. mid-migration). A booted system answers "which deployment" from
its kernel command line. Offline, the equivalent is the command line of
the boot entry the bootloader would pick.

So read the entries from /boot and the ESP and order them as `bootc
status` does, sharing its bootloader probe (now taking the boot
directories rather than looking at the host's /boot) and entry sorting.
A composefs default entry names its deployment by digest (or UKI name).
For an `ostree=` one, OSTree's own deployment order, which it loads from
the same entries, already has the default first, so use that rather than
re-sorting the entries: GRUB's file name order differs from it at ten or
more deployments. /boot has to be at SYSROOT/boot, since OSTree loads
its deployments from there. The ESP comes from a new `--esp`, defaulting
to a mounted SYSROOT/efi, then SYSROOT/boot/efi, matching what
systemd-gpt-auto-generator would set up, and otherwise to the ESP
discovered from the disks backing the sysroot, as install finds it.
Architectures without an ESP skip all that.

With no entries at all, e.g. when no ESP is found on a systemd-boot
install, a sole deployment is still selected as before.

Generated-by: AI

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Bootloader ordering and cross-backend deployment selection are sufficiently operationally sensitive to require final maintainer review.

Review effort: Balanced
Findings: None

What changed in this PR

Updates offline deployment mounting to follow the bootloader’s default entry.

Changes:

  • Selects OSTree or composefs deployments from GRUB/systemd-boot entries.
  • Adds --esp and documents boot partition requirements.
  • Adds unit and integration coverage for ordering and selection.
File Description
crates/​lib/​src/​mount.rs Implements boot-entry-based deployment selection.
crates/​lib/​src/​bootc_composefs/​status.rs Generalizes offline bootloader probing and entry sorting.
docs/​src/​man/​bootc-install-mount.8.md Documents selection semantics and --esp.
tmt/​tests/​booted/​test-install-outside-container.nu Tests multi-deployment and missing-deployment behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/lib/src/mount.rs Outdated
/// Where `/boot` is expected in the sysroot, as OSTree requires.
const BOOT: &str = "boot";
/// Where the ESP is looked for in the sysroot when `--esp` is not given.
const DEFAULT_ESP: &str = "boot/efi";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer /efi actually first - the ideal path is a DPS system matching what systemd-gpt-auto-generator would do.

But we definitely need boot/efi as fallback

Comment thread crates/lib/src/mount.rs Outdated

/// The mounted EFI System Partition, where systemd-boot keeps its boot entries.
///
/// Defaults to SYSROOT/boot/efi, if it exists.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above but also we must honor the same ESP discovery paths we do for install. Let's make that clear.

Defaults to `/efi` or `/boot/efi` if either are mounted, otherwise the ESP will be discovered (if applicable) from the block device(s) backing the target root.

Hmm btw in theory we should make this variable also conditional on whether the arch supports an ESP

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a3c55bf (your sign-off kept): --esp now defaults to a mounted SYSROOT/efi, then SYSROOT/boot/efi, then the ESP found from the backing block devices via the same find_colocated_esps() + mount_esp_readonly() install/status use, all gated on ARCH_USES_EFI (the flag is hidden elsewhere). Your doc text is on the flag, and the man page is updated to match.

Tests: a new unit test covers the preference order and discovery fallback, and plan-23 now also exercises the discovered, /boot/efi and /efi cases on composefs. just validate, the unit tests, and plan-23 on ostree/grub and on composefs systemd-boot/UKI all passed on a 16-core devspace.

Generated-by: https://github.com/cgwalters/#llms

@cgwalters-bot

Copy link
Copy Markdown
Contributor Author

@cgwalters The grub-cc failures were a test bug: like systemd-boot, grub-cc keeps its entries on the ESP, but the empty-/boot/efi case assumed only systemd-boot does. The test now checks whether the ESP holds entries. f36cbb0 is that fix, rebased onto main, and it drops your sign-off (it's a test change you didn't ask for), so it needs your re-approval.

Tested on Fedora 44 devspaces: plan-23 passes on composefs grub-cc bls (ext4 and xfs) and on ostree; just validate and unit tests pass. Not finished before the devspace timed out: grub-cc uki xfs, systemd uki ext4 and grub bls ext4. CI here covers those.

Generated-by: https://github.com/cgwalters/#llms

@cgwalters-bot

Copy link
Copy Markdown
Contributor Author

@cgwalters one open question, separate from the CI fix: on a grub-cc system the offline bootloader probe in this PR reports systemd-boot, so default-entry selection orders BLS entries by sort-key rather than GRUB's reverse filename order (#2221). With a single deployment it can't matter; with several it could pick a different default than GRUB boots. Follow up in this PR, or separately?

Generated-by: https://github.com/cgwalters/#llms

@cgwalters

Copy link
Copy Markdown
Collaborator

on a grub-cc system the offline bootloader probe in this PR reports systemd-boot, so default-entry selection orders BLS entries by sort-key rather than GRUB's reverse filename order (#2221).

I don't like the way we probe the bootloader. Here's an idea...what if for grub we changed how we write https://uapi-group.org/specifications/specs/boot_loader_specification/#standard-conformance-marker-file so it said grub-bls instead?

And that's what we dispatch on - bootupd at install time should know if the bootloader implements the spec in a conformant way or not, and then we query it.

For now...needing to touch the bootloader stuff in a new way I think is adding more risk to the install mount which we don't need right now. Let's take this one as draft and move it off the p0 composefs stabilization list.

/// `/boot/grub2/` *and* `/boot/loader/entries/`, and is unambiguously
/// GRUB. Only a BLS layout with no GRUB directory implies a BLS-native
/// bootloader.
pub(crate) fn bootloader(&self) -> Bootloader {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing grub-cc here. Should be the same as systemd-boot, so I think this is fine, but a comment here would be good

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still stands

efi_loader_info: Result<String, EfiError>,
bls_entries_dir_present: bool,
grub_dir_present: bool,
probe: impl FnOnce() -> Result<BootDirProbe>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this needs to be a function as an argument? Can't we just a standalone function?

Comment thread crates/lib/src/mount.rs
/// Where `/boot` is expected in the sysroot, as OSTree requires.
const BOOT: &str = "boot";
/// Where a mounted ESP is looked for in the sysroot when `--esp` is not
/// given, in order: `/efi` as systemd-gpt-auto-generator mounts it, then the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

systemd-gpt-auto-generator mounts ESP at /boot

From https://www.freedesktop.org/software/systemd/man/latest/systemd-gpt-auto-generator.html

The ESP is mounted to /boot/ if that directory is not used for XBOOTLDR, and otherwise to /efi/

Comment thread crates/lib/src/mount.rs
Comment on lines +199 to +200
is_mountpoint: impl Fn(&Dir) -> Result<bool>,
discover: impl FnOnce() -> Result<Option<T>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do these need to be a functions as arguments?

Comment thread crates/lib/src/mount.rs
else {
continue;
};
if is_mountpoint(&dir).with_context(|| format!("Checking whether {path} is mounted"))? {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_mountpoint is already a function on cap_std::fs::Dir

@cgwalters-bot

cgwalters-bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Fix round 1 of 2 for f36cbb03e3b4: run.

Generated-by: https://github.com/cgwalters/#llms

@cgwalters-bot

cgwalters-bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ dispatch ended well and handed back no result. Its reason:

No change pushed. The only failing required check is DCO: head commit 9273143 (install mount: Select the deployment from the default boot entry) has no Signed-off-by, because the previous fix round dropped the sign-off. The project's agent rules forbid an agent from adding Signed-off-by, so this needs a human. No code issue was found and no review ran, since there was no change to review. To fix: check out bot/install-mount-default-entry, run git commit --amend -s --no-edit (or git rebase --signoff 7cde7bb), then force-push.

@cgwalters-bot
cgwalters-bot force-pushed the bot/install-mount-default-entry branch from f36cbb0 to 9273143 Compare November 4, 2026 23:09
@cgwalters-bot
cgwalters-bot marked this pull request as ready for review November 4, 2026 23:09
@bootc-bot
bootc-bot Bot requested a review from henrywang November 4, 2026 23:09
@cgwalters-bot

cgwalters-bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Fix round 2 of 2 for 9273143c13cf: run.

Generated-by: https://github.com/cgwalters/#llms

@cgwalters-bot

cgwalters-bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Failed checks: one rerun reserved for these checks on this head.

Generated-by: https://github.com/cgwalters/#llms

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/documentation Updates to the documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants