{ config, lib, pkgs, ... }: let cfg = config.reposync; stowType = lib.types.submodule ( {name, ...}: { options = { enable = lib.mkEnableOption "call stow for this target stow command"; targetPrefix = lib.mkOption { type = lib.types.path; default = config.home.homeDirectory; description = "absolute prefix for the stow target"; }; target = lib.mkOption { type = lib.types.str; default = "."; description = "path to stow target relative to targetPrefix (home directory by default)"; }; packages = lib.mkOption { type = lib.types.str; default = name; defaultText = ""; example = "vim zsh"; description = "packages to stow from repository. use '.' to automatically stow all"; }; }; config = { enable = lib.mkDefault true; }; } ); outOfStoreGitRepositoryType = lib.types.submodule ( { name, config, ... }: { options = { enable = lib.mkEnableOption "whether to download this repository"; server = lib.mkOption { type = lib.types.str; default = "https://github.com/"; example = "git@github.com:"; description = "git server suffixed with the repo delimiter (e.g. '/' or ':')"; }; repository = lib.mkOption { type = lib.types.str; default = name; defaultText = ""; example = "nix-community/home-manager"; description = "git repository url"; }; # TODO test to add this remote feature #remotes = lib.mkOption { # type = lib.types.attrsOf lib.types.str; # default = {}; # example = { # fork = "https://github.com/tjkeller-xyz/home-manager.git"; # }; # description = "alternative remotes to the git repository"; #}; #defaultRemote = lib.mkOption { # type = lib.types.nullOr lib.types.str; # default = null; # example = "fork"; # description = "default remote to use"; #}; #branch = lib.mkOption { # type = lib.types.nullOr lib.types.str; # default = null; # example = "master"; # description = "pull from this branch. set to null for default branch"; #}; targetPrefix = lib.mkOption { type = lib.types.path; default = config.home.homeDirectory; description = "absolute prefix for the git repository path"; }; target = lib.mkOption { type = lib.types.str; default = name; defaultText = ""; description = "path to git repository relative to targetPrefix (home directory by default)"; }; stow = lib.mkOption { type = lib.types.attrsOf stowType; default = {}; description = "stow packages from the repository"; }; extraCloneOptions = lib.mkOption { type = lib.types.str; default = ""; example = "--recurse-submodules"; description = "extra command flags to add to git clone"; }; extraPullOptions = lib.mkOption { type = lib.types.str; default = ""; example = "--recurse-submodules"; description = "extra command flags to add to git pull"; }; extraCommands = lib.mkOption { type = lib.types.str; default = ""; description = '' extra commands to run each time repo is synced. IMPORANT: all commands should be idempotent to prevent breaking after repeated syncs. like `home.activation` scripts, extra commands should respect the DRY_RUN environment variable and use $VERBOSE_ARG in case the verbose flag is set. commands can be prefixed with `run` to automatically respect the DRY_RUN function. ''; }; generatedCalls = lib.mkOption { type = lib.types.listOf lib.types.str; readOnly = true; description = "generated calls for reposync.sh script"; }; }; config = let # generate calls callClone = url: target: flags: ''clonemissing "${url}" "${target}" "${flags}"''; callPull = target: flags: ''pull "${target}" "${flags}"''; callRemoteAdd = target: remote: url: ''remoteadd "${target}" "${remote}" "${url}"''; callStowRepo = dir: target: packages: ''stowrepo "${dir}" "${target}" "${packages}"''; # misc stowToCalls = s: callStowRepo target (combineTarget s.targetPrefix s.target) s.packages; combineTarget = prefix: target: prefix + "/" + target; target = combineTarget config.targetPrefix config.target; url = config.server + config.repository; in { enable = lib.mkDefault true; generatedCalls = lib.lists.flatten [ (callClone url target config.extraCloneOptions) (callPull target config.extraPullOptions) #(lib.mapAttrsToList (name: url: callRemoteAdd name url) config.remotes) (lib.mapAttrsToList (name: s: lib.mkIf s.enable (stowToCalls s)) config.stow) config.extraCommands ]; }; } ); in { options.reposync = { enable = lib.mkEnableOption "generate and install reposync script"; outOfStoreGitRepository = lib.mkOption { type = lib.types.attrsOf outOfStoreGitRepositoryType; default = {}; description = "imperative git repositories to be cloned"; }; }; config = let repocfg = cfg.outOfStoreGitRepository; lines = lineslist: lib.strings.concatStringsSep "\n" lineslist; fname = name: ''_sync-${name}''; wrapRepoCalls = name: r: '' function ${fname name}() { echo "Syncing repository '${name}'" ${lines r.generatedCalls} echo "Done" } ''; allRepoCallFuncs = lines (lib.mapAttrsToList (name: r: wrapRepoCalls name r) repocfg); syncAllFunc = '' function all() { ${lines (lib.mapAttrsToList (name: r: fname name) repocfg)} } ''; argCases = '' ${lines (lib.mapAttrsToList (name: r: "${name}) ${fname name} ;;") repocfg)} -a|--all) all ;; ''; reposyncSrc = pkgs.writeShellScriptBin "reposync" '' export PATH="${pkgs.git}/bin:$PATH" export PATH="${pkgs.stow}/bin:$PATH" ${builtins.readFile ./reposync-functions.sh} ${allRepoCallFuncs} ${syncAllFunc} for arg in "$@"; do case "$arg" in ${argCases} esac done ''; hm-reposync = pkgs.stdenv.mkDerivation rec { name = "hm-reposync"; srcs = [reposyncSrc ./reposync.1]; dontUnpack = true; buildInputs = with pkgs; [git stow]; installPhase = '' read src_bin src_man <<< "$srcs" # split srcs mkdir -p $out/bin $out/share/man/man1 cp $src_bin/bin/reposync $out/bin chmod +x $out/bin/reposync cp $src_man $out/share/man/man1/reposync.1 ''; }; in lib.mkIf cfg.enable { home.packages = [hm-reposync]; }; } # vim: set ts=2 sw=2 et: