From 91323fb35084256630542de8cc5c768648cbffa6 Mon Sep 17 00:00:00 2001 From: phanirithvij Date: Thu, 31 Oct 2024 07:06:31 +0530 Subject: [PATCH 1/3] systemd: support systemd.tmpfiles.settings Signed-off-by: phanirithvij --- nix/modules/tmpfiles.nix | 194 +++++++++++++++++++++++++++-- src/activate.rs | 2 +- src/activate/etc_files/etc_tree.rs | 4 +- src/activate/tmp_files.rs | 19 ++- 4 files changed, 205 insertions(+), 14 deletions(-) diff --git a/nix/modules/tmpfiles.nix b/nix/modules/tmpfiles.nix index 33a9894..e869380 100644 --- a/nix/modules/tmpfiles.nix +++ b/nix/modules/tmpfiles.nix @@ -1,6 +1,139 @@ -{ config, lib, ... }: +{ + config, + lib, + pkgs, + ... +}: let - inherit (lib) types; + inherit (lib) + concatStrings + concatStringsSep + getLib + literalExpression + mapAttrsToList + mkOption + types + ; + # copied from nixos/modules/system/boot/systemd/tmpfiles.nix + settingsOption = { + description = '' + Declare systemd-tmpfiles rules to create, delete, and clean up volatile + and temporary files and directories. + + Even though the service is called `*tmp*files` you can also create + persistent files. + ''; + example = { + "10-mypackage" = { + "/var/lib/my-service/statefolder".d = { + mode = "0755"; + user = "root"; + group = "root"; + }; + }; + }; + default = { }; + type = types.attrsOf ( + types.attrsOf ( + types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + type = mkOption { + type = types.str; + default = name; + example = "d"; + description = '' + The type of operation to perform on the file. + + The type consists of a single letter and optionally one or more + modifier characters. + + Please see the upstream documentation for the available types and + more details: + + ''; + }; + mode = mkOption { + type = types.str; + default = "-"; + example = "0755"; + description = '' + The file access mode to use when creating this file or directory. + ''; + }; + user = mkOption { + type = types.str; + default = "-"; + example = "root"; + description = '' + The user of the file. + + This may either be a numeric ID or a user/group name. + + If omitted or when set to `"-"`, the user and group of the user who + invokes systemd-tmpfiles is used. + ''; + }; + group = mkOption { + type = types.str; + default = "-"; + example = "root"; + description = '' + The group of the file. + + This may either be a numeric ID or a user/group name. + + If omitted or when set to `"-"`, the user and group of the user who + invokes systemd-tmpfiles is used. + ''; + }; + age = mkOption { + type = types.str; + default = "-"; + example = "10d"; + description = '' + Delete a file when it reaches a certain age. + + If a file or directory is older than the current time minus the age + field, it is deleted. + + If set to `"-"` no automatic clean-up is done. + ''; + }; + argument = mkOption { + type = types.str; + default = ""; + example = ""; + description = '' + An argument whose meaning depends on the type of operation. + + Please see the upstream documentation for the meaning of this + parameter in different situations: + + ''; + }; + }; + } + ) + ) + ) + ); + }; + + # generates a single entry for a tmpfiles.d rule + settingsEntryToRule = path: entry: '' + '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${entry.argument} + ''; + + # generates a list of tmpfiles.d rules from the attrs (paths) under tmpfiles.settings. + pathsToRules = mapAttrsToList ( + path: types: concatStrings (mapAttrsToList (_type: settingsEntryToRule path) types) + ); + + mkRuleFileContent = paths: concatStrings (pathsToRules paths); + in { options = { @@ -15,13 +148,60 @@ in for the exact format. ''; }; + + systemd.tmpfiles.settings = lib.mkOption settingsOption; + + systemd.tmpfiles.packages = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExpression "[ pkgs.lvm2 ]"; + apply = map getLib; + description = '' + List of packages containing {command}`systemd-tmpfiles` rules. + + All files ending in .conf found in + {file}`«pkg»/lib/tmpfiles.d` + will be included. + If this folder does not exist or does not contain any files an error will be returned instead. + + If a {file}`lib` output is available, rules are searched there and only there. + If there is no {file}`lib` output it will fall back to {file}`out` + and if that does not exist either, the default output will be used. + ''; + }; + }; config = { - environment.etc."tmpfiles.d/00-system-manager.conf".text = '' - # This file is created automatically and should not be modified. - # Please change the option ‘systemd.tmpfiles.rules’ instead. - ${lib.concatStringsSep "\n" config.systemd.tmpfiles.rules} - ''; + environment.etc = { + "tmpfiles.d".source = pkgs.symlinkJoin { + name = "tmpfiles.d"; + paths = map (p: p + "/lib/tmpfiles.d") config.systemd.tmpfiles.packages; + postBuild = '' + for i in $(cat $pathsPath); do + (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || ( + echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files." + exit 1 + ) + done + ''; + }; + }; + systemd.tmpfiles.packages = + [ + (pkgs.writeTextFile { + name = "system-manager-tmpfiles.d"; + destination = "/lib/tmpfiles.d/00-system-manager.conf"; + text = '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.tmpfiles.rules’ instead. + + ${concatStringsSep "\n" config.systemd.tmpfiles.rules} + ''; + }) + ] + ++ (mapAttrsToList ( + name: paths: pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (mkRuleFileContent paths) + ) config.systemd.tmpfiles.settings); }; } diff --git a/src/activate.rs b/src/activate.rs index 22ab114..5318c8a 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -82,7 +82,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> { match etc_files::activate(store_path, old_state.file_tree, ephemeral) { Ok(etc_tree) => { log::info!("Activating tmp files..."); - match tmp_files::activate() { + match tmp_files::activate(&etc_tree) { Ok(_) => { log::debug!("Successfully created tmp files"); } diff --git a/src/activate/etc_files/etc_tree.rs b/src/activate/etc_files/etc_tree.rs index 54b2d7e..661ee52 100644 --- a/src/activate/etc_files/etc_tree.rs +++ b/src/activate/etc_files/etc_tree.rs @@ -27,11 +27,11 @@ impl FileStatus { #[serde(rename_all = "camelCase")] pub struct FileTree { status: FileStatus, - path: PathBuf, + pub(crate) path: PathBuf, // TODO directories and files are now both represented as a string associated with a nested // map. For files the nested map is simple empty. // We could potentially optimise this. - nested: HashMap, + pub(crate) nested: HashMap, } impl AsRef for FileTree { diff --git a/src/activate/tmp_files.rs b/src/activate/tmp_files.rs index 4bb66d4..e4741f0 100644 --- a/src/activate/tmp_files.rs +++ b/src/activate/tmp_files.rs @@ -1,15 +1,26 @@ use crate::activate; +use crate::activate::etc_files::FileTree; use super::ActivationResult; use std::process; type TmpFilesActivationResult = ActivationResult<()>; -pub fn activate() -> TmpFilesActivationResult { +pub fn activate(etc_tree: &FileTree) -> TmpFilesActivationResult { + let conf_files = etc_tree + .nested + .get("etc") + .unwrap() + .nested + .get("tmpfiles.d") + .unwrap() + .nested + .iter() + .map(|(_, node)| node.path.to_string_lossy().to_string()) + .collect::>(); let mut cmd = process::Command::new("systemd-tmpfiles"); - cmd.arg("--create") - .arg("--remove") - .arg("/etc/tmpfiles.d/00-system-manager.conf"); + cmd.arg("--create").arg("--remove").args(conf_files); + log::debug!("running {:#?}", cmd); let output = cmd .stdout(process::Stdio::inherit()) .stderr(process::Stdio::inherit()) From 79cd7d93e65803242c280f165160e1a62fc3bb34 Mon Sep 17 00:00:00 2001 From: phanirithvij Date: Fri, 1 Nov 2024 18:11:46 +0530 Subject: [PATCH 2/3] systemd: systemd.tmpfiles.settings test Signed-off-by: phanirithvij --- examples/example.nix | 5 +++++ test/nix/modules/default.nix | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/examples/example.nix b/examples/example.nix index 123ce3c..9d238e4 100644 --- a/examples/example.nix +++ b/examples/example.nix @@ -80,5 +80,10 @@ ) ); systemd.tmpfiles.rules = [ "D /var/tmp/system-manager 0755 root root -" ]; + systemd.tmpfiles.settings.sample = { + "/var/tmp/sample".d = { + mode = "0755"; + }; + }; }; } diff --git a/test/nix/modules/default.nix b/test/nix/modules/default.nix index 4191bdc..914993e 100644 --- a/test/nix/modules/default.nix +++ b/test/nix/modules/default.nix @@ -175,6 +175,10 @@ forEachUbuntuImage "example" { vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf") vm.succeed("test -d /var/tmp/system-manager") + vm.succeed("test -d /var/tmp/sample") + + vm.succeed("test -f /etc/tmpfiles.d/sample.conf") + vm.succeed("test -f /etc/tmpfiles.d/00-system-manager.conf") ${system-manager.lib.activateProfileSnippet { node = "vm"; From eb602df12b7f3262e8a4359045d3947bcb7858df Mon Sep 17 00:00:00 2001 From: Phani Rithvij Date: Fri, 8 Nov 2024 18:30:19 +0530 Subject: [PATCH 3/3] systemd: avoid using unwrap Co-authored-by: Ramses --- src/activate/tmp_files.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/activate/tmp_files.rs b/src/activate/tmp_files.rs index e4741f0..aa03b92 100644 --- a/src/activate/tmp_files.rs +++ b/src/activate/tmp_files.rs @@ -10,14 +10,14 @@ pub fn activate(etc_tree: &FileTree) -> TmpFilesActivationResult { let conf_files = etc_tree .nested .get("etc") - .unwrap() - .nested - .get("tmpfiles.d") - .unwrap() - .nested - .iter() - .map(|(_, node)| node.path.to_string_lossy().to_string()) - .collect::>(); + .and_then(|etc| etc.nested.get("tmpfiles.d")) + .map_or(vec![], |tmpfiles_d| { + tmpfiles_d + .nested + .iter() + .map(|(_, node)| node.path.to_string_lossy().to_string()) + .collect::>() + }); let mut cmd = process::Command::new("systemd-tmpfiles"); cmd.arg("--create").arg("--remove").args(conf_files); log::debug!("running {:#?}", cmd);