{ config, lib, pkgs, ... }: with lib; let cfg = config.local.environ; prelude = pkgs.writeText "vtmp-prelude" '' set -o errexit set -o pipefail if [ $# -ne 3 ]; then echo "usage: $0 " >&2 exit 1 fi local="$2" remote="$1" boot_id="$(echo "$3" | head -c8)" ssh="${lib.getExe pkgs.openssh} -o BatchMode=yes" rsync="${lib.getExe pkgs.rsync}" cd "$HOME/vtmp" mkdir -p "$remote" declare -a rsync_opts rsync_opts+=("-glprtxz") rsync_opts+=("--open-noatime") rsync_opts+=("--preallocate") rsync_opts+=("--max-size=1G") rsync_opts+=("--rsh=$ssh") rsync_opts+=("--log-file=.rsync-$remote.log") rsync_opts+=("--filter=- /$remote/") rsync_opts+=("--filter=- /$remote") ''; vtmp-restore = pkgs.writeShellScript "vtmp-restore" '' source ${prelude} [ ! -L .restore ] || exit 0 parent="$($ssh -- "$remote" " \ set -o errexit; \ set -o pipefail; \ \ mkdir -p -- \"vtmp/$local/$boot_id\"; \ ln -fsT -- ../.. \"vtmp/$local/$boot_id/$remote\"; \ \ last=\"\$(readlink -qe -- \"vtmp/$local/last\")\" || { echo \"$boot_id\"; exit 0; }; \ if [ \"\$last\" != \"$boot_id\" -a ! -L \"vtmp/$local/$boot_id/.last\" ]; then \ ln -fsT -- \"$remote/$local/\$last\" \"vtmp/$local/$boot_id/.last\"; \ fi; \ \ parent=\"\$(realpath -qe --relative-to=\"vtmp/$local\" -- \"vtmp/$local/last/.save\")\" || \ { echo \"$boot_id\"; exit 0; }; \ \ if [ \"vtmp/$local/$boot_id/.last\" -nt \"vtmp/$local/\$parent/.last\" ]; then \ mv -T --exchange --no-copy \"vtmp/$local\"/{\"\$parent\",\"$boot_id\"}/.last; \ fi; \ \ echo \"\$parent\"; \ ")" if [ -z "$parent" ]; then echo "$0: error: empty parent" >&2 exit 1 fi # Pull restored state from $remote # Note that $parent can equal $boot_id if ~/vtmp got cleared after saving with no reboot rsync_opts+=("--filter=- /.save") rsync_opts+=("--filter=- /.restore") $rsync "''${rsync_opts[@]}" -- "$remote:vtmp/$local/$parent/" ./ $ssh -- "$remote" " \ set -o errexit; \ set -o pipefail; \ \ touch -cr \"vtmp/$local/$parent\" \"vtmp/$local/$boot_id\"; \ if [ \"$parent\" != \"$boot_id\" ]; then \ ln -fsT -- \"$remote/$local/$parent\" \"vtmp/$local/$boot_id/.save\"; \ fi; \ \ ln -fsT -- \"$boot_id\" \"vtmp/$local/last\"; \ \ if [ \"$parent\" != \"$boot_id\" ]; then \ rm -f \"vtmp/$local/$parent/.save\"; \ \ for link in .restore; do \ if [ -L \"vtmp/$local/$parent/\$link\" ]; then \ mv -T \"vtmp/$local\"/{\"$parent\",\"$boot_id\"}/\"\$link\"; \ fi; \ done; \ \ touch -c \"vtmp/$local/$parent\"; \ mv -T --exchange --no-copy \"vtmp/$local\"/{\"$parent\",\"$boot_id\"}; \ fi; \ " [ "$parent" != "$boot_id" ] && target="$remote/$local/$parent" || target=. ln -fsT -- "$target" .restore ''; vtmp-sync = pkgs.writeShellScript "vtmp-sync" '' source ${prelude} # Push to $remote from $local rsync_opts+=("--delete") $rsync "''${rsync_opts[@]}" -- ./ "$remote:vtmp/$local/$boot_id/" # Pull from $remote to $local rsync_opts+=("--filter=- /$local/$boot_id/") $rsync "''${rsync_opts[@]}" -- "$remote:vtmp/" "./$remote/" ln -fsT ../.. "./$remote/$local/$boot_id" ''; in { options.local.environ = { vtmpSyncHost = mkOption { type = with lib.types; nullOr str; default = null; }; }; config = mkMerge [ (mkIf cfg.enable { systemd.user.tmpfiles.rules = [ "d %t/vtmp 0700" ]; home.file = { "vtmp".source = config.lib.file.mkOutOfStoreSymlink "/run/user/${toString config.local.uid}/vtmp"; }; gtk.gtk3.bookmarks = [ "file://${config.home.homeDirectory}/vtmp" "file://${config.home.homeDirectory}/tmp" ]; }) (mkIf (cfg.enable && cfg.vtmpSyncHost != null) { programs.ssh = { extraOptionOverrides.PermitLocalCommand = "yes"; matchBlocks.${cfg.vtmpSyncHost}.extraOptions.LocalCommand = "systemctl --user import-environment SSH_AUTH_SOCK; " + "systemctl --user start vtmp-sync.timer"; }; systemd.user = { targets.vtmp-sync-failure = { Unit = { Conflicts = ["vtmp-sync.timer"]; }; }; services = { vtmp-restore = { Unit = { ConditionPathIsSymbolicLink = ["!%t/vtmp/.restore"]; }; Service = { Type = "oneshot"; ExecStart = "${vtmp-restore} ${cfg.vtmpSyncHost} %l %b"; Environment = ["PATH=${lib.makeBinPath [pkgs.coreutils]}"]; }; }; vtmp-sync = { Unit = { After = ["vtmp-restore.service"]; Requires = ["vtmp-restore.service"]; OnFailure = ["vtmp-sync-failure.target"]; }; Service = { Type = "oneshot"; ExecStart = "${vtmp-sync} ${cfg.vtmpSyncHost} %l %b"; Environment = ["PATH=${lib.makeBinPath [pkgs.coreutils]}"]; }; }; }; timers.vtmp-sync = { Timer = { OnActiveSec = "15s"; OnUnitInactiveSec = "1h"; }; }; }; }) ]; }