This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / modules / backup.nix
4.8 kB 147 lines
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.lass.backup; 9 states = lib.attrValues cfg.state; 10 key = config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh.id25519".path; 11 12 # Runs every state's hook in its own script, keeps going when one fails 13 # and fails at the end if any did. 14 runHooks = 15 hook: 16 pkgs.writeShellScript "backup-${hook}" '' 17 failed=() 18 ${lib.concatMapStrings (state: '' 19 echo "backup: ${hook} ${state.name}" 20 ${pkgs.writeShellScript "backup-${hook}-${state.name}" '' 21 set -xeo pipefail 22 ${state.${hook}} 23 ''} || failed+=(${lib.escapeShellArg state.name}) 24 '') (lib.filter (state: state.${hook} != null) states)} 25 if [ ''${#failed[@]} -gt 0 ]; then 26 echo "backup: ${hook} failed for: ''${failed[*]}" >&2 27 exit 1 28 fi 29 ''; 30in 31{ 32 options.lass.backup = { 33 repo = lib.mkOption { 34 type = lib.types.nullOr lib.types.str; 35 default = null; 36 example = "user@backup.example.com:/./machine"; 37 description = '' 38 Borg repository the machine backs up to. Without it no backup job is 39 generated, so modules can declare their state everywhere. 40 ''; 41 }; 42 43 sshPort = lib.mkOption { 44 type = lib.types.port; 45 default = 22; 46 }; 47 48 knownHosts = lib.mkOption { 49 type = lib.types.lines; 50 description = "known_hosts lines pinning the host key of the repo's server."; 51 }; 52 53 state = lib.mkOption { 54 default = { }; 55 description = '' 56 Backup needs of a service, declared next to it. Dumps go to 57 /var/backup, which is always backed up. 58 ''; 59 type = lib.types.attrsOf ( 60 lib.types.submodule ( 61 { name, ... }: 62 { 63 options = { 64 name = lib.mkOption { 65 type = lib.types.str; 66 default = name; 67 }; 68 folders = lib.mkOption { 69 type = lib.types.listOf lib.types.str; 70 default = [ ]; 71 }; 72 exclude = lib.mkOption { 73 type = lib.types.listOf lib.types.str; 74 default = [ ]; 75 description = "borg exclude patterns."; 76 }; 77 preBackup = lib.mkOption { 78 type = lib.types.nullOr lib.types.lines; 79 default = null; 80 description = '' 81 Runs before the backup inside the read-only borg unit (as 82 root), e.g. to dump a database to /var/backup. If it fails, 83 no backup is made and the job fails. 84 ''; 85 }; 86 postBackup = lib.mkOption { 87 type = lib.types.nullOr lib.types.lines; 88 default = null; 89 description = '' 90 Runs as root after the backup, also when it failed, e.g. to 91 start a service stopped by preBackup. 92 ''; 93 }; 94 readWritePaths = lib.mkOption { 95 type = lib.types.listOf lib.types.str; 96 default = [ ]; 97 description = "Paths preBackup needs to write to."; 98 }; 99 }; 100 } 101 ) 102 ); 103 }; 104 }; 105 106 config = lib.mkIf (cfg.repo != null) { 107 clan.core.vars.generators.borgbackup = { 108 files."borgbackup.ssh.id25519" = { }; 109 files."borgbackup.ssh.id25519.pub".secret = false; 110 runtimeInputs = with pkgs; [ 111 coreutils 112 openssh 113 ]; 114 script = '' 115 ssh-keygen -t ed25519 -N "" -f $out/borgbackup.ssh.id25519 116 ''; 117 }; 118 119 systemd.tmpfiles.rules = [ "d /var/backup 0700 root root -" ]; 120 121 # Archives are named and pruned by <hostname>-<job>; another job name 122 # would leave the existing archives unpruned. 123 services.borgbackup.jobs.hetzner = { 124 inherit (cfg) repo; 125 paths = lib.unique ([ "/var/backup" ] ++ lib.concatMap (state: state.folders) states); 126 exclude = lib.unique ([ "*.pyc" ] ++ lib.concatMap (state: state.exclude) states); 127 readWritePaths = lib.unique ( 128 [ "/var/backup" ] ++ lib.concatMap (state: state.readWritePaths) states 129 ); 130 preHook = "${runHooks "preBackup"}"; 131 encryption.mode = "none"; 132 compression = "auto,zstd"; 133 startAt = "*-*-* 03:00:00"; 134 environment.BORG_RSH = "ssh -oPort=${toString cfg.sshPort} -oUserKnownHostsFile=${pkgs.writeText "backup-known-hosts" cfg.knownHosts} -i ${key}"; 135 prune.keep = { 136 within = "1d"; 137 daily = 7; 138 weekly = 4; 139 monthly = 0; 140 }; 141 }; 142 143 systemd.services.borgbackup-job-hetzner.serviceConfig.ExecStopPost = lib.mkIf (lib.any ( 144 state: state.postBackup != null 145 ) states) [ "+${runHooks "postBackup"}" ]; 146 }; 147}