Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

systemd: support systemd.tmpfiles.settings #148

Merged
merged 3 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/example.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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";
};
};
};
}
194 changes: 187 additions & 7 deletions nix/modules/tmpfiles.nix
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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
'';
Copy link
Contributor Author

@phanirithvij phanirithvij Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it differs from nixpkgs version

orig nixpkgs version https://github.com/NixOS/nixpkgs/blob/01ddc67fd61124e3c9df1438e6874731af6d3f06/nixos/modules/system/boot/systemd/tmpfiles.nix#L240-L243

        '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
          rm -f $out/${removePrefix "tmpfiles.d/" name}
        '') config.system.build.etc.passthru.targets;
      }) + "/*";
  • there is no support for globs in system-manager's environment.etc.source so + "/*"; is out.
  • I don't know what config.system.build.etc.passthru.targets is tbh, can't find it in system-manager codebase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  etc' = lib.filter (f: f.enable) (lib.attrValues config.environment.etc);

  etc = pkgs.runCommandLocal "etc" {
    # This is needed for the systemd module
    passthru.targets = map (x: x.target) etc';
  } /* sh */ ''

from nixos/modules/system/etc/etc.nix

I don't know if it is needed for system-manager?

};
};
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);
};
}
2 changes: 1 addition & 1 deletion src/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
4 changes: 2 additions & 2 deletions src/activate/etc_files/etc_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 15 additions & 4 deletions src/activate/tmp_files.rs
Original file line number Diff line number Diff line change
@@ -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")
.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::<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())
Expand Down
4 changes: 4 additions & 0 deletions test/nix/modules/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down