nixos-init: init env-generator

The env generator allows us to declaratively set environment variables
via the module system for all systemd generators.

This will allow us to drop systemd/0013-inherit-systemd-environment-when-calling-generators.patch
This commit is contained in:
nikstur
2026-02-05 01:45:10 +01:00
parent b9f2fa1c7d
commit 4aae99b7d7
7 changed files with 108 additions and 7 deletions

View File

@@ -742,6 +742,8 @@ in
};
systemd = {
generatorPath = [ cfg.package ];
sockets.sshd = lib.mkIf cfg.startWhenNeeded {
description = "SSH Socket";
wantedBy = [ "sockets.target" ];

View File

@@ -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"

View File

@@ -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")

View File

@@ -48,6 +48,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
"initrd-init"
"find-etc"
"resolve-in-root"
"env-generator"
];
postInstall = ''

View File

@@ -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<String, String>);
/// 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(())
}

View File

@@ -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,

View File

@@ -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;