systemd: support systemd.tmpfiles.settings

Signed-off-by: phanirithvij <phanirithvij2000@gmail.com>
This commit is contained in:
phanirithvij 2024-10-31 07:06:31 +05:30
parent a79e02428d
commit 91323fb350
4 changed files with 205 additions and 14 deletions

View file

@ -1,6 +1,139 @@
{ config, lib, ... }: {
config,
lib,
pkgs,
...
}:
let 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:
<https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
'';
};
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:
<https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
'';
};
};
}
)
)
)
);
};
# 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.<name>
pathsToRules = mapAttrsToList (
path: types: concatStrings (mapAttrsToList (_type: settingsEntryToRule path) types)
);
mkRuleFileContent = paths: concatStrings (pathsToRules paths);
in in
{ {
options = { options = {
@ -15,13 +148,60 @@ in
for the exact format. 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 = { config = {
environment.etc."tmpfiles.d/00-system-manager.conf".text = '' environment.etc = {
# This file is created automatically and should not be modified. "tmpfiles.d".source = pkgs.symlinkJoin {
# Please change the option systemd.tmpfiles.rules instead. name = "tmpfiles.d";
${lib.concatStringsSep "\n" config.systemd.tmpfiles.rules} 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);
};
} }

View file

@ -82,7 +82,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
match etc_files::activate(store_path, old_state.file_tree, ephemeral) { match etc_files::activate(store_path, old_state.file_tree, ephemeral) {
Ok(etc_tree) => { Ok(etc_tree) => {
log::info!("Activating tmp files..."); log::info!("Activating tmp files...");
match tmp_files::activate() { match tmp_files::activate(&etc_tree) {
Ok(_) => { Ok(_) => {
log::debug!("Successfully created tmp files"); log::debug!("Successfully created tmp files");
} }

View file

@ -27,11 +27,11 @@ impl FileStatus {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FileTree { pub struct FileTree {
status: FileStatus, status: FileStatus,
path: PathBuf, pub(crate) path: PathBuf,
// TODO directories and files are now both represented as a string associated with a nested // TODO directories and files are now both represented as a string associated with a nested
// map. For files the nested map is simple empty. // map. For files the nested map is simple empty.
// We could potentially optimise this. // We could potentially optimise this.
nested: HashMap<String, FileTree>, pub(crate) nested: HashMap<String, FileTree>,
} }
impl AsRef<FileTree> for FileTree { impl AsRef<FileTree> for FileTree {

View file

@ -1,15 +1,26 @@
use crate::activate; use crate::activate;
use crate::activate::etc_files::FileTree;
use super::ActivationResult; use super::ActivationResult;
use std::process; use std::process;
type TmpFilesActivationResult = ActivationResult<()>; 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::<Vec<_>>();
let mut cmd = process::Command::new("systemd-tmpfiles"); let mut cmd = process::Command::new("systemd-tmpfiles");
cmd.arg("--create") cmd.arg("--create").arg("--remove").args(conf_files);
.arg("--remove") log::debug!("running {:#?}", cmd);
.arg("/etc/tmpfiles.d/00-system-manager.conf");
let output = cmd let output = cmd
.stdout(process::Stdio::inherit()) .stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit()) .stderr(process::Stdio::inherit())