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
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
{
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);
};
}

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) {
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");
}

View file

@ -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<String, FileTree>,
pub(crate) nested: HashMap<String, FileTree>,
}
impl AsRef<FileTree> for FileTree {

View file

@ -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::<Vec<_>>();
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())