{ config, lib, ... }: let notInVM = lib.mkIf (!config.virtualisation.isVmVariant); inherit (config.networking) hostName; sopsFile = lib.mkDefault ../../hosts/nixos/${hostName}/secrets.yaml; in # 3-2-1 Backup Rule: # * have 3 separate copies # * on 2 different media # * with 1 kept offsite # # borgbackup vs restic vs kopia vs diplicity # https://mangohost.net/blog/duplicacy-vs-restic-vs-borg-which-backup-tool-is-right-in-2025/ # https://onidel.com/restic-vs-borgbackup-vs-kopia-2025/ # https://www.reddit.com/r/BorgBackup/comments/v3bwfg/why_should_i_switch_from_restic_to_borg/ # https://www.aarsen.me/posts/2022-02-15-sweet-unattended-backups.html # # restic: # * native s3, go, decent restore, good deduplication, simplicity, decent backup/restore speed, slow prunes on huge repos # borgbackup: # * clunky s3 (rclone), python/c, 1 machine per repo, backup goes brr, multiple encryption modes # kopia: # * native s3, go, fast restore, restore goes brr, flexible key management, no nixos module # duplicity: # * global deduplication, but global job, paid gui # zrepl: # * requires zfs on both ends # TODO: can i make restic backup from a zfs snapshot for atomicity? # https://www.aarsen.me/posts/2022-02-15-sweet-unattended-backups.html#pulling-it-together # https://gist.github.com/stackcoder/ccb3b17812ed11700ee83d762b970b98 { options = { pbsds.backup.paths = lib.mkOption { type = with lib.types; listOf str; default = [ ]; }; }; imports = lib.map notInVM [ # https://search.nixos.org/options?channel=unstable&query=backup ./mysqlBackup.nix ./postgresqlBackup.nix # ./vaultwarden.nix # services.vaultwarden.backupDir # ./gitea.nix # services.gitea.dump.enable # ./forgejo.nix # services.forgejo.dump.enable # stuff in /var/lib on nox: # ./transmission.nix # ./plex.nix # ./thelounge.nix ]; config = notInVM { # https://restic.readthedocs.io/en/latest/070_encryption.html#manage-repository-keys sops.secrets = { # $ sops --set '["restic_systems_password_meconium"] "'$(pwgen --ambiguous --secure 64 1)'"' hosts/nixos/$(nix eval .#nixosConfigurations --apply builtins.attrNames --json | jq .[] -r | xargs gum choose)/secrets.yaml restic_systems_password_meconium.sopsFile = sopsFile; # $ sops --set '["restic_systems_password_panorama"] "'$(pwgen --ambiguous --secure 64 1)'"' hosts/nixos/$(nix eval .#nixosConfigurations --apply builtins.attrNames --json | jq .[] -r | xargs gum choose)/secrets.yaml restic_systems_password_panorama.sopsFile = sopsFile; # # https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables restic_systems_password_s3.sopsFile = sopsFile; restic_systems_environment_s3.sopsFile = sopsFile; }; services.restic.backups = let shared = { initialize = true; # createWrapper = true; # adds a "restic-${name}" wrapper in system path # TODO: --skip-if-unchanged ? paths = [ "/var/lib" ] ++ config.pbsds.backup.paths; # TODO: How should the timer behave on a laptop? timerConfig.OnCalendar = "hourly"; pruneOpts = [ "--keep-daily 5" "--keep-weekly 3" "--keep-monthly 2" ]; }; in { "systems-meconium" = shared // { # repository = "sftp:USER_TODO@noximilien:/mnt/meconium/Backups/restic/system-${hostName}"; repository = "sftp:USER_TODO@noximilien:/mnt/meconium/Backups/restic/systems"; passwordFile = config.sops.secrets.restic_systems_password_meconium.path; # environmentFile = config.sops.secrets.restic_systems_environment_meconium.path; }; "systems-panorama" = shared // { # repository = "sftp:USER_TODO@eple:/mnt/panorama/Backups/restic/system-${hostName}"; repository = "sftp:USER_TODO@eple:/mnt/panorama/Backups/restic/systems"; passwordFile = config.sops.secrets.restic_systems_password_panorama.path; # environmentFile = config.sops.secrets.restic_systems_environment_panorama.path; }; # "systems-b2" = shared // { # repository = "s3:1246890.r2.cloudflarestorage.com/restic-systems"; # passwordFile = config.sops.secrets.restic_systems_password_s3.path; # environmentFile = config.sops.secrets.restic_systems_environment_s3.path; # }; }; # backup of user homes /* TODO = lib.pip config.users.users [ lib.attrNames (lib.filter (user: config.users.users.${user}.enable)) (lib.filter (user: config.users.users.${user}.isNormalUser)) # (lib.filter (user: config.users.users.${user}.createHome)) (lib.map (user: { # sops.secrets.restic_user_${user}_password_meconium.owner = user; # sops.secrets.restic_user_${user}_password_meconium.sopsFile = sopsFile; services.restic.backups."user-${user}" = { inherit (config.services.restic.backups."system") initialize pruneOpts timerConfig ; # createWrapper = true; # adds a "restic-${name}" wrapper in system path inherit user; # the user can see this password, hence we must use per-user restic repositories passwordFile = config.sops.secrets.restic_user_${user}_password_meconium.path; # environmentFile = config.sops.secrets.restic_user_${user}_environment_meconium.path; paths = [ config.users.users.${user}.home ]; repository = "sftp:noximilien/mnt/meconium/Backups/restic/user-${user}"; }; } )) ]; */ }; }