mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-08 11:36:05 +01:00
277 lines
8.5 KiB
Nix
277 lines
8.5 KiB
Nix
{
|
||
config,
|
||
lib,
|
||
pkgs,
|
||
...
|
||
}:
|
||
|
||
let
|
||
|
||
cfg = config.systemd.user.tmpfiles;
|
||
|
||
ruleSubmodule = lib.types.submodule (
|
||
{ name, ... }:
|
||
{
|
||
options.type = lib.mkOption {
|
||
type = lib.types.str;
|
||
readOnly = true;
|
||
default = name;
|
||
defaultText = "‹tmpfiles-type›";
|
||
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: {manpage}`tmpfiles.d(5)`
|
||
'';
|
||
};
|
||
options.mode = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "-";
|
||
example = "0755";
|
||
description = ''
|
||
The file access mode to use when creating this file or directory.
|
||
'';
|
||
};
|
||
options.user = lib.mkOption {
|
||
type = lib.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.
|
||
'';
|
||
};
|
||
options.group = lib.mkOption {
|
||
type = lib.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.
|
||
'';
|
||
};
|
||
options.age = lib.mkOption {
|
||
type = lib.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.
|
||
'';
|
||
};
|
||
options.argument = lib.mkOption {
|
||
type = lib.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: {manpage}`tmpfiles.d(5)`
|
||
'';
|
||
};
|
||
}
|
||
);
|
||
|
||
attrsWith' = placeholder: elemType: lib.types.attrsWith { inherit elemType placeholder; };
|
||
|
||
nonEmptyAttrsWith' =
|
||
placeholder: elemType:
|
||
let
|
||
attrs = lib.types.addCheck (attrsWith' placeholder elemType) (s: s != { });
|
||
in
|
||
attrs
|
||
// {
|
||
name = "nonEmptyAttrsOf";
|
||
description = "non-empty ${attrs.description}";
|
||
emptyValue = { }; # no .value attribute, meaning there is not empty value
|
||
substSubModules = m: nonEmptyAttrsWith' placeholder (elemType.substSubModules m);
|
||
};
|
||
|
||
configSubmodule = lib.types.submodule {
|
||
options.rules = lib.mkOption {
|
||
description = "The rules contained in this configuration.";
|
||
example = {
|
||
"%C".d = {
|
||
mode = "0755";
|
||
user = "alice";
|
||
group = "alice";
|
||
age = "4 weeks";
|
||
};
|
||
};
|
||
type = nonEmptyAttrsWith' "path" (nonEmptyAttrsWith' "tmpfiles-type" ruleSubmodule);
|
||
};
|
||
options.purgeOnChange = lib.mkOption {
|
||
description = ''
|
||
Whether the rules that are marked for purging, will automatically
|
||
be purged when the set of rules changes.
|
||
|
||
See {manpage}`systemd-tmpfiles(8)` for details about purging.
|
||
'';
|
||
type = lib.types.bool;
|
||
default = false;
|
||
};
|
||
};
|
||
|
||
modulePrefix = [
|
||
"systemd"
|
||
"user"
|
||
"tmpfiles"
|
||
"settings"
|
||
];
|
||
|
||
mkFileName = configName: "user-tmpfiles.d/home-manager-${configName}.conf";
|
||
|
||
mkConfigFile =
|
||
name: rules:
|
||
{
|
||
suffix ? [ name ],
|
||
}:
|
||
let
|
||
escapeArgument = lib.strings.escapeC [
|
||
"\t"
|
||
"\n"
|
||
"\r"
|
||
" "
|
||
"\\"
|
||
];
|
||
mkRule = path: rule: ''
|
||
'${rule.type}' '${path}' '${rule.mode}' '${rule.user}' '${rule.group}' '${rule.age}' ${escapeArgument rule.argument}
|
||
'';
|
||
in
|
||
{
|
||
text = ''
|
||
# This file was generated by Home Manager and should not be modified.
|
||
# Please change the option '${lib.showAttrPath (modulePrefix ++ suffix)}' instead.
|
||
${lib.pipe rules [
|
||
(lib.mapAttrs (_path: lib.attrValues))
|
||
(lib.mapAttrsToList (path: map (mkRule path)))
|
||
lib.flatten
|
||
lib.concatStrings
|
||
]}
|
||
'';
|
||
onChange = ''
|
||
run ${pkgs.systemd}/bin/systemd-tmpfiles --user --remove --create ''${DRY_RUN:+--dry-run} '${mkFileName name}'
|
||
'';
|
||
};
|
||
|
||
nonPurgedConfigs = lib.filterAttrs (_name: config: !config.purgeOnChange) cfg.settings;
|
||
purgedConfigs = lib.filterAttrs (_name: config: config.purgeOnChange) cfg.settings;
|
||
|
||
# WARNING: When changing this path, the next home-manager generation will
|
||
# not find and the rules of the old generation that are subject to purging.
|
||
purgedRulesConfigName = "purge-on-change";
|
||
|
||
in
|
||
{
|
||
meta.maintainers = with lib.maintainers; [
|
||
bmrips
|
||
dawidsowa
|
||
];
|
||
|
||
imports = [
|
||
(lib.mkRemovedOptionModule [ "systemd" "user" "tmpfiles" "rules" ] ''
|
||
It has been replaced by 'systemd.user.tmpfiles.settings'.
|
||
'')
|
||
];
|
||
|
||
options.systemd.user.tmpfiles.settings = lib.mkOption {
|
||
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 = {
|
||
cache.rules."%C".d = {
|
||
mode = "0755";
|
||
user = "alice";
|
||
group = "alice";
|
||
age = "4 weeks";
|
||
};
|
||
};
|
||
default = { };
|
||
type = attrsWith' "config-name" configSubmodule;
|
||
};
|
||
|
||
config = lib.mkMerge [
|
||
|
||
(lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
|
||
# The activation script must be enabled unconditionally in order to
|
||
# guarantee that the old rules are purged even if the new set of rules
|
||
# is empty, i.e. `cfg.rulesToPurgeOnChange == [ ]`.
|
||
home.activation.purgeTmpfiles = lib.hm.dag.entryAfter [ "writeBoundary" ] (
|
||
let
|
||
relativeXdgConfigHome = lib.strings.removePrefix "${config.home.homeDirectory}/" config.xdg.configHome;
|
||
configPath = "home-files/${relativeXdgConfigHome}/${purgedRulesConfigName}";
|
||
in
|
||
''
|
||
if [[ -v oldGenPath && -f $oldGenPath/${configPath} ]] &&
|
||
diff -q $oldGenPath/${configPath} $newGenPath/${configPath} &>/dev/null; then
|
||
verboseEcho "Purge old tmpfiles"
|
||
run ${pkgs.systemd}/bin/systemd-tmpfiles --user --purge ''${DRY_RUN:+--dry-run} $oldGenPath/${configPath}
|
||
fi
|
||
''
|
||
);
|
||
})
|
||
|
||
(lib.mkIf (cfg.settings != { }) {
|
||
assertions = [
|
||
(lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs lib.platforms.linux)
|
||
];
|
||
|
||
warnings = lib.flatten (
|
||
lib.mapAttrsToListRecursive (
|
||
path: value:
|
||
lib.optional
|
||
(lib.last path == "argument" && lib.match ''.*\\([nrt]|x[0-9A-Fa-f]{2}).*'' value != null)
|
||
''
|
||
The '${lib.showAttrPath (modulePrefix ++ path)}' option
|
||
appears to contain escape sequences, which will be escaped again.
|
||
Unescape them if this is not intended. The assigned string is:
|
||
"${value}"
|
||
''
|
||
) cfg.settings
|
||
);
|
||
|
||
xdg.configFile = {
|
||
"systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source =
|
||
"${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service";
|
||
"systemd/user/systemd-tmpfiles-setup.service".source =
|
||
"${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service";
|
||
"systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source =
|
||
"${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer";
|
||
"systemd/user/systemd-tmpfiles-clean.service".source =
|
||
"${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service";
|
||
}
|
||
// lib.mapAttrs' (
|
||
name: config: lib.nameValuePair (mkFileName name) (mkConfigFile name config.rules { })
|
||
) nonPurgedConfigs
|
||
// lib.optionalAttrs (purgedConfigs != { }) {
|
||
${mkFileName purgedRulesConfigName} =
|
||
let
|
||
purgedConfigsMerged = lib.foldl' lib.recursiveUpdate { } (lib.attrValues purgedConfigs);
|
||
in
|
||
mkConfigFile purgedRulesConfigName purgedConfigsMerged.rules { suffix = [ ]; };
|
||
};
|
||
})
|
||
|
||
];
|
||
}
|