# Local backup service for PostgreSQL and Maubot # Phase 1: Manual trigger via `systemctl start backup` # Phase 2: Enable timer for daily automation { config, pkgs, lib, ... }: with lib; let cfg = config.services.backup; in { options.services.backup = { enable = mkEnableOption "local backup service"; location = mkOption { type = types.str; default = "/var/backup"; description = "Backup storage directory"; }; retention = mkOption { type = types.int; default = 4; description = "Days to retain backups"; }; }; config = mkIf cfg.enable { # Ensure backup directory exists systemd.tmpfiles.rules = [ "d ${cfg.location} 0750 root root -" ]; # Backup service (oneshot, manual trigger) systemd.services.backup = { description = "Local backup service"; after = [ "postgresql.service" ]; requires = [ "postgresql.service" ]; serviceConfig = { Type = "oneshot"; User = "root"; # Low priority - don't impact running services IOSchedulingClass = "idle"; Nice = 19; }; path = [ config.services.postgresql.package # pg_dumpall pkgs.gzip pkgs.sqlite pkgs.util-linux # runuser pkgs.coreutils pkgs.findutils ]; script = '' set -euo pipefail DATE=$(date +%Y-%m-%d) BASE="${cfg.location}" TMP="$BASE/.incomplete-$DATE" DEST="$BASE/$DATE" # Skip if today's backup exists if [ -d "$DEST" ]; then echo "Backup already exists: $DEST" exit 0 fi # Clean up any previous incomplete attempts rm -rf "$BASE"/.incomplete-* mkdir -p "$TMP" # PostgreSQL (hot, consistent via MVCC) echo "Backing up PostgreSQL..." runuser -u postgres -- pg_dumpall | gzip > "$TMP/postgres.sql.gz" gzip -t "$TMP/postgres.sql.gz" # Maubot SQLite (consistent via .backup API) if [ -f /var/lib/maubot/bot.db ]; then echo "Backing up Maubot..." sqlite3 /var/lib/maubot/bot.db ".backup '$TMP/maubot.db'" else echo "Maubot DB not found, skipping" fi # Atomic publish mv "$TMP" "$DEST" # Prune old backups (keep ${toString cfg.retention} days) find "$BASE" -mindepth 1 -maxdepth 1 -type d -mtime +${toString cfg.retention} -exec rm -rf {} + echo "Backup complete: $DEST" ls -lh "$DEST" ''; }; # Timer (disabled by default, enable for Phase 2) # systemd.timers.backup = { # wantedBy = [ "timers.target" ]; # timerConfig = { # OnCalendar = "daily"; # Persistent = true; # }; # }; }; }