# this module is heavily inspired by a home-manager module # created by @piousdeer, although improved and slightly simplified. # see: https://gist.github.com/piousdeer/b29c272eaeba398b864da6abf6cb5daa { config, lib, ... }: let inherit (lib) hm types assertMsg mkOption escapeShellArg splitString recursiveUpdate setAttrByPath concatLines ; optionPathsToExtend = [ "home.file" "xdg.configFile" "xdg.dataFile" "xdg.stateFile" ]; in { options = let # like `mergeAttrsList`, but doesn't overwrite deep paths, # adding to the inner attrsets instead. recursiveMergeAttrsList = builtins.foldl' recursiveUpdate { }; path = p: splitString "." p; mutableOption = mkOption { type = types.attrsOf ( types.submodule { options.mutable = mkOption { type = types.bool; default = false; description = '' Instead of linking from this file to the nix store, copy the contents to the path directly, thus making the file writable to programs. Requires the `force` option to be true. ''; }; } ); }; in recursiveMergeAttrsList (map (p: setAttrByPath (path p) mutableOption) optionPathsToExtend); config = { home.activation.overwriteMutableFiles = hm.dag.entryAfter [ "linkGeneration" ] ( let # all xdg files end up inside the normal `home.file` # list either way, we don't have to look into the others. cfg = config.home.file // config.xdg.configFile; allFiles = builtins.attrValues cfg; mutableFiles = builtins.filter ( f: (f.mutable or false) && assertMsg f.force "`mutable` also requires `force` to be set to true" ) allFiles; copyFileCommand = { source, target, ... }: '' run cp $VERBOSE_ARG --remove-destination --no-preserve=mode \ ${escapeShellArg source} ${escapeShellArg target} \ || errorEcho "Overwriting mutable file '${escapeShellArg target}' failed!" ''; in '' ${config.lib.bash.initHomeManagerLib} verboseEcho "Overwriting symlinks with mutable files" ${concatLines (map copyFileCommand mutableFiles)} '' ); }; }