aboutsummaryrefslogtreecommitdiff
path: root/nixos/shared
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2025-12-13 15:08:25 +0100
committerMartin Fischer <martin@push-f.com>2025-12-14 22:48:04 +0100
commitde4843c5002f3f58f8bc69dd57d368324b67f4ce (patch)
tree9acb9d5e92315413cbbf3abf62b3da3bed04a1b8 /nixos/shared
parent8476372e535d6a9bedd39237da6b29d04c374256 (diff)
feat: automatic backups
Diffstat (limited to 'nixos/shared')
-rw-r--r--nixos/shared/restic-database-backups.nix129
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;
+ };
+ };
+}