From 3926dcf5478d11e1fe9a5027c6b00686219c7826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Ravier?= Date: Tue, 3 Dec 2024 20:03:55 +0100 Subject: [PATCH 1/2] migrate: Add GRUB static migration subcommand See: https://gitlab.com/fedora/ostree/sig/-/issues/35 See: https://pagure.io/workstation-ostree-config/pull-request/591 Fixes: https://github.com/coreos/bootupd/issues/789 --- src/bootupd.rs | 125 ++++++++++++++++++++++++++++++++++++++++++- src/cli/bootupctl.rs | 9 ++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/bootupd.rs b/src/bootupd.rs index c16a885c..11ff4098 100644 --- a/src/bootupd.rs +++ b/src/bootupd.rs @@ -9,10 +9,12 @@ use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedSt use crate::util; use anyhow::{anyhow, Context, Result}; use clap::crate_version; +use fn_error_context::context; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::collections::BTreeMap; -use std::path::Path; +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; pub(crate) enum ConfigMode { None, @@ -489,6 +491,127 @@ pub(crate) fn client_run_validate() -> Result<()> { Ok(()) } +#[context("Migrating to static grub config")] +pub(crate) fn client_run_migrate() -> Result<()> { + // Used to condition execution of this unit at the systemd level + let stamp_file = "/boot/.bootupd-static-migration-complete"; + + // Did we already complete the migration? + let mut ostree_cmd = std::process::Command::new("ostree"); + let result = ostree_cmd + .args([ + "config", + "--repo=/sysroot/ostree/repo", + "get", + "sysroot.bootloader", + ]) + .output() + .context("Querying ostree sysroot.bootloader")?; + if !result.status.success() { + // ostree will exit with a non zero return code if the key does not exists + println!("ostree repo 'sysroot.bootloader' config option not set yet."); + } else { + let bootloader = String::from_utf8(result.stdout) + .with_context(|| "decoding as UTF-8 output of ostree command")?; + if bootloader.trim_end() == "none" { + println!("ostree repo 'sysroot.bootloader' config option already set to 'none'."); + println!("Assuming that the migration is already complete."); + File::create(stamp_file)?; + return Ok(()); + } + println!( + "ostree repo 'sysroot.bootloader' config currently set to: {}", + bootloader.trim_end() + ); + } + + // Remount /boot read write just for this unit (we are called in a slave mount namespace by systemd) + ensure_writable_boot()?; + + let grub_config_dir = PathBuf::from("/boot/grub2"); + let dirfd = openat::Dir::open(&grub_config_dir).context("Opening /boot/grub2")?; + + // Migrate /boot/grub2/grub.cfg to a static GRUB config if it is a symlink + let grub_config_filename = PathBuf::from("/boot/grub2/grub.cfg"); + match dirfd.read_link("grub.cfg") { + Err(_) => { + println!( + "'{}' is not a symlink. Nothing to migrate.", + grub_config_filename.display() + ); + } + Ok(path) => { + println!("Migrating to a static GRUB config..."); + + // Resolve symlink location + let mut current_config = grub_config_dir.clone(); + current_config.push(path); + + // Backup the current GRUB config which is hopefully working right now + let backup_config = PathBuf::from("/boot/grub2/grub.cfg.backup"); + println!( + "Creating a backup of the current GRUB config '{}' in '{}'...", + current_config.display(), + backup_config.display() + ); + fs::copy(¤t_config, &backup_config).context("Failed to backup GRUB config")?; + + // Copy it again alongside the current symlink + let current_config_copy = PathBuf::from("/boot/grub2/grub.cfg.current"); + fs::copy(¤t_config, ¤t_config_copy) + .context("Failed to copy the current GRUB config")?; + + // Atomically exchange the configs + dirfd + .local_exchange("grub.cfg.current", "grub.cfg") + .context("Failed to exchange symlink with current GRUB config")?; + + // Remove the now unused symlink (optional cleanup, ignore any failures) + _ = dirfd.remove_file("grub.cfg.current"); + + println!("GRUB config symlink successfully replaced with the current config."); + } + }; + + // If /etc/default/grub exists then we have to force the regeneration of the + // GRUB config to remove the ostree entries that duplicates the BLS ones + let grub_default = PathBuf::from("/etc/default/grub"); + if grub_default.exists() { + println!("Marking bootloader as BLS capable..."); + File::create("/boot/grub2/.grub2-blscfg-supported") + .context("Failed to mark bootloader as BLS capable")?; + + println!("Regenerating GRUB config with only BLS configs..."); + let status = std::process::Command::new("grub2-mkconfig") + .arg("-o") + .arg(grub_config_filename) + .status()?; + if !status.success() { + anyhow::bail!("Failed to regenerate GRUB config"); + } + } + + println!("Setting up 'sysroot.bootloader' to 'none' in ostree repo config..."); + let status = std::process::Command::new("ostree") + .args([ + "config", + "--repo=/sysroot/ostree/repo", + "set", + "sysroot.bootloader", + "none", + ]) + .status()?; + if !status.success() { + anyhow::bail!("Failed to set 'sysroot.bootloader' to 'none' in ostree repo config"); + } + + // Migration complete, let's write the stamp file + File::create(stamp_file)?; + + println!("Static GRUB config migration completed successfully!"); + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/cli/bootupctl.rs b/src/cli/bootupctl.rs index ab30decb..4ac934a3 100644 --- a/src/cli/bootupctl.rs +++ b/src/cli/bootupctl.rs @@ -58,6 +58,8 @@ pub enum CtlVerb { AdoptAndUpdate, #[clap(name = "validate", about = "Validate system state")] Validate, + #[clap(name = "migrate", about = "Migrate a system to static a GRUB config")] + Migrate, } #[derive(Debug, Parser)] @@ -95,6 +97,7 @@ impl CtlCommand { CtlVerb::Backend(CtlBackend::Install(opts)) => { super::bootupd::DCommand::run_install(opts) } + CtlVerb::Migrate => Self::run_migrate(), } } @@ -135,6 +138,12 @@ impl CtlCommand { ensure_running_in_systemd()?; bootupd::client_run_validate() } + + /// Runner for `migrate` verb. + fn run_migrate() -> Result<()> { + ensure_running_in_systemd()?; + bootupd::client_run_migrate() + } } /// Checks if the current process is (apparently at least) From 06132d0b33ce6b19e5a2ce8222703c24acb6f0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Ravier?= Date: Wed, 18 Dec 2024 12:29:35 +0100 Subject: [PATCH 2/2] migrate: Add systemd service unit to trigger on boot Note that the service is intentionally not enabled by default as it should be up to the distribution to add a systemd preset if the migration to a static GRUB config is needed. This will be used on Atomic Desktops & IoT systems to migrate systems to a static GRUB config before enabling composefs as GRUB curently does not interact well with it: https://bugzilla.redhat.com/show_bug.cgi?id=2308594 See: https://github.com/coreos/bootupd/issues/789 See: https://fedoraproject.org/wiki/Changes/ComposefsAtomicDesktops --- Makefile | 1 + .../bootupd-static-grub-migration.service | 17 +++++++++++++++++ contrib/packaging/bootupd.spec | 1 + 3 files changed, 19 insertions(+) create mode 100644 contrib/packaging/bootupd-static-grub-migration.service diff --git a/Makefile b/Makefile index 59c212c9..f977008b 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ install-grub-static: install-systemd-unit: install -m 644 -D -t "${DESTDIR}$(PREFIX)/lib/systemd/system/" contrib/packaging/bootloader-update.service + install -m 644 -D -t "${DESTDIR}$(PREFIX)/lib/systemd/system/" contrib/packaging/bootupd-static-grub-migration.service bin-archive: rm target/inst -rf diff --git a/contrib/packaging/bootupd-static-grub-migration.service b/contrib/packaging/bootupd-static-grub-migration.service new file mode 100644 index 00000000..5f726f17 --- /dev/null +++ b/contrib/packaging/bootupd-static-grub-migration.service @@ -0,0 +1,17 @@ +[Unit] +Description=bootupd static GRUB config migration +Documentation=https://github.com/coreos/bootupd +ConditionPathExists=!/boot/.bootupd-static-migration-complete +RequiresMountsFor=/sysroot /boot +# Only run after a successful bootloader update +After=bootloader-update.service +Requires=bootloader-update.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/bootupctl migrate +RemainAfterExit=yes +MountFlags=slave + +[Install] +WantedBy=multi-user.target diff --git a/contrib/packaging/bootupd.spec b/contrib/packaging/bootupd.spec index b6db508f..6d096c15 100644 --- a/contrib/packaging/bootupd.spec +++ b/contrib/packaging/bootupd.spec @@ -36,6 +36,7 @@ BuildRequires: systemd-rpm-macros %{_libexecdir}/bootupd %{_prefix}/lib/bootupd/grub2-static/ %{_unitdir}/bootloader-update.service +%{_unitdir}/bootupd-static-grub-migration.service %prep %autosetup -n %{crate}-%{version} -p1 -Sgit