diff --git a/nix/modules/lanzaboote.nix b/nix/modules/lanzaboote.nix index fa463951..17ba32a3 100644 --- a/nix/modules/lanzaboote.nix +++ b/nix/modules/lanzaboote.nix @@ -60,6 +60,8 @@ in ''; }; + keepCurrentBootedConfiguration = lib.mkEnableOption "keeping the current booted configuration (labeled 'SAFE' in boot menu)"; + pkiBundle = lib.mkOption { type = lib.types.nullOr lib.types.externalPath; description = "PKI bundle containing db, PK, KEK"; @@ -170,7 +172,7 @@ in --systemd-boot-loader-config ${loaderConfigFile} \ --configuration-limit ${toString configurationLimit} \ --allow-unsigned ${lib.boolToString cfg.allowUnsigned} \ - --bootcounting-initial-tries ${toString cfg.bootCounting.initialTries}''; + --bootcounting-initial-tries ${toString cfg.bootCounting.initialTries}${lib.optionalString cfg.keepCurrentBootedConfiguration " \\\n --safe-generation /run/booted-system"}''; defaultText = lib.literalExpression '' ''${lib.getExe config.boot.lanzaboote.package} install \ --system ''${config.boot.kernelPackages.stdenv.hostPlatform.system} \ @@ -178,7 +180,7 @@ in --systemd-boot-loader-config ''${loaderConfigFile} \ --configuration-limit ''${toString configurationLimit} \ --allow-unsigned ''${lib.boolToString config.boot.lanzaboote.allowUnsigned} \ - --bootcounting-initial-tries ''${toString config.boot.lanzaboote.bootCounting.initialTries}''; + --bootcounting-initial-tries ''${toString config.boot.lanzaboote.bootCounting.initialTries}''${lib.optionalString config.boot.lanzaboote.keepCurrentBootedConfiguration " \\\n --safe-generation /run/booted-system"}''; }; extraEfiSysMountPoints = lib.mkOption { diff --git a/rust/tool/shared/src/generation.rs b/rust/tool/shared/src/generation.rs index 0e46fde6..bc7db410 100644 --- a/rust/tool/shared/src/generation.rs +++ b/rust/tool/shared/src/generation.rs @@ -64,6 +64,8 @@ impl From for LanzabooteExtension { pub struct Generation { /// Profile symlink index pub version: u64, + // Custom label, replacing version if present + pub label: Option, /// Build time pub build_time: Option, /// Top-level specialisation name @@ -93,6 +95,7 @@ impl Generation { ) -> Result { Ok(Self { version: link.version, + label: link.label.clone(), build_time: link.build_time, specialisation_name: Some(specialisation_name), spec: ExtendedBootJson { @@ -128,6 +131,7 @@ impl Generation { Ok(Self { version: link.version, + label: link.label.clone(), build_time: link.build_time, specialisation_name, spec: ExtendedBootJson { @@ -160,23 +164,30 @@ impl Generation { .map(|x| x.to_string()) .unwrap_or_else(|| String::from("Unknown")); - format!( - "Generation {}{}, {}", - self.version, - self.describe_specialisation(), - build_time - ) + format!("Generation {}, {}", self.version_tag(), build_time) } /// A unique short identifier. pub fn version_tag(&self) -> String { - format!("{}{}", self.version, self.describe_specialisation(),) + format!( + "{}{}", + self.label + .clone() + .unwrap_or_else(|| self.version.to_string()), + self.describe_specialisation(), + ) } } impl fmt::Display for Generation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.version) + write!( + f, + "{}", + self.label + .clone() + .unwrap_or_else(|| self.version.to_string()) + ) } } @@ -193,6 +204,7 @@ fn read_build_time(path: &Path) -> Result { #[derive(Debug)] pub struct GenerationLink { pub version: u64, + pub label: Option, pub path: PathBuf, pub build_time: Option, } @@ -201,6 +213,15 @@ impl GenerationLink { pub fn from_path(path: impl AsRef) -> Result { Ok(Self { version: parse_version(&path).context("Failed to parse version")?, + label: None, + path: PathBuf::from(path.as_ref()), + build_time: read_build_time(path.as_ref()).ok(), + }) + } + pub fn from_path_with_label(path: impl AsRef, label: String) -> Result { + Ok(Self { + version: 0, + label: Some(label), path: PathBuf::from(path.as_ref()), build_time: read_build_time(path.as_ref()).ok(), }) diff --git a/rust/tool/systemd/src/cli.rs b/rust/tool/systemd/src/cli.rs index a74829ca..6951b67e 100644 --- a/rust/tool/systemd/src/cli.rs +++ b/rust/tool/systemd/src/cli.rs @@ -70,6 +70,10 @@ struct InstallCommand { /// List of generation links (e.g. /nix/var/nix/profiles/system-*-link) generations: Vec, + + /// A safe generation (usually /run/booted-system) that should always be kept, ignored from --configuration-limit if used + #[arg(long)] + safe_generation: Option, } impl Cli { @@ -113,6 +117,7 @@ fn install(args: InstallCommand) -> Result<()> { args.bootcounting_initial_tries, args.esp, args.generations, + args.safe_generation, ); if args.allow_unsigned diff --git a/rust/tool/systemd/src/install.rs b/rust/tool/systemd/src/install.rs index e6314425..ba0bec13 100644 --- a/rust/tool/systemd/src/install.rs +++ b/rust/tool/systemd/src/install.rs @@ -33,6 +33,7 @@ pub struct InstallerBuilder { bootcounting_initial_tries: u32, esp: PathBuf, generation_links: Vec, + safe_generation: Option, } impl InstallerBuilder { @@ -46,6 +47,7 @@ impl InstallerBuilder { bootcounting_initial_tries: u32, esp: PathBuf, generation_links: Vec, + safe_generation: Option, ) -> Self { Self { lanzaboote_stub: lanzaboote_stub.as_ref().to_path_buf(), @@ -56,6 +58,7 @@ impl InstallerBuilder { bootcounting_initial_tries, esp, generation_links, + safe_generation, } } @@ -75,6 +78,7 @@ impl InstallerBuilder { bootcounting_initial_tries: self.bootcounting_initial_tries, esp_paths, generation_links: self.generation_links, + safe_generation : self.safe_generation, arch: self.arch, } } @@ -91,6 +95,7 @@ pub struct Installer { bootcounting_initial_tries: u32, esp_paths: SystemdEspPaths, generation_links: Vec, + safe_generation: Option, arch: Architecture, } @@ -104,6 +109,12 @@ impl Installer { .map(GenerationLink::from_path) .collect::>>()?; + let mut all_links = self + .safe_generation + .iter() + .map(|p| GenerationLink::from_path_with_label(p, "SAFE".to_string())) + .collect::>>()?; + // Sort the links by version, so that the limit actually skips the oldest generations. links.sort_by_key(|l| l.version); @@ -118,9 +129,11 @@ impl Installer { .rev() .take(self.configuration_limit) .rev() - .collect() + .collect(); }; - self.install_generations_from_links(&links)?; + all_links.append(&mut links); + + self.install_generations_from_links(&all_links)?; self.install_systemd_boot()?;