{ config, lib, pkgs, ... }: /* this module enables a backup script made with borg! to use import & set the options below to declare a backup add the following code to a module and it will backup all listed paths in a borg archive to the specified repo | <3yy> | V V system.backups.baks = { ${service} = { paths = [ cfg.data_dir ]; }; }; */ let cfg = config.system.backups; sec = config.sops.secrets; borg = "${pkgs.borgbackup}/bin/borg"; in { options.system.backups = { enable = lib.mkEnableOption "enables backups with borg"; baks = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf (lib.types.listOf lib.types.path)); default = {}; description = "backup jobs, nested attribute sets should be = paths []"; }; gameserver_baks = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf (lib.types.listOf lib.types.path)); default = {}; description = "backup jobs for game servers, nested attribute sets should be = paths []"; }; repo = lib.mkOption { type = lib.types.path; default = "/holocron/archives/devices/snowbelle"; description = "borg repository path"; }; gameserver_repo = lib.mkOption { type = lib.types.path; default = "/holocron/archives/gameservers/borg"; description = "borg repository path"; }; passwd_file = lib.mkOption { type = lib.types.path; default = sec."borg_passwd".path; description = "borg repository passphrase file"; }; mode = lib.mkOption { type = lib.types.str; default = "split"; # "all" description = "choice between creating one archive of all paths or one archive per service"; }; }; config = lib.mkIf (cfg.enable && cfg.baks != {}) { # create and or set perms for repo dirs systemd.tmpfiles.rules = [ "d ${cfg.repo} 2770 root archives - -" "d ${cfg.gameserver_repo} 2770 root archives - -" ]; # create servie to backup services systemd.services.backups = { description = "backup services with borg!"; path = [pkgs.borgbackup]; serviceConfig = { Type = "oneshot"; User = "root"; Group = "archives"; # make perms shake out UMask = "0007"; # make perms shake out # the actual script borg is using ExecStart = pkgs.writeShellScript "borg-backup" '' backup() { set -euo pipefail export BORG_PASSPHRASE="$(cat ${cfg.passwd_file})" export BORG_REPO="${cfg.repo}" timestamp="$(date +'%Y-%m-%d_%H:%M:%S')" mode=split # init repo in needed if ! borg info "$BORG_REPO" >/dev/null 2>&1; then echo "Initializing Borg repo at $BORG_REPO" borg init --encryption=repokey "$BORG_REPO" fi borg break-lock "$BORG_REPO" || true echo "starting backup at $timestamp" if [ "$mode" = "split" ]; then # loop for each backup ${lib.concatStringsSep "\n\n" (lib.mapAttrsToList ( bak_name: bak_paths: '' echo "------------ Backing up ${bak_name} ------------" archive="$timestamp-${bak_name}" echo "backing up: ${lib.concatStringsSep " " bak_paths.paths} → $archive" borg create \ --verbose \ --filter AME \ --list \ --stats \ --show-rc \ --compression lz4 \ "$BORG_REPO::$archive" \ ${lib.concatStringsSep " " bak_paths.paths} echo "pruning old backups for ${bak_name}..." borg prune -v --list "$BORG_REPO" \ --glob-archives "*-${bak_name}" \ --keep-daily=7 \ --keep-weekly=52 \ --keep-monthly=-1 echo "backup run complete at \"$BORG_REPO::$archive\"" '' ) cfg.baks)} exit 0 else # flatten all paths from cfg.baks into one big list all_paths="${ lib.concatStringsSep " " (lib.flatten (lib.mapAttrsToList (_: bak: bak.paths) cfg.baks)) }" borg create \ --verbose \ --filter AME \ --list \ --stats \ --show-rc \ --compression lzma,9 \ "$BORG_REPO::$timestamp-${toString config.networking.hostName}" \ $all_paths echo "pruning old backups for ${toString config.networking.hostName}..." borg prune -v --list "$BORG_REPO" \ --glob-archives "*-${toString config.networking.hostName}" \ --keep-daily=7 \ --keep-weekly=52 \ --keep-monthly=-1 echo "backup run complete at \"$BORG_REPO::${toString config.networking.hostName}\"" exit 0 fi } start_time=$(date +%s) backup end_time=$(date +%s) exec_time=$((end_time - start_time)) cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}') echo "" echo "backup stats:" echo "exec time: $exec_time" echo "cpu usage: $cpu_usage" ''; }; }; # create timer to run backups daily systemd.timers.backups = { description = "daily borg backup timer"; wantedBy = ["timers.target"]; timerConfig = { OnCalendar = "04:00"; Persistent = true; }; }; # create servie to backup gameservers (back these up hourly) systemd.services.gameserver_backups = { description = "backup services with borg!"; path = [pkgs.borgbackup]; serviceConfig = { Type = "oneshot"; User = "root"; Group = "archives"; # make perms shake out UMask = "0007"; # make perms shake out # the actual script borg is using ExecStart = pkgs.writeShellScript "borg-gameserver_backup" '' backup() { set -euo pipefail export BORG_PASSPHRASE="$(cat ${cfg.passwd_file})" export BORG_REPO="${cfg.gameserver_repo}" timestamp="$(date +'%Y-%m-%d_%H:%M:%S')" # init repo in needed if ! borg info "$BORG_REPO" >/dev/null 2>&1; then echo "Initializing Borg repo at $BORG_REPO" borg init --encryption=repokey "$BORG_REPO" fi borg break-lock "$BORG_REPO" || true echo "starting backup at $timestamp" # loop for each backup ${lib.concatStringsSep "\n\n" (lib.mapAttrsToList ( bak_name: bak_paths: '' echo "------------ Backing up ${bak_name} ------------" archive="$timestamp-${bak_name}" echo "backing up: ${lib.concatStringsSep " " bak_paths.paths} → $archive" borg create \ --verbose \ --filter AME \ --list \ --stats \ --show-rc \ --compression lz4 \ "$BORG_REPO::$archive" \ ${lib.concatStringsSep " " bak_paths.paths} echo "pruning old backups for ${bak_name}..." borg prune -v --list "$BORG_REPO" \ --glob-archives "*-${bak_name}" \ --keep-hourly=24 \ --keep-daily=7 \ --keep-weekly=12 \ --keep-monthly=12 echo "backup run complete at \"$BORG_REPO::$archive\"" '' ) cfg.gameserver_baks)} exit 0 } start_time=$(date +%s) backup end_time=$(date +%s) exec_time=$((end_time - start_time)) cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}') echo "" echo "backup stats:" echo "exec time: $exec_time" echo "cpu usage: $cpu_usage" ''; }; }; # create timer to run backups daily systemd.timers.gameserver_backups = { description = "daily borg backup timer"; wantedBy = ["timers.target"]; timerConfig = { OnCalendar = "*-*-* *:00:00"; # every hour, at :01 (one min after db dump) Persistent = true; }; }; # db backups services.mysqlBackup = lib.mkIf config.services.mysql.enable { # mc servers use this enable = true; location = "/var/backup/mysql"; user = "root"; calendar = "*-*-* *:59:00"; compressionAlg = "zstd"; databases = config.services.mysql.ensureDatabases; # set to all databases defined in esure databases }; services.postgresqlBackup = lib.mkIf config.services.postgresql.enable { # immich uses this enable = true; location = "/var/backup/postgresql"; compression = "zstd"; # optional: "xz", "zstd", "none" startAt = "03:58"; databases = ["immich"]; # set to all databases defined in esure databases #databases = config.services.postgresql.ensureDatabases; # set to all databases defined in esure databases }; # install borg binary environment.systemPackages = with pkgs; [borgbackup tree]; # declare secret for repo password sops.secrets = { "borg_passwd" = { owner = "root"; group = "root"; }; }; }; }