This repository has no description
0

Configure Feed

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

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