324 lines
10 KiB
Nix
Raw Normal View History

{
config,
lib,
pkgs,
...
}:
let
2025-08-09 14:37:25 -04:00
cfg = config.me.install;
inherit (lib)
filter
attrNames
;
2025-08-09 14:37:25 -04:00
2025-08-09 21:19:13 -04:00
get_shell_values =
target:
let
homedir = config.users.users."${target.username}".home;
group = config.users.users."${target.username}".group;
in
{
source = lib.strings.escapeShellArg "${target.source}";
destination = lib.strings.escapeShellArg "${homedir}/${target.target}";
mode = lib.strings.escapeShellArg "${target.mode}";
username = lib.strings.escapeShellArg "${target.username}";
group = lib.strings.escapeShellArg "${group}";
};
2025-08-09 14:37:25 -04:00
install_user_file =
2025-08-09 19:57:09 -04:00
let
constructors = {
"overwrite" = install_user_file_overwrite;
"symlink" = install_user_file_symlink;
};
in
2025-08-09 20:27:27 -04:00
stage: target: (constructors."${target.method}"."${stage}" target);
install_user_file_overwrite = {
"check" = (target: "");
"install" = (
target:
let
inherit (get_shell_values target)
source
destination
mode
2025-08-09 21:19:13 -04:00
username
group
;
2025-08-09 21:19:13 -04:00
flags = lib.strings.concatStringsSep " " [
(if mode != "" then "-m ${mode}" else "")
(if username != "" then "-o ${username}" else "")
(if group != "" then "-g ${group}" else "")
];
2025-08-09 20:27:27 -04:00
in
2025-08-09 22:28:24 -04:00
if target.recursive then
[
''
find ${source} -type f -print0 | while read -r -d "" file; do
relative_path=$(realpath -s --relative-to ${source} "$file")
full_dest=${destination}/"$relative_path"
containing_directory=$(dirname "$full_dest")
if [ ! -e "$containing_directory" ]; then
$DRY_RUN_CMD install $VERBOSE_ARG -d "$containing_directory"
fi
$DRY_RUN_CMD install $VERBOSE_ARG -D --compare ${flags} "$file" "$full_dest"
done
''
]
else
[
''
$DRY_RUN_CMD install $VERBOSE_ARG -D --compare ${flags} ${source} ${destination}
''
]
2025-08-09 20:27:27 -04:00
);
"uninstall" = (
target:
let
inherit (get_shell_values target)
2025-08-09 22:28:24 -04:00
source
destination
;
in
2025-08-09 22:28:24 -04:00
if target.recursive then
[
''
find ${source} -type f -print0 | while read -r -d "" file; do
relative_path=$(realpath -s --relative-to ${source} "$file")
full_dest=${destination}/"$relative_path"
$DRY_RUN_CMD echo rm -f "$full_dest"
done
''
]
else
[
''
$DRY_RUN_CMD echo rm -f ${destination}
''
]
);
2025-08-09 20:27:27 -04:00
};
install_user_file_symlink = {
"check" = (target: "");
"install" = (
target:
let
inherit (get_shell_values target)
source
destination
2025-08-09 22:28:24 -04:00
mode
2025-08-09 21:19:13 -04:00
username
group
;
2025-08-09 21:19:13 -04:00
owner = lib.strings.concatStringsSep ":" (
filter (val: val != "") [
username
group
]
);
2025-08-09 22:28:24 -04:00
flags = lib.strings.concatStringsSep " " [
(if mode != "" then "-m ${mode}" else "")
(if username != "" then "-o ${username}" else "")
(if group != "" then "-g ${group}" else "")
];
2025-08-09 20:27:27 -04:00
in
2025-08-09 22:28:24 -04:00
if target.recursive then
[
''
find ${source} -type f -print0 | while read -r -d "" file; do
relative_path=$(realpath -s --relative-to ${source} "$file")
full_dest=${destination}/"$relative_path"
containing_directory=$(dirname "$full_dest")
if [ ! -e "$containing_directory" ]; then
$DRY_RUN_CMD install $VERBOSE_ARG -d ${flags} "$containing_directory"
fi
$DRY_RUN_CMD ln $VERBOSE_ARG -s "$file" "$full_dest"
$DRY_RUN_CMD chown $VERBOSE_ARG -h ${owner} "$full_dest"
done
''
]
else
[
''
$DRY_RUN_CMD ln $VERBOSE_ARG -s ${source} ${destination}
$DRY_RUN_CMD chown $VERBOSE_ARG -h ${owner} ${destination}
''
]
2025-08-09 20:27:27 -04:00
);
"uninstall" = (
target:
let
inherit (get_shell_values target)
2025-08-09 22:28:24 -04:00
source
destination
;
in
2025-08-09 22:28:24 -04:00
if target.recursive then
[
''
find ${source} -type f -print0 | while read -r -d "" file; do
relative_path=$(realpath -s --relative-to ${source} "$file")
full_dest=${destination}/"$relative_path"
$DRY_RUN_CMD echo rm -f "$full_dest"
done
''
]
else
[
''
$DRY_RUN_CMD echo rm -f ${destination}
''
]
);
2025-08-09 20:27:27 -04:00
};
in
{
imports = [ ];
options.me.install = {
user = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
2025-08-09 14:37:25 -04:00
{ name, config, ... }:
let
username = name;
in
{
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
2025-08-09 14:37:25 -04:00
defaultText = "enable";
example = lib.literalExpression false;
description = "Whether we want to install files in this user's home directory.";
};
2025-08-09 14:37:25 -04:00
file = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ name, config, ... }:
let
path = name;
in
{
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
defaultText = "enable";
example = lib.literalExpression false;
description = "Whether we want to install this file in this user's home directory.";
};
username = lib.mkOption {
type = lib.types.str;
defaultText = "username";
example = "root";
description = "The username for the user whose home directory will contain the file.";
};
target = lib.mkOption {
type = lib.types.str;
defaultText = "target";
example = ".local/share/foo/bar.txt";
description = "The path where the file should be written.";
};
method = lib.mkOption {
type = lib.types.enum [
"symlink"
"overwrite"
# "bind_mount" TODO: for directories?
];
default = "symlink";
defaultText = "me.install.file.path.method";
example = "overwrite";
description = "The way in which the file should be installed.";
};
mode = lib.mkOption {
type = lib.types.str;
default = "0444";
defaultText = "me.install.file.path.mode";
example = "0750";
description = "The read, write, execute permission flags.";
};
source = lib.mkOption {
type = lib.types.path;
defaultText = "me.install.file.path.source";
example = ./files/foo.txt;
description = "The source file to install into the destination.";
};
2025-08-09 22:28:24 -04:00
recursive = lib.mkOption {
type = lib.types.bool;
default = false;
defaultText = "recursive";
example = lib.literalExpression false;
description = "Whether we want to recurse through the directory doing individual installs for each file.";
};
2025-08-09 14:37:25 -04:00
};
2025-08-09 14:37:25 -04:00
config = {
username = lib.mkDefault username;
target = lib.mkDefault path;
};
}
)
);
};
};
}
)
);
};
};
2025-08-09 14:37:25 -04:00
config =
let
all_users = builtins.map (username: cfg.user."${username}") (attrNames cfg.user);
enabled_users = filter (user: user.enable) all_users;
all_file_targets = lib.flatten (
builtins.map (user: (builtins.map (path: user.file."${path}") (attrNames user.file))) enabled_users
);
enabled_file_targets = filter (target: target.enable) all_file_targets;
2025-08-09 22:28:24 -04:00
check_commands = lib.flatten (builtins.map (install_user_file "check") enabled_file_targets);
install_commands = lib.flatten (builtins.map (install_user_file "install") enabled_file_targets);
uninstall_commands = lib.flatten (
builtins.map (install_user_file "uninstall") enabled_file_targets
);
2025-08-09 14:37:25 -04:00
in
{
systemd.services.me-install-file = {
enable = true;
description = "me-install-file";
wantedBy = [ "multi-user.target" ];
wants = [ "multi-user.target" ];
after = [ "multi-user.target" ];
# path = with pkgs; [
# zfs
# ];
unitConfig.DefaultDependencies = "no";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
};
2025-08-09 22:28:24 -04:00
script =
''
set -o pipefail
IFS=$'\n\t'
''
+ (lib.strings.concatStringsSep "\n" (
[
]
++ check_commands
++ install_commands
));
preStop =
''
set -o pipefail
IFS=$'\n\t'
''
+ (lib.strings.concatStringsSep "\n" uninstall_commands);
2025-08-09 14:37:25 -04:00
};
2025-08-09 19:57:09 -04:00
environment.etc."install_out".text = config.systemd.services.me-install-file.script;
2025-08-09 22:28:24 -04:00
environment.etc."uninstall_out".text = config.systemd.services.me-install-file.preStop;
2025-08-09 14:37:25 -04:00
};
}