nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1use std::{fs, os::unix::fs::PermissionsExt, path::Path, process::Command};
2
3use anyhow::{Context, Result};
4
5use crate::{activate::activate, config::Config, fs::atomic_symlink, proc_mounts::Mounts};
6
7const NIX_STORE_PATH: &str = "/nix/store";
8
9fn prefixed_store_path(prefix: &str) -> String {
10 format!("{prefix}{NIX_STORE_PATH}")
11}
12
13/// Initialize the system in a prefix.
14///
15/// This is done only once during the boot of the system.
16///
17/// It is not designed to be re-executed during the lifetime of a system boot cycle.
18pub fn init(prefix: &str, toplevel: impl AsRef<Path>, config: &Config) -> Result<()> {
19 log::info!("Setting up /nix/store permissions...");
20 setup_nix_store_permissions(prefix);
21
22 log::info!("Remounting /nix/store with the correct options...");
23 remount_nix_store(prefix, &config.nix_store_mount_opts)?;
24
25 log::info!("Setting up /run/booted-system...");
26 atomic_symlink(&toplevel, format!("{prefix}/run/booted-system"))?;
27
28 log::info!("Activating the system...");
29 activate(prefix, toplevel, config)?;
30
31 Ok(())
32}
33
34/// Set up the correct permissions for the Nix Store.
35///
36/// Gracefully fail if they cannot be changed to accommodate read-only filesystems.
37fn setup_nix_store_permissions(prefix: &str) {
38 const ROOT_UID: u32 = 0;
39 const NIXBUILD_GID: u32 = 0;
40 const NIX_STORE_MODE: u32 = 0o1775;
41
42 let nix_store_path = prefixed_store_path(prefix);
43
44 std::os::unix::fs::chown(&nix_store_path, Some(ROOT_UID), Some(NIXBUILD_GID)).ok();
45 fs::metadata(&nix_store_path)
46 .map(|metadata| {
47 let mut permissions = metadata.permissions();
48 permissions.set_mode(NIX_STORE_MODE);
49 })
50 .ok();
51}
52
53/// Remount the Nix Store in a prefix with the provided options.
54fn remount_nix_store(prefix: &str, nix_store_mount_opts: &[String]) -> Result<()> {
55 let nix_store_path = prefixed_store_path(prefix);
56
57 let mut missing_opts = Vec::new();
58 let mounts = Mounts::parse_from_proc_mounts()?;
59
60 if let Some(last_nix_store_mount) = mounts.find_mountpoint(&nix_store_path) {
61 for opt in nix_store_mount_opts {
62 if !last_nix_store_mount.mntopts.contains(opt) {
63 missing_opts.push(opt.clone());
64 }
65 }
66 if !missing_opts.is_empty() {
67 log::info!(
68 "/nix/store is missing mount options: {}.",
69 missing_opts.join(",")
70 );
71 }
72 } else {
73 log::info!("/nix/store is not a mountpoint.");
74 missing_opts.extend_from_slice(nix_store_mount_opts);
75 }
76
77 if !missing_opts.is_empty() {
78 log::info!("Remounting /nix/store with {}...", missing_opts.join(","));
79
80 mount(&["--bind", &nix_store_path, &nix_store_path])?;
81 mount(&[
82 "-o",
83 &format!("remount,bind,{}", missing_opts.join(",")),
84 &nix_store_path,
85 ])?;
86 }
87
88 Ok(())
89}
90
91/// Call `mount` with the provided `args`.
92fn mount(args: &[&str]) -> Result<()> {
93 let output = Command::new("mount")
94 .args(args)
95 .output()
96 .context("Failed to run mount. Most likely, the binary is not on PATH")?;
97
98 if !output.status.success() {
99 return Err(anyhow::anyhow!(
100 "mount executed unsuccessfully: {}",
101 String::from_utf8_lossy(&output.stdout)
102 ));
103 }
104
105 Ok(())
106}