diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index c289d1961205..b87e5ccad65f 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -742,6 +742,8 @@ in }; systemd = { + generatorPath = [ cfg.package ]; + sockets.sshd = lib.mkIf cfg.startWhenNeeded { description = "SSH Socket"; wantedBy = [ "sockets.target" ]; diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 2d81b1bb3d44..9a187e86d76b 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -233,6 +233,8 @@ let proxy_env = config.networking.proxy.envVars; + json = pkgs.formats.json { }; + in { @@ -356,6 +358,30 @@ in ''; }; + generatorEnvironment = mkOption { + type = types.attrsOf types.str; + default = { }; + example = { + MY_VAR = "my-value"; + }; + description = '' + Environment variables for systemd generators. + + The `PATH` environment variable is populated via `systemd.generatorPath`. + ''; + }; + + generatorPath = mkOption { + type = types.listOf types.package; + default = [ ]; + example = [ + pkgs.hello + ]; + description = '' + Packages added to the `PATH` environment variable of all systemd generators. + ''; + }; + shutdown = mkOption { type = types.attrsOf types.path; default = { }; @@ -636,6 +662,12 @@ in "systemd/user-preset/00-nixos.preset".text = '' ignore * ''; + + "systemd/generator-environment.json".source = + json.generate "systemd-generator-environment.json" cfg.generatorEnvironment; + + "systemd/system-environment-generators/env-generator".source = + "${config.system.nixos-init.package}/bin/env-generator"; }; services.dbus.enable = true; @@ -683,12 +715,7 @@ in systemd.managerEnvironment = { # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools # util-linux is needed for the main fsck utility wrapping the fs-specific ones - PATH = lib.makeBinPath ( - config.system.fsPackages - ++ [ cfg.package.util-linux ] - # systemd-ssh-generator needs sshd in PATH - ++ lib.optional config.services.openssh.enable config.services.openssh.package - ); + PATH = lib.makeBinPath (config.system.fsPackages ++ [ cfg.package.util-linux ]); LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; TZDIR = "/etc/zoneinfo"; # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable @@ -704,6 +731,16 @@ in DefaultIPAccounting = lib.mkDefault true; }; + # These are needed for systemd-fstab-generator to schedule systemd-fsck@ + # units. + systemd.generatorPath = config.system.fsPackages ++ [ + cfg.package.util-linux + ]; + + systemd.generatorEnvironment = { + PATH = lib.makeBinPath cfg.generatorPath; + }; + system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [ "DEVTMPFS" "CGROUPS" diff --git a/nixos/tests/systemd-ssh-proxy.nix b/nixos/tests/systemd-ssh-proxy.nix index 6ccdc0012b4f..38ae79ba9c1c 100644 --- a/nixos/tests/systemd-ssh-proxy.nix +++ b/nixos/tests/systemd-ssh-proxy.nix @@ -19,6 +19,7 @@ in nodes = { virthost = { + environment.systemPackages = [ pkgs.jq ]; services.openssh = { enable = true; settings.PermitRootLogin = "prohibit-password"; @@ -48,6 +49,10 @@ in virthost.succeed("cp '${snakeOilEd25519PrivateKey}' ~/.ssh/id_ed25519") virthost.succeed("chmod 600 ~/.ssh/id_ed25519") + with subtest("Check the environment generator"): + print(virthost.succeed("jq '.' /etc/systemd/generator-environment.json")) + print(virthost.succeed("/etc/systemd/system-environment-generators/env-generator")) + with subtest("ssh into a container with AF_UNIX"): virthost.wait_for_unit("container@guest.service") virthost.wait_until_succeeds("ssh -i ~/.ssh/id_ed25519 unix/run/systemd/nspawn/unix-export/guest/ssh echo meow | grep meow") diff --git a/pkgs/by-name/ni/nixos-init/package.nix b/pkgs/by-name/ni/nixos-init/package.nix index 0e4c26cb2dd3..c84c711c7599 100644 --- a/pkgs/by-name/ni/nixos-init/package.nix +++ b/pkgs/by-name/ni/nixos-init/package.nix @@ -48,6 +48,7 @@ rustPlatform.buildRustPackage (finalAttrs: { "initrd-init" "find-etc" "resolve-in-root" + "env-generator" ]; postInstall = '' diff --git a/pkgs/by-name/ni/nixos-init/src/env_generator.rs b/pkgs/by-name/ni/nixos-init/src/env_generator.rs new file mode 100644 index 000000000000..69995b601e59 --- /dev/null +++ b/pkgs/by-name/ni/nixos-init/src/env_generator.rs @@ -0,0 +1,53 @@ +use std::{ + collections::HashMap, + fs, + io::{self, Write}, +}; + +use anyhow::{Context, Result}; +use serde::Deserialize; + +const CONFIG_PATH: &str = "/etc/systemd/generator-environment.json"; +const KMSG_PATH: &str = "/dev/kmsg"; + +#[derive(Deserialize)] +struct Config(HashMap); + +/// Implementation for the entrypoint of the `env-generator` binary. +/// +/// Reads the JSON config for the systemd generator environment and prints it in KEY=VALUE format +/// to stdout. This makes the configured environment variables available for all systemd +/// generators. +fn env_generator_impl() -> Result<()> { + let content = fs::read(CONFIG_PATH).with_context(|| format!("Failed to read {CONFIG_PATH}"))?; + let config: Config = serde_json::from_slice(&content).context("Failed to parse config")?; + + let mut buffer = Vec::new(); + for (key, value) in config.0 { + writeln!(&mut buffer, "{key}=\"{value}\"").context("Failed to write to buffer")?; + } + + let stdout = io::stdout(); + let mut locked = stdout.lock(); + locked + .write_all(&buffer) + .context("Failed to write to stdout")?; + + Ok(()) +} + +/// Entrypoint for the `env-generator` binary. +/// +/// Generators cannot use normal logging but have to write to /dev/kmsg. +/// +/// The return value is just here so that we can use the `main.rs` entrypoint for this binary. +/// Errors returned from this function will not be logged and thus are meaningless. +pub fn env_generator() -> Result<()> { + if let Err(err) = env_generator_impl() { + // Sometimes we do not have /dev/kmsg, e.g. inside a container + if let Ok(mut kmsg) = fs::OpenOptions::new().write(true).open(KMSG_PATH) { + let _ = write!(kmsg, "<3>env-generator: {err:#}"); + } + } + Ok(()) +} diff --git a/pkgs/by-name/ni/nixos-init/src/lib.rs b/pkgs/by-name/ni/nixos-init/src/lib.rs index 2ee90c6f8228..e9a716724051 100644 --- a/pkgs/by-name/ni/nixos-init/src/lib.rs +++ b/pkgs/by-name/ni/nixos-init/src/lib.rs @@ -1,5 +1,6 @@ mod activate; mod config; +mod env_generator; mod find_etc; mod fs; mod init; @@ -14,6 +15,7 @@ use anyhow::{Context, Result, bail}; pub use crate::{ activate::activate, + env_generator::env_generator, find_etc::find_etc, init::init, initrd_init::initrd_init, diff --git a/pkgs/by-name/ni/nixos-init/src/main.rs b/pkgs/by-name/ni/nixos-init/src/main.rs index a051af590be3..4f74415b8d36 100644 --- a/pkgs/by-name/ni/nixos-init/src/main.rs +++ b/pkgs/by-name/ni/nixos-init/src/main.rs @@ -2,7 +2,7 @@ use std::{env, io::Write, process::ExitCode}; use log::Level; -use nixos_init::{find_etc, initrd_init, resolve_in_root}; +use nixos_init::{env_generator, find_etc, initrd_init, resolve_in_root}; fn main() -> ExitCode { let arg0 = env::args() @@ -15,6 +15,7 @@ fn main() -> ExitCode { "find-etc" => find_etc, "resolve-in-root" => resolve_in_root, "initrd-init" => initrd_init, + "env-generator" => env_generator, _ => { log::error!("Command {arg0} unknown"); return ExitCode::FAILURE;