commit 5acf4d0f5a3dc36c37ac49c5544baea1a614f3fd Author: padreug Date: Thu Jan 15 12:59:42 2026 +0100 Initial commit: LNbits Borg backup NixOS module diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2be92b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +result diff --git a/borg-lnbits.nix b/borg-lnbits.nix new file mode 100644 index 0000000..4598ed6 --- /dev/null +++ b/borg-lnbits.nix @@ -0,0 +1,176 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.borg-lnbits; + +in { + options.services.borg-lnbits = { + 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" + '') + ]; + }; +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..608d417 --- /dev/null +++ b/flake.nix @@ -0,0 +1,10 @@ +{ + description = "LNbits Borg backup NixOS module"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: { + nixosModules.borg-lnbits = import ./borg-lnbits.nix; + nixosModules.default = self.nixosModules.borg-lnbits; + }; +}