{ config, lib, pkgs, ... }: let cfg = config.me.install; inherit (lib) filter attrNames ; 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}"; dir_mode = lib.strings.escapeShellArg "${target.dir_mode}"; username = lib.strings.escapeShellArg "${target.username}"; group = lib.strings.escapeShellArg "${group}"; }; install_user_file = let constructors = { "overwrite" = install_user_file_overwrite; "symlink" = install_user_file_symlink; }; in stage: target: (constructors."${target.method}"."${stage}" target); install_user_file_overwrite = { "check" = (target: ""); "install" = ( target: let inherit (get_shell_values target) source destination mode dir_mode username group ; flags = lib.strings.concatStringsSep " " [ (if mode != "" then "-m ${mode}" else "") (if username != "" then "-o ${username}" else "") (if group != "" then "-g ${group}" else "") ]; dir_flags = lib.strings.concatStringsSep " " [ (if dir_mode != "" then "-m ${dir_mode}" else "") (if username != "" then "-o ${username}" else "") (if group != "" then "-g ${group}" else "") ]; in 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" create_containing_directories "$full_dest" ${dir_flags} $DRY_RUN_CMD install $VERBOSE_ARG --compare ${flags} "$file" "$full_dest" done '' ] else [ '' $DRY_RUN_CMD install $VERBOSE_ARG -D --compare ${flags} ${source} ${destination} '' ] ); "uninstall" = ( target: let inherit (get_shell_values target) source destination ; in 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} '' ] ); }; install_user_file_symlink = { "check" = (target: ""); "install" = ( target: let inherit (get_shell_values target) source destination mode dir_mode username group ; owner = lib.strings.concatStringsSep ":" ( filter (val: val != "") [ username group ] ); dir_flags = lib.strings.concatStringsSep " " [ (if dir_mode != "" then "-m ${dir_mode}" else "") (if username != "" then "-o ${username}" else "") (if group != "" then "-g ${group}" else "") ]; in 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" create_containing_directories "$full_dest" ${dir_flags} $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} '' ] ); "uninstall" = ( target: let inherit (get_shell_values target) source destination ; in 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} '' ] ); }; in { imports = [ ]; options.me.install = { user = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( { name, config, ... }: let username = name; in { options = { enable = lib.mkOption { type = lib.types.bool; default = true; defaultText = "enable"; example = lib.literalExpression false; description = "Whether we want to install files in this user's home directory."; }; 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."; }; dir_mode = lib.mkOption { type = lib.types.str; default = "0755"; defaultText = "dir_mode"; example = "0755"; description = "The read, write, execute permission flags for any parent directories that need to be created."; }; 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."; }; 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."; }; }; config = { username = lib.mkDefault username; target = lib.mkDefault path; }; } ) ); }; }; } ) ); }; }; 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; 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 ); 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"; }; script = '' set -o pipefail IFS=$'\n\t' source ${./files/lib.bash} '' + (lib.strings.concatStringsSep "\n" ( [ ] ++ check_commands ++ install_commands )); preStop = '' set -o pipefail IFS=$'\n\t' source ${./files/lib.bash} '' + (lib.strings.concatStringsSep "\n" uninstall_commands); }; environment.etc."install_out".text = config.systemd.services.me-install-file.script; environment.etc."uninstall_out".text = config.systemd.services.me-install-file.preStop; }; }