{ config, pkgs, ... }: let resticPrunePre = pkgs.writeScript "resticPrunePre" '' set -ex # Recover from an unclean shutdown if ${pkgs.zfs}/bin/zfs list tank/backup-old; then ${pkgs.zfs}/bin/zfs list tank/backup || ${pkgs.zfs}/bin/zfs rename tank/backup-old tank/backup fi # Undo a prune that has been aborted ${pkgs.zfs}/bin/zfs destroy tank/backup-prune || true ${pkgs.zfs}/bin/zfs destroy tank/backup@prune || true ${pkgs.zfs}/bin/zfs destroy tank/backup-old || true # Wait for the restic repository to be unlocked while [ -n "$(${pkgs.restic}/bin/restic list locks)" ]; then sleep fi # Clone the Dataset ${pkgs.zfs}/bin/zfs snapshot tank/backup@prune ${pkgs.zfs}/bin/zfs clone tank/backup@prune tank/backup-prune chown backup:backup /backup-prune ''; resticPrune = pkgs.writeScript "resticPrune" '' export RESTIC_REPOSITORY="$RESTIC_REPOSITORY-prune" ${pkgs.restic} prune --no-cache --max-unused 0 ''; resticPrunePost = pkgs.writeScript "resticPrunePost" '' set -ex # make the original read-only ${pkgs.zfs}/bin/zfs set readonly=on tank/backup # Copy new data over ${pkgs.restic}/bin/restic copy --no-cache --no-lock # Promote the pruned dataset ${pkgs.zfs}/bin/zfs promote tank/backup-prune # Change the dataset names ${pkgs.zfs}/bin/zfs rename tank/backup tank/backup-old ${pkgs.zfs}/bin/zfs rename tank/backup-prune tank/backup # Destroy the old dataset ${pkgs.zfs}/bin/zfs destroy -r tank/backup-old ''; in { users.users.backup = { description = "Backup user"; home = "/backup"; isSystemUser = true; openssh.authorizedKeys.keys = [ ]; group = "backup"; }; users.groups.backup = { }; systemd.services.restic-prune = { enable = true; description = "Cleaning up restic backups"; serviceConfig = { ExecStartPre = "!${resticPrunePre}"; ExecStart = "${resticPrune}"; ExecStartPost = "!${resticPrunePost}"; User = "backup"; Group = "backup"; Type = "oneshot"; EnvironmentFile = config.sops.secrets."services/restic/env".owner; }; }; sops.secrets."services/restic/env".owner = "backup"; }