Files
config/profiles/backup/client/default.nix

148 lines
5.7 KiB
Nix

{ 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}";
};
}
))
];
*/
};
}