ops-jrz1/modules/backup.nix
Dan 8826d62bcc Add maubot integration and infrastructure updates
- maubot.nix: Declarative bot framework with plugin deployment
- backup.nix: Local backup service for Matrix/bridge data
- sna-instagram-bot: Instagram content bridge plugin
- beads: Issue tracking workflow integrated
- spec 004: Browser-based dev environment design
- nixpkgs bump: Oct 22 → Dec 2
- Fix maubot health check (401 = healthy)
2025-12-08 15:55:12 -08:00

109 lines
2.7 KiB
Nix

# 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;
# };
# };
};
}