feat: add backup module with Restic + Backblaze B2

- Encrypted backups to B2
- Configurable retention (daily/weekly/monthly)
- SOPS-managed credentials
- Automatic pruning

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ashisgreat22 2026-03-18 14:11:11 +01:00
parent f82b822d16
commit fd056367d2
3 changed files with 146 additions and 0 deletions

134
modules/backup.nix Normal file
View file

@ -0,0 +1,134 @@
# Backup Module (Restic + Backblaze B2)
# Provides: Automated encrypted backups to B2
#
# Usage:
# myModules.backup = {
# enable = true;
# repository = "b2:your-bucket-name";
# paths = [ "/var/lib/vaultwarden" "/var/backup/vaultwarden" ];
# };
#
# Secrets needed in secrets.yaml:
# b2_account_id: "your-b2-key-id"
# b2_account_key: "your-b2-key"
# restic_password: "your-encryption-password"
{
config,
lib,
...
}:
let
cfg = config.myModules.backup;
in
{
options.myModules.backup = {
enable = lib.mkEnableOption "Automated backups with restic to Backblaze B2";
repository = lib.mkOption {
type = lib.types.str;
example = "b2:my-backup-bucket";
description = "B2 bucket name (format: b2:bucket-name)";
};
paths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "/var/lib/vaultwarden" "/home" ];
description = "Paths to backup";
};
exclude = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "*.tmp" "*/cache/*" ];
description = "Patterns to exclude from backup";
};
schedule = lib.mkOption {
type = lib.types.str;
default = "daily";
example = "*-*-* 02:00:00";
description = "Systemd timer schedule for backups";
};
pruneSchedule = lib.mkOption {
type = lib.types.str;
default = "weekly";
description = "Schedule for pruning old backups";
};
retainDays = lib.mkOption {
type = lib.types.int;
default = 30;
description = "Keep daily backups for this many days";
};
retainWeeks = lib.mkOption {
type = lib.types.int;
default = 12;
description = "Keep weekly backups for this many weeks";
};
retainMonths = lib.mkOption {
type = lib.types.int;
default = 12;
description = "Keep monthly backups for this many months";
};
};
config = lib.mkIf cfg.enable {
sops.secrets.b2_account_id = { };
sops.secrets.b2_account_key = { };
sops.secrets.restic_password = { };
services.restic.backups.b2 = {
inherit (cfg) paths;
repository = cfg.repository;
# B2 credentials from SOPS
environmentFile = config.sops.templates."backup.env".path;
# Encryption password
passwordFile = config.sops.secrets.restic_password.path;
# Exclude patterns
exclude = cfg.exclude;
# Backup schedule
timerConfig = {
OnCalendar = cfg.schedule;
RandomizedDelaySec = "1h";
Persistent = true;
};
# Pruning
pruneOpts = [
"--keep-daily ${toString cfg.retainDays}"
"--keep-weekly ${toString cfg.retainWeeks}"
"--keep-monthly ${toString cfg.retainMonths}"
];
};
# Prune schedule
systemd.timers.restic-backups-b2-prune = {
description = "Prune old B2 backups";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.pruneSchedule;
RandomizedDelaySec = "1h";
Persistent = true;
Unit = "restic-backups-b2.service";
};
};
# SOPS template for B2 credentials
sops.templates."backup.env" = {
content = ''
B2_ACCOUNT_ID=${config.sops.placeholder.b2_account_id}
B2_ACCOUNT_KEY=${config.sops.placeholder.b2_account_key}
'';
};
};
}