lanzatool: bootspec from generation
The bootspec is now read from each generation so that more than one entry can be generated when calling install
This commit is contained in:
parent
3c094ee5ff
commit
0a96623461
|
@ -38,16 +38,20 @@ in
|
||||||
boot.loader.supportsInitrdSecrets = mkForce true;
|
boot.loader.supportsInitrdSecrets = mkForce true;
|
||||||
boot.loader.external = {
|
boot.loader.external = {
|
||||||
enable = true;
|
enable = true;
|
||||||
passBootspec = true;
|
installHook = pkgs.writeShellScript "bootinstall" ''
|
||||||
installHook = "${pkgs.writeShellScriptBin "bootinstall" ''
|
${optionalString cfg.enrollKeys ''
|
||||||
${optionalString cfg.enrollKeys ''
|
mkdir -p /tmp/pki
|
||||||
mkdir -p /tmp/pki
|
cp -r ${cfg.pkiBundle}/* /tmp/pki
|
||||||
cp -r ${cfg.pkiBundle}/* /tmp/pki
|
${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine
|
||||||
${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine
|
''}
|
||||||
''}
|
|
||||||
${cfg.package}/bin/lanzatool install --pki-bundle ${cfg.pkiBundle} --public-key ${cfg.publicKeyFile} --private-key ${cfg.privateKeyFile} "$@" /nix/var/nix/profiles/system-*-link
|
${cfg.package}/bin/lanzatool install \
|
||||||
''}/bin/bootinstall";
|
--pki-bundle ${cfg.pkiBundle} \
|
||||||
# ${cfg.package}/bin/lanzatool install ${optionalString cfg.enrollKeys "--auto-enroll"} --pki-bundle ${cfg.pkiBundle}
|
--public-key ${cfg.publicKeyFile} \
|
||||||
|
--private-key ${cfg.privateKeyFile} \
|
||||||
|
${config.boot.loader.efi.efiSysMountPoint} \
|
||||||
|
/nix/var/nix/profiles/system-*-link
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
"label": "LanzaOS",
|
"label": "LanzaOS",
|
||||||
"toplevel": "/run/current-system",
|
"toplevel": "/run/current-system",
|
||||||
"extension": {
|
"extension": {
|
||||||
"esp": "esp",
|
|
||||||
"systemd": "/run/current-system/systemd",
|
|
||||||
"bootctl": "/run/current-system/sw/bin/bootctl",
|
|
||||||
"osRelease": "/etc/os-release"
|
"osRelease": "/etc/os-release"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,8 +25,5 @@ pub struct Bootspec {
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Extension {
|
pub struct Extension {
|
||||||
pub esp: String,
|
|
||||||
pub bootctl: PathBuf,
|
|
||||||
pub os_release: PathBuf,
|
pub os_release: PathBuf,
|
||||||
pub systemd: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ struct InstallCommand {
|
||||||
#[arg(long, default_value = "false")]
|
#[arg(long, default_value = "false")]
|
||||||
auto_enroll: bool,
|
auto_enroll: bool,
|
||||||
|
|
||||||
bootspec: PathBuf,
|
esp: PathBuf,
|
||||||
|
|
||||||
generations: Vec<PathBuf>,
|
generations: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ fn install(args: InstallCommand) -> Result<()> {
|
||||||
args.private_key,
|
args.private_key,
|
||||||
args.pki_bundle,
|
args.pki_bundle,
|
||||||
args.auto_enroll,
|
args.auto_enroll,
|
||||||
args.bootspec,
|
args.esp,
|
||||||
args.generations,
|
args.generations,
|
||||||
)
|
)
|
||||||
.install()
|
.install()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::bootspec::Bootspec;
|
|
||||||
use crate::generation::Generation;
|
use crate::generation::Generation;
|
||||||
|
|
||||||
pub struct EspPaths {
|
pub struct EspPaths {
|
||||||
|
@ -18,20 +17,22 @@ pub struct EspPaths {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EspPaths {
|
impl EspPaths {
|
||||||
pub fn new(esp: &str, generation: Generation, bootspec: &Bootspec) -> Result<Self> {
|
pub fn new(esp: impl AsRef<Path>, generation: &Generation) -> Result<Self> {
|
||||||
let esp = Path::new(esp);
|
let esp = esp.as_ref();
|
||||||
let esp_nixos = esp.join("EFI/nixos");
|
let esp_nixos = esp.join("EFI/nixos");
|
||||||
let esp_linux = esp.join("EFI/Linux");
|
let esp_linux = esp.join("EFI/Linux");
|
||||||
let esp_systemd = esp.join("EFI/systemd");
|
let esp_systemd = esp.join("EFI/systemd");
|
||||||
let esp_efi_fallback_dir = esp.join("EFI/BOOT");
|
let esp_efi_fallback_dir = esp.join("EFI/BOOT");
|
||||||
|
|
||||||
|
let bootspec = &generation.bootspec;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
esp: esp.to_owned(),
|
esp: esp.to_path_buf(),
|
||||||
nixos: esp_nixos.clone(),
|
nixos: esp_nixos.clone(),
|
||||||
kernel: esp_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?),
|
kernel: esp_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?),
|
||||||
initrd: esp_nixos.join(nixos_path(&bootspec.initrd, "initrd")?),
|
initrd: esp_nixos.join(nixos_path(&bootspec.initrd, "initrd")?),
|
||||||
linux: esp_linux.clone(),
|
linux: esp_linux.clone(),
|
||||||
lanzaboote_image: esp_linux.join(generation_path(generation)),
|
lanzaboote_image: esp_linux.join(generation_path(&generation)),
|
||||||
efi_fallback_dir: esp_efi_fallback_dir.clone(),
|
efi_fallback_dir: esp_efi_fallback_dir.clone(),
|
||||||
efi_fallback: esp_efi_fallback_dir.join("BOOTX64.EFI"),
|
efi_fallback: esp_efi_fallback_dir.join("BOOTX64.EFI"),
|
||||||
systemd: esp_systemd.clone(),
|
systemd: esp_systemd.clone(),
|
||||||
|
@ -60,6 +61,6 @@ fn nixos_path(path: impl AsRef<Path>, name: &str) -> Result<PathBuf> {
|
||||||
Ok(PathBuf::from(nixos_filename))
|
Ok(PathBuf::from(nixos_filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generation_path(generation: Generation) -> PathBuf {
|
fn generation_path(generation: &Generation) -> PathBuf {
|
||||||
PathBuf::from(format!("nixos-generation-{}.efi", generation))
|
PathBuf::from(format!("nixos-generation-{}.efi", generation))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,55 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
use crate::bootspec::Bootspec;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Generation(u64);
|
pub struct Generation {
|
||||||
|
version: u64,
|
||||||
|
pub bootspec: Bootspec,
|
||||||
|
}
|
||||||
|
|
||||||
impl Generation {
|
impl Generation {
|
||||||
pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> {
|
pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> {
|
||||||
let file_name = toplevel.as_ref().file_name().ok_or(anyhow::anyhow!(
|
let bootspec_path = toplevel.as_ref().join("bootspec/boot.v1.json");
|
||||||
"Failed to extract file name from generation"
|
let bootspec: Bootspec = serde_json::from_slice(
|
||||||
))?;
|
&fs::read(&bootspec_path).context("Failed to read bootspec file")?,
|
||||||
|
)
|
||||||
|
.context("Failed to parse bootspec json")?;
|
||||||
|
|
||||||
let file_name_str = file_name
|
Ok(Self {
|
||||||
.to_str()
|
version: parse_version(toplevel)?,
|
||||||
.with_context(|| "Failed to convert file name of generation to string")?;
|
bootspec,
|
||||||
|
})
|
||||||
let generation_version = file_name_str
|
|
||||||
.split("-")
|
|
||||||
.nth(1)
|
|
||||||
.ok_or(anyhow::anyhow!("Failed to extract version from generation"))?;
|
|
||||||
|
|
||||||
let parsed_generation_version = generation_version.parse().with_context(|| {
|
|
||||||
format!("Failed to parse generation version: {}", generation_version)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self(parsed_generation_version))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Generation {
|
impl fmt::Display for Generation {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_version(toplevel: impl AsRef<Path>) -> Result<u64> {
|
||||||
|
let file_name = toplevel.as_ref().file_name().ok_or(anyhow::anyhow!(
|
||||||
|
"Failed to extract file name from generation"
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let file_name_str = file_name
|
||||||
|
.to_str()
|
||||||
|
.with_context(|| "Failed to convert file name of generation to string")?;
|
||||||
|
|
||||||
|
let generation_version = file_name_str
|
||||||
|
.split("-")
|
||||||
|
.nth(1)
|
||||||
|
.ok_or(anyhow::anyhow!("Failed to extract version from generation"))?;
|
||||||
|
|
||||||
|
let parsed_generation_version = generation_version
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Failed to parse generation version: {}", generation_version))?;
|
||||||
|
|
||||||
|
Ok(parsed_generation_version)
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ use anyhow::{Context, Result};
|
||||||
use nix::unistd::sync;
|
use nix::unistd::sync;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use crate::bootspec::Bootspec;
|
|
||||||
use crate::esp::EspPaths;
|
use crate::esp::EspPaths;
|
||||||
use crate::generation::Generation;
|
use crate::generation::Generation;
|
||||||
use crate::pe;
|
use crate::pe;
|
||||||
|
@ -20,7 +19,7 @@ pub struct Installer {
|
||||||
private_key: PathBuf,
|
private_key: PathBuf,
|
||||||
_pki_bundle: Option<PathBuf>,
|
_pki_bundle: Option<PathBuf>,
|
||||||
_auto_enroll: bool,
|
_auto_enroll: bool,
|
||||||
bootspec: PathBuf,
|
esp: PathBuf,
|
||||||
generations: Vec<PathBuf>,
|
generations: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ impl Installer {
|
||||||
private_key: PathBuf,
|
private_key: PathBuf,
|
||||||
_pki_bundle: Option<PathBuf>,
|
_pki_bundle: Option<PathBuf>,
|
||||||
_auto_enroll: bool,
|
_auto_enroll: bool,
|
||||||
bootspec: PathBuf,
|
esp: PathBuf,
|
||||||
generations: Vec<PathBuf>,
|
generations: Vec<PathBuf>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -42,7 +41,7 @@ impl Installer {
|
||||||
private_key,
|
private_key,
|
||||||
_pki_bundle,
|
_pki_bundle,
|
||||||
_auto_enroll,
|
_auto_enroll,
|
||||||
bootspec,
|
esp,
|
||||||
generations,
|
generations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +49,7 @@ impl Installer {
|
||||||
pub fn install(&self) -> Result<()> {
|
pub fn install(&self) -> Result<()> {
|
||||||
for toplevel in &self.generations {
|
for toplevel in &self.generations {
|
||||||
let generation = Generation::from_toplevel(toplevel).with_context(|| {
|
let generation = Generation::from_toplevel(toplevel).with_context(|| {
|
||||||
format!("Failed to extract generation version from: {toplevel:?}")
|
format!("Failed to build generation from toplevel: {toplevel:?}")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
println!("Installing generation {generation}");
|
println!("Installing generation {generation}");
|
||||||
|
@ -64,16 +63,14 @@ impl Installer {
|
||||||
pub fn install_generation(&self, generation: Generation) -> Result<()> {
|
pub fn install_generation(&self, generation: Generation) -> Result<()> {
|
||||||
println!("Reading bootspec...");
|
println!("Reading bootspec...");
|
||||||
|
|
||||||
let bootspec_doc: Bootspec = serde_json::from_slice(
|
let bootspec = &generation.bootspec;
|
||||||
&fs::read(&self.bootspec).context("Failed to read bootspec file")?,
|
|
||||||
)
|
|
||||||
.context("Failed to parse bootspec json")?;
|
|
||||||
|
|
||||||
let esp_paths = EspPaths::new(&bootspec_doc.extension.esp, generation, &bootspec_doc)?;
|
let esp_paths = EspPaths::new(&self.esp, &generation)?;
|
||||||
|
|
||||||
println!("Assembling lanzaboote image...");
|
println!("Assembling lanzaboote image...");
|
||||||
|
|
||||||
let kernel_cmdline = assemble_kernel_cmdline(bootspec_doc.init, bootspec_doc.kernel_params);
|
let kernel_cmdline =
|
||||||
|
assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone());
|
||||||
|
|
||||||
// prepare a secure temporary directory
|
// prepare a secure temporary directory
|
||||||
// permission bits are not set, because when files below
|
// permission bits are not set, because when files below
|
||||||
|
@ -86,7 +83,7 @@ impl Installer {
|
||||||
let lanzaboote_image = pe::lanzaboote_image(
|
let lanzaboote_image = pe::lanzaboote_image(
|
||||||
&secure_temp_dir,
|
&secure_temp_dir,
|
||||||
&self.lanzaboote_stub,
|
&self.lanzaboote_stub,
|
||||||
&bootspec_doc.extension.os_release,
|
&bootspec.extension.os_release,
|
||||||
&kernel_cmdline,
|
&kernel_cmdline,
|
||||||
&esp_paths.kernel,
|
&esp_paths.kernel,
|
||||||
&esp_paths.initrd,
|
&esp_paths.initrd,
|
||||||
|
@ -97,8 +94,8 @@ impl Installer {
|
||||||
println!("Wrapping initrd into a PE binary...");
|
println!("Wrapping initrd into a PE binary...");
|
||||||
|
|
||||||
let initrd_location = secure_temp_dir.path().join("initrd");
|
let initrd_location = secure_temp_dir.path().join("initrd");
|
||||||
copy(&bootspec_doc.initrd, &initrd_location)?;
|
copy(&bootspec.initrd, &initrd_location)?;
|
||||||
if let Some(initrd_secrets_script) = bootspec_doc.initrd_secrets {
|
if let Some(initrd_secrets_script) = &bootspec.initrd_secrets {
|
||||||
append_initrd_secrets(&initrd_secrets_script, &initrd_location)?;
|
append_initrd_secrets(&initrd_secrets_script, &initrd_location)?;
|
||||||
}
|
}
|
||||||
let wrapped_initrd = pe::wrap_initrd(&secure_temp_dir, &self.initrd_stub, &initrd_location)
|
let wrapped_initrd = pe::wrap_initrd(&secure_temp_dir, &self.initrd_stub, &initrd_location)
|
||||||
|
@ -108,16 +105,15 @@ impl Installer {
|
||||||
|
|
||||||
let signer = Signer::new(&self.public_key, &self.private_key);
|
let signer = Signer::new(&self.public_key, &self.private_key);
|
||||||
|
|
||||||
let systemd_boot = bootspec_doc
|
let systemd_boot = bootspec
|
||||||
.extension
|
.toplevel
|
||||||
.systemd
|
.join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi");
|
||||||
.join("lib/systemd/boot/efi/systemd-bootx64.efi");
|
|
||||||
|
|
||||||
let files_to_copy_and_sign = [
|
let files_to_copy_and_sign = [
|
||||||
(&systemd_boot, &esp_paths.efi_fallback),
|
(&systemd_boot, &esp_paths.efi_fallback),
|
||||||
(&systemd_boot, &esp_paths.systemd_boot),
|
(&systemd_boot, &esp_paths.systemd_boot),
|
||||||
(&lanzaboote_image, &esp_paths.lanzaboote_image),
|
(&lanzaboote_image, &esp_paths.lanzaboote_image),
|
||||||
(&bootspec_doc.kernel, &esp_paths.kernel),
|
(&bootspec.kernel, &esp_paths.kernel),
|
||||||
(&wrapped_initrd, &esp_paths.initrd),
|
(&wrapped_initrd, &esp_paths.initrd),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -160,11 +156,11 @@ pub fn append_initrd_secrets(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assemble_kernel_cmdline(init: PathBuf, kernel_params: Vec<String>) -> Vec<String> {
|
fn assemble_kernel_cmdline(init: &Path, kernel_params: Vec<String>) -> Vec<String> {
|
||||||
let init_string = init
|
let init_string = String::from(
|
||||||
.into_os_string()
|
init.to_str()
|
||||||
.into_string()
|
.expect("Failed to convert init path to string"),
|
||||||
.expect("Failed to convert init path to string");
|
);
|
||||||
let mut kernel_cmdline: Vec<String> = vec![format!("init={}", init_string)];
|
let mut kernel_cmdline: Vec<String> = vec![format!("init={}", init_string)];
|
||||||
kernel_cmdline.extend(kernel_params);
|
kernel_cmdline.extend(kernel_params);
|
||||||
kernel_cmdline
|
kernel_cmdline
|
||||||
|
|
Loading…
Reference in New Issue