Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import GRUB static migration code #790

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions contrib/packaging/bootupd-static-grub-migration.service
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

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

Are there any systems which would enable the static migration but not the update service?

If not, why wouldn't we just fold this functionality into the bootloader-update.service via

ExecStart=bootupctl migrate
ExecStart=bootupctl update

Or I guess just have bootupctl update --migrate-if-needed or so.

Copy link
Member Author

Choose a reason for hiding this comment

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

We indeed want all the systems that do the migration to update their bootloader before.

We don't need to do the migration on systems which already have a static GRUB configs and we can check the state for that, so maybe this should indeed be folded into a single command and moved from a stamp file to a field in the state JSON.

If we merge both units then it also means that we have to careful when rolling this out as we won't be easily able to enable one or the other independently. But that's also kind of OK as I'm looking at doing that in F41 as well before the F42 release.

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
1 change: 1 addition & 0 deletions contrib/packaging/bootupd.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
125 changes: 124 additions & 1 deletion src/bootupd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<()> {
travier marked this conversation as resolved.
Show resolved Hide resolved
// 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(&current_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(&current_config, &current_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::*;
Expand Down
9 changes: 9 additions & 0 deletions src/cli/bootupctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -95,6 +97,7 @@ impl CtlCommand {
CtlVerb::Backend(CtlBackend::Install(opts)) => {
super::bootupd::DCommand::run_install(opts)
}
CtlVerb::Migrate => Self::run_migrate(),
}
}

Expand Down Expand Up @@ -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)
Expand Down
Loading