diff --git a/nixos/modules/system/activation/nixos-init.nix b/nixos/modules/system/activation/nixos-init.nix index a2928cf1e0a1a..9d5094017c0bb 100644 --- a/nixos/modules/system/activation/nixos-init.nix +++ b/nixos/modules/system/activation/nixos-init.nix @@ -20,28 +20,49 @@ in package = lib.mkPackageOption pkgs "nixos-init" { }; }; - config = lib.mkIf cfg.enable { - assertions = [ - { - assertion = config.boot.initrd.systemd.enable; - message = "nixos-init can only be used with boot.initrd.systemd.enable"; - } - { - assertion = config.system.etc.overlay.enable; - message = "nixos-init can only be used with system.etc.overlay.enable"; - } - { - assertion = config.services.userborn.enable || config.systemd.sysusers.enable; - message = "nixos-init can only be used with services.userborn.enable or systemd.sysusers.enable"; - } - { - assertion = config.boot.postBootCommands == ""; - message = "nixos-init cannot be used with boot.postBootCommands"; - } - { - assertion = config.powerManagement.powerUpCommands == ""; - message = "nixos-init cannot be used with powerManagement.powerUpCommands"; - } - ]; - }; + config = lib.mkMerge [ + { + boot.bootspec.extensions = { + "org.nixos.nixos-init.v1" = { + firmware = "${config.hardware.firmware}/lib/firmware"; + modprobe_binary = "${pkgs.kmod}/bin/modprobe"; + nix_store_mount_opts = config.boot.nixStoreMountOpts; + } + // lib.optionalAttrs (config.environment.binsh != null) { + sh_binary = config.environment.binsh; + } + // lib.optionalAttrs (config.environment.usrbinenv != null) { + env_binary = config.environment.usrbinenv; + } + // lib.optionalAttrs config.system.etc.overlay.enable { + etc_metadata_image = config.system.build.etcMetadataImage; + etc_basedir = config.system.build.etcBasedir; + }; + }; + } + (lib.mkIf cfg.enable { + assertions = [ + { + assertion = config.boot.initrd.systemd.enable; + message = "nixos-init can only be used with boot.initrd.systemd.enable"; + } + { + assertion = config.system.etc.overlay.enable; + message = "nixos-init can only be used with system.etc.overlay.enable"; + } + { + assertion = config.services.userborn.enable || config.systemd.sysusers.enable; + message = "nixos-init can only be used with services.userborn.enable or systemd.sysusers.enable"; + } + { + assertion = config.boot.postBootCommands == ""; + message = "nixos-init cannot be used with boot.postBootCommands"; + } + { + assertion = config.powerManagement.powerUpCommands == ""; + message = "nixos-init cannot be used with powerManagement.powerUpCommands"; + } + ]; + }) + ]; } diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index f09bd38aa393d..50a5204b0d702 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -33,11 +33,6 @@ let ln -s ${config.system.build.etc}/etc $out/etc - ${lib.optionalString config.system.etc.overlay.enable '' - ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image - ln -s ${config.system.build.etcBasedir} $out/etc-basedir - ''} - ln -s ${config.system.path} $out/sw ln -s "$systemd" $out/systemd diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 0c20432e58f42..6afb23bd421c1 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -733,17 +733,6 @@ in cfg.package.util-linux config.system.nixos-init.package ]; - environment = { - FIRMWARE = "${config.hardware.firmware}/lib/firmware"; - MODPROBE_BINARY = "${pkgs.kmod}/bin/modprobe"; - NIX_STORE_MOUNT_OPTS = lib.concatStringsSep "," config.boot.nixStoreMountOpts; - } - // lib.optionalAttrs (config.environment.usrbinenv != null) { - ENV_BINARY = config.environment.usrbinenv; - } - // lib.optionalAttrs (config.environment.binsh != null) { - SH_BINARY = config.environment.binsh; - }; serviceConfig = { ExecStart = [ "" diff --git a/pkgs/by-name/ni/nixos-init/Cargo.lock b/pkgs/by-name/ni/nixos-init/Cargo.lock index 1be95a5be152b..4f44e49b6219a 100644 --- a/pkgs/by-name/ni/nixos-init/Cargo.lock +++ b/pkgs/by-name/ni/nixos-init/Cargo.lock @@ -14,6 +14,17 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bootspec" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75726e2aa2b4c5a9d5c4cf3cb7f24658b6ec861616088f3ef3fb72edc0599286" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -73,6 +84,12 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "itoa" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" + [[package]] name = "libc" version = "0.2.174" @@ -91,14 +108,23 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + [[package]] name = "nixos-init" version = "0.1.0" dependencies = [ "anyhow", + "bootspec", "env_logger", "indoc", "log", + "serde", + "serde_json", "tempfile", ] @@ -108,6 +134,24 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + [[package]] name = "r-efi" version = "5.3.0" @@ -127,6 +171,66 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ryu" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.20.0" @@ -140,6 +244,32 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" diff --git a/pkgs/by-name/ni/nixos-init/Cargo.toml b/pkgs/by-name/ni/nixos-init/Cargo.toml index 0672931045c68..a6495ad1ffdfc 100644 --- a/pkgs/by-name/ni/nixos-init/Cargo.toml +++ b/pkgs/by-name/ni/nixos-init/Cargo.toml @@ -7,6 +7,9 @@ edition = "2024" anyhow = "1.0.98" log = "0.4.27" env_logger = { version = "0.11.8", default-features = false } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +bootspec = "2.0.0" [dev-dependencies] tempfile = "3.20.0" diff --git a/pkgs/by-name/ni/nixos-init/src/activate.rs b/pkgs/by-name/ni/nixos-init/src/activate.rs index 048b037d9a51a..6b5ac049a528e 100644 --- a/pkgs/by-name/ni/nixos-init/src/activate.rs +++ b/pkgs/by-name/ni/nixos-init/src/activate.rs @@ -1,4 +1,7 @@ -use std::{fs, path::Path}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use anyhow::{Context, Result}; @@ -9,7 +12,8 @@ use crate::{config::Config, fs::atomic_symlink}; /// This runs both during boot and during re-activation initiated by switch-to-configuration. pub fn activate(prefix: &str, toplevel: impl AsRef, config: &Config) -> Result<()> { log::info!("Setting up /run/current-system..."); - atomic_symlink(&toplevel, format!("{prefix}/run/current-system"))?; + let system_path = PathBuf::from(prefix).join("run/current-system"); + atomic_symlink(&toplevel, system_path)?; log::info!("Setting up modprobe..."); setup_modprobe(&config.modprobe_binary)?; @@ -92,10 +96,9 @@ fn setup_firmware_search_path(firmware: impl AsRef) -> Result<()> { /// /// We do this here accidentally. `/usr/bin/env` is currently load-bearing for `NixOS`. fn setup_usrbinenv(prefix: &str, env_binary: impl AsRef) -> Result<()> { - const USRBINENV_PATH: &str = "/usr/bin/env"; - - fs::create_dir_all(format!("{prefix}/usr/bin")).context("Failed to create /usr/bin")?; - atomic_symlink(&env_binary, format!("{prefix}{USRBINENV_PATH}")) + let usrbin_path = PathBuf::from(prefix).join("usr/bin"); + fs::create_dir_all(&usrbin_path).context("Failed to create /usr/bin")?; + atomic_symlink(&env_binary, usrbin_path.join("env")) } /// Setup /bin/sh. @@ -103,6 +106,6 @@ fn setup_usrbinenv(prefix: &str, env_binary: impl AsRef) -> Result<()> { /// `/bin/sh` is an essential part of a Linux system as this path is hardcoded in the `system()` call /// from libc. See `man systemd(3)`. fn setup_binsh(prefix: &str, sh_binary: impl AsRef) -> Result<()> { - const BINSH_PATH: &str = "/bin/sh"; - atomic_symlink(&sh_binary, format!("{prefix}{BINSH_PATH}")) + let binsh_path = PathBuf::from(prefix).join("bin/sh"); + atomic_symlink(&sh_binary, binsh_path) } diff --git a/pkgs/by-name/ni/nixos-init/src/config.rs b/pkgs/by-name/ni/nixos-init/src/config.rs index b974943cb56a6..fa5eea0d36056 100644 --- a/pkgs/by-name/ni/nixos-init/src/config.rs +++ b/pkgs/by-name/ni/nixos-init/src/config.rs @@ -1,43 +1,39 @@ -use std::env; +use serde::Deserialize; +use std::fs; +use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; +use bootspec::BootJson; +#[derive(Deserialize)] pub struct Config { pub firmware: String, pub modprobe_binary: String, pub nix_store_mount_opts: Vec, pub env_binary: Option, pub sh_binary: Option, + pub etc_basedir: Option, + pub etc_metadata_image: Option, } impl Config { - /// Read the config from the environment. - /// - /// These options are provided by wrapping the binary when assembling the toplevel. - pub fn from_env() -> Result { - let nix_store_mount_opts = required("NIX_STORE_MOUNT_OPTS")? - .split(',') - .map(std::borrow::ToOwned::to_owned) - .collect(); + /// Read the config from the metadata file in the toplevel directory. + pub fn from_toplevel(toplevel: impl AsRef, prefix: &str) -> Result { + let bootspec_path = + PathBuf::from(prefix).join(toplevel.as_ref().join("boot.json").strip_prefix("/")?); - Ok(Self { - firmware: required("FIRMWARE")?, - modprobe_binary: required("MODPROBE_BINARY")?, - nix_store_mount_opts, - env_binary: optional("ENV_BINARY"), - sh_binary: optional("SH_BINARY"), - }) - } -} + let boot_json: BootJson = fs::read(bootspec_path) + .context("Failed to read bootspec file") + .and_then(|raw| serde_json::from_slice(&raw).context("Failed to read bootspec JSON"))?; -/// Read a required environment variable -/// -/// Fail with useful context if the variable is not set in the environment. -fn required(key: &str) -> Result { - env::var(key).with_context(|| format!("Failed to read {key} from environment")) -} + let config = boot_json + .extensions + .get("org.nixos.nixos-init.v1") + .context("Failed to extract nixos-init bootspec extension") + .and_then(|v| { + serde_json::from_value(v.clone()).context("Failed to deserialise config") + })?; -/// Read an optional environment variable -fn optional(key: &str) -> Option { - env::var(key).ok() + Ok(config) + } } diff --git a/pkgs/by-name/ni/nixos-init/src/find_etc.rs b/pkgs/by-name/ni/nixos-init/src/find_etc.rs index fb9faabe21b20..2af0dab6ffbbe 100644 --- a/pkgs/by-name/ni/nixos-init/src/find_etc.rs +++ b/pkgs/by-name/ni/nixos-init/src/find_etc.rs @@ -2,6 +2,7 @@ use std::{os::unix, path::Path}; use anyhow::{Context, Result}; +use crate::config::Config; use crate::{SYSROOT_PATH, canonicalize_in_chroot, find_toplevel_in_prefix}; /// Entrypoint for the `find-etc` binary. @@ -12,20 +13,23 @@ use crate::{SYSROOT_PATH, canonicalize_in_chroot, find_toplevel_in_prefix}; /// need to re-build it. pub fn find_etc() -> Result<()> { let toplevel = find_toplevel_in_prefix(SYSROOT_PATH)?; + let config = Config::from_toplevel(&toplevel, SYSROOT_PATH)?; - let etc_metadata_image = Path::new(SYSROOT_PATH).join( - canonicalize_in_chroot(SYSROOT_PATH, &toplevel.join("etc-metadata-image"))? - .strip_prefix("/")?, - ); + let basedir = config + .etc_basedir + .context("Failed to read etc_basedir from bootspec")?; + let etc_basedir = Path::new(SYSROOT_PATH) + .join(canonicalize_in_chroot(SYSROOT_PATH, Path::new(&basedir))?.strip_prefix("/")?); - let etc_basedir = Path::new(SYSROOT_PATH).join( - canonicalize_in_chroot(SYSROOT_PATH, &toplevel.join("etc-basedir"))?.strip_prefix("/")?, - ); + let metadata_image = config + .etc_metadata_image + .context("Failed to read etc_metadata_image from bootspec")?; + let etc_metadata_image = Path::new(SYSROOT_PATH) + .join(canonicalize_in_chroot(SYSROOT_PATH, Path::new(&metadata_image))?.strip_prefix("/")?); + unix::fs::symlink(etc_basedir, "/etc-basedir").context("Failed to link /etc-basedir")?; unix::fs::symlink(etc_metadata_image, "/etc-metadata-image") .context("Failed to link /etc-metadata-image")?; - unix::fs::symlink(etc_basedir, "/etc-basedir").context("Failed to link /etc-basedir")?; - Ok(()) } diff --git a/pkgs/by-name/ni/nixos-init/src/init.rs b/pkgs/by-name/ni/nixos-init/src/init.rs index aec7db39c951e..44af1979f4b27 100644 --- a/pkgs/by-name/ni/nixos-init/src/init.rs +++ b/pkgs/by-name/ni/nixos-init/src/init.rs @@ -60,7 +60,7 @@ fn remount_nix_store(prefix: &str, nix_store_mount_opts: &[String]) -> Result<() if let Some(last_nix_store_mount) = mounts.find_mountpoint(&nix_store_path) { for opt in nix_store_mount_opts { if !last_nix_store_mount.mntopts.contains(opt) { - missing_opts.push(opt.to_string()); + missing_opts.push(opt.clone()); } } if !missing_opts.is_empty() { diff --git a/pkgs/by-name/ni/nixos-init/src/initrd_init.rs b/pkgs/by-name/ni/nixos-init/src/initrd_init.rs index c391f9b9c6451..ac18705404a00 100644 --- a/pkgs/by-name/ni/nixos-init/src/initrd_init.rs +++ b/pkgs/by-name/ni/nixos-init/src/initrd_init.rs @@ -8,13 +8,14 @@ use crate::{ /// /// Initialize `NixOS` from a systemd initrd. pub fn initrd_init() -> Result<()> { - let config = Config::from_env().context("Failed to get configuration")?; let init_in_sysroot = find_init_in_prefix(SYSROOT_PATH).context("Failed to find init in sysroot")?; let init_path = if let Ok(toplevel) = verify_init_is_nixos(SYSROOT_PATH, &init_in_sysroot) { log::info!("Initializing NixOS..."); - init(SYSROOT_PATH, toplevel, &config)?; + let config = Config::from_toplevel(&toplevel, SYSROOT_PATH) + .context("Failed to get configuration")?; + init(SYSROOT_PATH, &toplevel, &config)?; None } else { log::info!("Not initializing NixOS. Switching to new root immediately...");