{ config, lib, pkgs, ... }: with lib; let cfg = config.services.lnbits-backup; in { options.services.lnbits-backup = { enable = mkEnableOption "LNbits Borg Backup Service"; dataPath = mkOption { type = types.path; default = "/var/lib/lnbits/data"; description = "Path to LNbits data directory"; }; repository = mkOption { type = types.str; example = "ssh://borg@192.168.1.100//mnt/my_ssd/borg-backups/lnbits"; description = "Borg repository location (SSH URL or user@host:/path format)"; }; schedule = mkOption { type = types.str; default = "hourly"; description = "Backup schedule (hourly, daily, or systemd timer format)"; }; compression = mkOption { type = types.str; default = "auto,lz4"; description = "Compression: auto,lz4 (Pi), auto,zstd (RockPro64). 'auto' skips already-compressed files."; }; passphraseFile = mkOption { type = types.path; default = "/root/secrets/borg-passphrase"; description = "Path to Borg passphrase file"; }; sshKeyFile = mkOption { type = types.path; default = "/root/.ssh/borg_backup_key"; description = "SSH private key for repository access"; }; retention = { hourly = mkOption { type = types.int; default = 24; }; daily = mkOption { type = types.int; default = 7; }; weekly = mkOption { type = types.int; default = 4; }; monthly = mkOption { type = types.int; default = 6; }; }; alertEmail = mkOption { type = types.nullOr types.str; default = null; description = "Email for alerts (optional)"; }; }; config = mkIf cfg.enable { services.borgbackup.jobs.lnbits = { paths = [ cfg.dataPath "/tmp/lnbits-snapshot.sqlite3" ]; exclude = [ "*.log" "*.pyc" "__pycache__" "*.tmp" ]; repo = cfg.repository; encryption = { mode = "repokey-blake2"; passCommand = "cat ${cfg.passphraseFile}"; }; compression = cfg.compression; startAt = cfg.schedule; persistentTimer = true; # Run missed backups after reboots prune.keep = { inherit (cfg.retention) hourly daily weekly monthly; }; preHook = '' echo "=== LNbits Backup Starting: $(date) ===" if [ -f "${cfg.dataPath}/database.sqlite3" ]; then echo "Creating SQLite snapshot..." ${pkgs.sqlite}/bin/sqlite3 "${cfg.dataPath}/database.sqlite3" \ ".backup '/tmp/lnbits-snapshot.sqlite3'" SIZE=$(stat -c%s "/tmp/lnbits-snapshot.sqlite3") echo "Snapshot created: $SIZE bytes" if [ "$SIZE" -lt 1000 ]; then echo "ERROR: Snapshot too small!" rm -f /tmp/lnbits-snapshot.sqlite3 exit 1 fi else echo "ERROR: Database not found!" exit 1 fi ''; postHook = '' rm -f /tmp/lnbits-snapshot.sqlite3 # Weekly integrity check (Sundays) if [ $(date +%u) -eq 7 ]; then echo "Running weekly integrity check..." ${pkgs.borgbackup}/bin/borg check --repository-only ${cfg.repository} fi echo "=== Backup Complete: $(date) ===" ''; environment = { BORG_RSH = "ssh -i ${cfg.sshKeyFile} -o StrictHostKeyChecking=accept-new"; }; }; # Install required packages and helper commands environment.systemPackages = with pkgs; [ borgbackup sqlite (writeShellScriptBin "lnbits-borg-list" '' export BORG_PASSPHRASE=$(cat ${cfg.passphraseFile}) export BORG_RSH="ssh -i ${cfg.sshKeyFile}" ${borgbackup}/bin/borg list ${cfg.repository} "$@" '') (writeShellScriptBin "lnbits-borg-info" '' export BORG_PASSPHRASE=$(cat ${cfg.passphraseFile}) export BORG_RSH="ssh -i ${cfg.sshKeyFile}" ${borgbackup}/bin/borg info ${cfg.repository} "$@" '') (writeShellScriptBin "lnbits-borg-restore" '' if [ $# -lt 1 ]; then echo "Usage: lnbits-borg-restore [destination]" echo "" echo "Available archives:" lnbits-borg-list exit 1 fi ARCHIVE="$1" DEST="''${2:-/tmp/lnbits-restore}" export BORG_PASSPHRASE=$(cat ${cfg.passphraseFile}) export BORG_RSH="ssh -i ${cfg.sshKeyFile}" mkdir -p "$DEST" cd "$DEST" echo "Restoring to: $DEST" ${borgbackup}/bin/borg extract ${cfg.repository}::$ARCHIVE echo "Done! Restored files in: $DEST" '') (writeShellScriptBin "lnbits-borg-mount" '' MOUNT="''${1:-/mnt/borg-browse}" export BORG_PASSPHRASE=$(cat ${cfg.passphraseFile}) export BORG_RSH="ssh -i ${cfg.sshKeyFile}" mkdir -p "$MOUNT" ${borgbackup}/bin/borg mount ${cfg.repository} "$MOUNT" echo "Mounted at: $MOUNT" echo "Unmount: borg umount $MOUNT" '') ]; }; }