diff options
| author | Martin Fischer <martin@push-f.com> | 2025-12-13 15:08:25 +0100 |
|---|---|---|
| committer | Martin Fischer <martin@push-f.com> | 2025-12-14 22:48:04 +0100 |
| commit | de4843c5002f3f58f8bc69dd57d368324b67f4ce (patch) | |
| tree | 9acb9d5e92315413cbbf3abf62b3da3bed04a1b8 /nixos/shared | |
| parent | 8476372e535d6a9bedd39237da6b29d04c374256 (diff) | |
feat: automatic backups
Diffstat (limited to 'nixos/shared')
| -rw-r--r-- | nixos/shared/restic-database-backups.nix | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/nixos/shared/restic-database-backups.nix b/nixos/shared/restic-database-backups.nix new file mode 100644 index 0000000..f1f6f36 --- /dev/null +++ b/nixos/shared/restic-database-backups.nix @@ -0,0 +1,129 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.restic-database-backups; +in +{ + options.restic-database-backups = { + repository = mkOption { + type = types.str; + description = "Restic repository URL."; + }; + + passwordFile = mkOption { + type = types.path; + description = "Path to restic password file."; + }; + + timerConfig = mkOption { + type = types.attrs; + description = "systemd timer configuration."; + }; + + pruneOpts = mkOption { + type = types.listOf types.str; + default = []; + description = "Prune options passed to restic."; + }; + + postgresDatabases = mkOption { + type = types.listOf types.str; + default = []; + description = "List of PostgreSQL database names to back up."; + }; + + sqliteDatabases = mkOption { + type = types.attrsOf types.path; + default = {}; + description = "SQLite databases to back up (name -> path)."; + }; + }; + + config = + { + services.restic.backups = + let + backup = { + repository = cfg.repository; + passwordFile = cfg.passwordFile; + # I want the backups to run one after the other so I'm disabling + # the timer creation here and then later set up "after". + timerConfig = null; + extraBackupArgs = ["--skip-if-unchanged"]; + progressFps = 0.0166; # once per minute + }; + in + # TODO: pg_dumpall --globals-only + (listToAttrs ( + map (db: { + name = "postgres-${db}"; + value = backup // + { + backupPrepareCommand = '' + mkdir -p /var/backup + chmod 700 /var/backup + ${pkgs.sudo}/bin/sudo ${pkgs.su}/bin/su postgres -c '${pkgs.postgresql}/bin/pg_dump ${db}' > /var/backup/${db}.sql + ''; + paths = ["/var/backup/${db}.sql"]; + backupCleanupCommand = '' + rm -f /var/backup/${db}.sql + ''; + }; + }) cfg.postgresDatabases + )) + // (mapAttrs' (name: path: { + name = "sqlite-${name}"; + value = backup // { + backupPrepareCommand = '' + mkdir -p /var/backup + chmod 700 /var/backup + ${pkgs.sqlite}/bin/sqlite3 ${path} ".backup /var/backup/${name}.sqlite" + ''; + paths = ["/var/backup/${name}.sqlite"]; + backupCleanupCommand = '' + rm -f /var/backup/${name}.sqlite + ''; + }; + }) cfg.sqliteDatabases) + // { + database-prune = backup // { + pruneOpts = cfg.pruneOpts; + }; + }; + + systemd.services = + let + serviceNames = + (map (db: "restic-backups-postgres-${db}") cfg.postgresDatabases) + ++ (map (name: "restic-backups-sqlite-${name}") (attrNames cfg.sqliteDatabases)) + ++ ["restic-backups-database-prune"]; + fullServiceNames = map (name: "${name}.service") serviceNames; + services = listToAttrs ( + imap0 (idx: name: { + name = name; + value = mkIf (idx > 0) { + after = [(elemAt fullServiceNames (idx - 1))]; + }; + }) serviceNames + ); + in + services + // { + database-backups = { + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.coreutils}/bin/true"; + }; + wants = fullServiceNames; + after = fullServiceNames; + }; + }; + + systemd.timers.database-backups = { + wantedBy = ["timers.target"]; + timerConfig = cfg.timerConfig; + }; + }; +} |
