From 33b704060e74e970313a1d64f6112588fca2332c Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Thu, 24 Oct 2024 14:52:36 +0200 Subject: refactor(modules/unison): Migrate to `by-name` and parameterize --- modules/by-name/un/unison/module.nix | 92 ++++++++++++++++++++++++++++++ modules/by-name/un/unison/shellScript.nix | 95 +++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 modules/by-name/un/unison/module.nix create mode 100644 modules/by-name/un/unison/shellScript.nix (limited to 'modules/by-name') diff --git a/modules/by-name/un/unison/module.nix b/modules/by-name/un/unison/module.nix new file mode 100644 index 00000000..baf92b02 --- /dev/null +++ b/modules/by-name/un/unison/module.nix @@ -0,0 +1,92 @@ +{ + lib, + config, + pkgs, + sysLib, + ... +}: let + cfg = config.soispha.services.unison; + + script = import ./shellScript.nix {inherit sysLib lib pkgs cfg;}; +in { + options.soispha.services.unison = let + homePath = lib.types.strMatching "^~.*"; + in { + enable = lib.mkEnableOption "a unison home sync script"; + + dataDir = lib.mkOption { + type = lib.types.path; + description = '' + This directory is used by unison to store it's data. + ''; + }; + + foreign = { + address = lib.mkOption { + type = lib.types.str; + description = '' + The address to contact via ssh, when syncing. + ''; + }; + userName = lib.mkOption { + type = lib.types.str; + description = '' + The user name to try to login with at the foreign host. + ''; + }; + }; + + userSourceDir = lib.mkOption { + description = '' + The directory to replace the `~` in the relative user paths with. + If using `impermanence`, this should be the path to the persistent home directory. + ''; + }; + + pathsToIgnore = lib.mkOption { + type = lib.types.listOf homePath; + default = []; + description = '' + A list of the paths that should not be synced. + Beware that this applies not only to this path, but also to all paths under it. + ''; + }; + pathsToSync = lib.mkOption { + type = lib.types.listOf homePath; + default = []; + description = '' + A list of the paths that should be synced. + Beware that this applies not only to this path, but also to all paths under it. + ''; + }; + + unisonOptions = lib.mkOption { + internal = true; + default = { + sshcmd = "ssh"; + ui = "text"; + auto = "true"; + # This is a trap, thanks to the HM links + # TODO: Auto-ignore all `home.file` paths <2024-10-24> + links = "false"; + + backupdir = "${cfg.dataDir}/backups"; + backuploc = "central"; + }; + type = lib.types.attrsOf lib.types.str; + description = "The options passed to every unison call."; + }; + }; + + config = lib.mkIf cfg.enable { + home-manager.users.soispha = { + home.sessionVariables = { + UNISON = cfg.dataDir; + }; + home.packages = [ + pkgs.unison + script + ]; + }; + }; +} diff --git a/modules/by-name/un/unison/shellScript.nix b/modules/by-name/un/unison/shellScript.nix new file mode 100644 index 00000000..5ff0c219 --- /dev/null +++ b/modules/by-name/un/unison/shellScript.nix @@ -0,0 +1,95 @@ +{ + sysLib, + lib, + pkgs, + cfg, +}: let + esa = lib.strings.escapeShellArg; + + expandHomePath = path: + if lib.strings.hasPrefix "~" path + then "${cfg.userSourceDir}${lib.strings.removePrefix "~" path}" + else + builtins.throw + '' + BUG: Every pathname needs to start with a '~'. + This should have been checked by the NixOS module system? + ''; + + getIgnored = paths_to_ignore: path: + serialiseArgs { + ignore = + builtins.filter (x: x != null) (builtins.map (getIgnoredSingle path) paths_to_ignore); + }; + + getIgnoredSingle = path: pathToIgnore: let + clean_path_to_ignore = expandHomePath pathToIgnore; + commonPath = builtins.substring 0 (builtins.stringLength path) clean_path_to_ignore; + in + if commonPath == path + then let + preFinalPath = + builtins.substring (builtins.stringLength commonPath) + (builtins.stringLength clean_path_to_ignore) + clean_path_to_ignore; + finalPath = + if lib.strings.hasPrefix "/" preFinalPath + then lib.strings.removePrefix "/" preFinalPath + else preFinalPath; + in "BelowPath ${finalPath}" + else null; + + serialiseArg = key: val: + if builtins.typeOf val == "string" + then esa "-${key}=${lib.strings.escape ["="] val}" + else if builtins.typeOf val == "list" + then lib.strings.concatStringsSep " " (builtins.map (serialiseArg key) val) + else builtins.throw "Unsupported type: ${builtins.typeOf val}"; + + serialiseArgs = args: + lib.strings.concatStringsSep " " ( + lib.attrsets.mapAttrsToList + serialiseArg + args + ); + + mkScriptLine = pathname: let + path = + expandHomePath pathname; + in + lib.strings.concatStringsSep " " [ + "unison" + "${serialiseArgs cfg.unisonOptions}" + "$EXTRA_OPTIONS" + "${getIgnored cfg.pathsToIgnore path}" + "${esa path}" + (esa "ssh://${cfg.foreign.userName}@${cfg.foreign.address}/${path}") + ]; + + script = lib.strings.concatStringsSep "\n" (builtins.map mkScriptLine cfg.pathsToSync); +in + sysLib.writeShellScript { + name = "unison-sync"; + src = builtins.toFile "unison-backup" ('' + #!/usr/bin/env dash + + # shellcheck source=/dev/null + SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH + + export UNISON=${esa cfg.dataDir}; + + if [ "$1" = "links" ]; then + shift 1; + EXTRA_OPTIONS="-links=true"; + fi + EXTRA_OPTIONS="$EXTRA_OPTIONS $*" + '' + + script); + + dependencies = with pkgs; [ + unison + openssh # needed to connect to the other server + less # needed to show diffs + diffutils # needed to compute diffs + ]; + } -- cgit 1.4.1