{ config, lib, pkgs, inputs, ... }: let inherit (lib) escapeShellArg isAttrs mkEnableOption mkIf mkOption recursiveUpdate ; inherit (lib.types) either enum listOf path str submodule ; inherit (builtins) concatMap; cfg = config.persist; username = config.home.username; homeDir = config.home.homeDirectory; absoluteHomePath = map (x: "${homeDir}/${x}"); in { imports = [ inputs.impermanence.homeManagerModules.impermanence ]; options = let directoryEntryType = submodule { options = { directory = mkOption { type = str; description = "The directory path to be linked."; }; method = mkOption { type = enum [ "bindfs" "symlink" ]; default = config.defaultDirectoryMethod; description = '' The linking method to be used for this specific directory entry. ''; }; }; }; common = { directories = mkOption { type = listOf (either str directoryEntryType); default = [ ]; description = '' List of directories to persist. Each element can be a string (e.g., ".cache") or an attribute set (e.g., { directory = ".local/share/Steam"; method = "symlink"; }). ''; example = [ ".config/foo" { directory = ".config/bar"; method = "symlink"; } ]; }; files = mkOption { type = listOf str; default = [ ]; description = "List of files to persist."; example = [ ".config/foo.conf" ]; }; }; in { persist = { enable = mkEnableOption "A tmpfs root with explicit opt-in state"; persistRoot = mkOption { type = path; default = "/persist${config.home.homeDirectory}"; }; # TODO backups state = recursiveUpdate { # backup = {...}; } common; cache = recursiveUpdate { clean = { enable = mkEnableOption "cleaning the cache files and directories"; dates = mkOption { type = str; default = "weekly"; description = "A systemd.time calendar description of when to clean the cache files"; }; }; } common; }; }; config = let takeAll = what: concatMap (x: x.${what}); persists = with cfg; [ state cache ]; allFiles = takeAll "files" persists; allDirs = takeAll "directories" persists; # Helper function to extract path strings from the mixed list getPaths = map (x: if isAttrs x then x.directory else x); in mkIf cfg.enable { home.persistence.${cfg.persistRoot} = { allowOther = true; directories = allDirs; files = allFiles; }; # Persist by default persist.cache.directories = [ ".cache" ]; persist.state = { directories = [ "Downloads" "Documents" "Music" "Pictures" "Videos" ".config/dconf" ".local/share/nix" ".local/share/systemd" ".ssh" ]; }; systemd.user = mkIf cfg.cache.clean.enable { services."persist-cache-cleanup-${username}" = { Unit = { Description = "Cleaning up cache files and directories for user ${username}"; Wants = [ "modprobed-db.timer" ]; }; Service = let # Extract only the path strings for the cleanup script cacheDirPaths = getPaths cfg.cache.directories; in { ExecStart = pkgs.writeShellScript "" '' ${builtins.concatStringsSep "\n" ( map (x: "${pkgs.coreutils}/bin/rm ${escapeShellArg x}") (absoluteHomePath cfg.cache.files) )} ${builtins.concatStringsSep "\n" ( map (x: "${pkgs.findutils}/bin/find ${escapeShellArg x} -mindepth 1 -delete") ( absoluteHomePath cacheDirPaths ) )} ''; Type = "simple"; }; Install.WantedBy = [ "default.target" ]; }; timers."persist-cache-cleanup-${username}" = { Unit = { Description = "Run persist-cache-cleanup-${username} service by set schedule"; PartOf = [ "persist-cache-cleanup-${username}.service" ]; }; Timer = { Persistent = true; OnCalendar = cfg.cache.clean.dates; }; Install.WantedBy = [ "timers.target" ]; }; }; }; }