- 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)
109 lines
2.7 KiB
Nix
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;
|
|
# };
|
|
# };
|
|
};
|
|
}
|