{ lib, config, nixosConfig, sysLib, pkgs, ... }: let unisonPath = "${config.xdg.dataHome}/unison"; # These are only used for the script unisonOptions = { sshcmd = "ssh"; ui = "text"; auto = "true"; # This is useless, with hm links links = "false"; backupdir = "${unisonPath}/backups"; backuploc = "central"; backupcurr = paths_to_merge; # merge = # builtins.map (x: ''${x} -> diff3 --text --merge CURRENT1 CURRENTARCH CURRENT2 > NEW'') # paths_to_merge; }; paths_to_merge = mkPathName { file_names = ["log" "history" "harpoon.json" "file_frecency.bin" "main.shada"]; extensions = ["log"]; }; paths_to_keep = [ "~/.local/state/mpv" "~/.local/state/nvim" "~/.local/share" "~/.local/.Trash-1000" "~/.mozilla/.Trash-1000" "~/.mozilla/firefox" "~/media" "~/school" "~/repos" ]; paths_to_ignore = [ # already synchronized by the taskserver "~/.local/share/task" # Should not be synchronized "~/.local/share/unison" # Is just to big to be synchronized (# TODO: Work around that <2024-08-31> ) "~/media/music" ]; hostName = let hn = nixosConfig.networking.hostName; in if hn == "tiamat" then "apzu" else if hn == "apzu" then "tiamat" else builtins.throw "Host (${hn}) not yet covered in the unison host mapping."; mkPathName = { file_names, extensions, }: builtins.map (x: ''Name ${x}'') ( (builtins.map (x: ''*.${x}'') extensions) ++ file_names ); unitName = name: builtins.replaceStrings ["/"] ["-"] name; mkPath = path: if lib.strings.hasPrefix "~" path then "${builtins.elemAt (builtins.attrNames config.home.persistence) 0}${lib.strings.removePrefix "~" path}" else builtins.throw "Every pathname needs to start with a '~'"; mkPair = pathname: let path = mkPath pathname; in { name = unitName "${pathname}"; value = { stateDirectory = unisonPath; roots = [ "${path}" "ssh://${config.home.username}@${hostName}.fritz.box/${path}" ]; }; }; getIgnoredSingle = path: path_to_ignore: let clean_path_to_ignore = mkPath path_to_ignore; 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; getIgnored = paths_to_ignore: path: serialiseArgs { ignore = builtins.filter (x: x != null) (builtins.map (getIgnoredSingle path) paths_to_ignore); }; serialiseArg = key: val: if builtins.typeOf val == "string" then lib.strings.escapeShellArg "-${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 ); esa = a: lib.strings.escapeShellArg a; mkScriptLine = pathname: let path = mkPath pathname; in lib.strings.concatStringsSep " " [ "unison" "${serialiseArgs unisonOptions}" "$EXTRA_OPTIONS" "${getIgnored paths_to_ignore path}" "${esa path}" (esa "ssh://${config.home.username}@${hostName}.fritz.box/${path}") ]; script = lib.strings.concatStringsSep "\n" (builtins.map mkScriptLine paths_to_keep); pairs = builtins.listToAttrs (builtins.map mkPair paths_to_keep); in { home.sessionVariables = { UNISON = unisonPath; }; home.packages = [ pkgs.unison (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 unisonPath}; 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 ]; }) ]; services.unison = { enable = false; inherit pairs; }; }