aboutsummaryrefslogtreecommitdiffstats
path: root/modules/by-name/un
diff options
context:
space:
mode:
Diffstat (limited to 'modules/by-name/un')
-rw-r--r--modules/by-name/un/unison/module.nix92
-rw-r--r--modules/by-name/un/unison/shellScript.nix95
2 files changed, 187 insertions, 0 deletions
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
+ ];
+ }