at master 10 kB view raw
1{ pkgs, ... }: 2 3let 4 passphrase = "supersecret"; 5 dataDir = "/ran:dom/data"; 6 subDir = "not_anything_here"; 7 excludedSubDirFile = "not_this_file_either"; 8 excludeFile = "not_this_file"; 9 keepFile = "important_file"; 10 keepFileData = "important_data"; 11 localRepo = "/root/back:up"; 12 # a repository on a file system which is not mounted automatically 13 localRepoMount = "/noAutoMount"; 14 archiveName = "my_archive"; 15 remoteRepo = "borg@server:."; # No need to specify path 16 privateKey = pkgs.writeText "id_ed25519" '' 17 -----BEGIN OPENSSH PRIVATE KEY----- 18 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 19 QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe 20 RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw 21 AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg 22 9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ= 23 -----END OPENSSH PRIVATE KEY----- 24 ''; 25 publicKey = '' 26 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client 27 ''; 28 privateKeyAppendOnly = pkgs.writeText "id_ed25519" '' 29 -----BEGIN OPENSSH PRIVATE KEY----- 30 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 31 QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8 32 cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw 33 AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8 34 IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ= 35 -----END OPENSSH PRIVATE KEY----- 36 ''; 37 publicKeyAppendOnly = '' 38 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client 39 ''; 40 41in 42{ 43 name = "borgbackup"; 44 meta = with pkgs.lib; { 45 maintainers = with maintainers; [ dotlambda ]; 46 }; 47 48 nodes = { 49 client = 50 { ... }: 51 { 52 virtualisation.fileSystems.${localRepoMount} = { 53 device = "tmpfs"; 54 fsType = "tmpfs"; 55 options = [ "noauto" ]; 56 }; 57 58 services.borgbackup.jobs = { 59 60 local = { 61 paths = dataDir; 62 repo = localRepo; 63 preHook = '' 64 # Don't append a timestamp 65 archiveName="${archiveName}" 66 ''; 67 encryption = { 68 mode = "repokey"; 69 inherit passphrase; 70 }; 71 compression = "auto,zlib,9"; 72 prune.keep = { 73 within = "1y"; 74 yearly = 5; 75 }; 76 exclude = [ "*/${excludeFile}" ]; 77 extraCreateArgs = [ 78 "--exclude-caches" 79 "--exclude-if-present" 80 ".dont backup" 81 ]; 82 83 wrapper = "borg-main"; 84 postHook = "echo post"; 85 startAt = [ ]; # Do not run automatically 86 }; 87 88 localMount = { 89 paths = dataDir; 90 repo = localRepoMount; 91 encryption.mode = "none"; 92 wrapper = null; 93 startAt = [ ]; 94 }; 95 96 remote = { 97 paths = dataDir; 98 repo = remoteRepo; 99 encryption.mode = "none"; 100 startAt = [ ]; 101 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519"; 102 }; 103 104 remoteAppendOnly = { 105 paths = dataDir; 106 repo = remoteRepo; 107 encryption.mode = "none"; 108 startAt = [ ]; 109 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly"; 110 }; 111 112 commandSuccess = { 113 dumpCommand = pkgs.writeScript "commandSuccess" '' 114 echo -n test 115 ''; 116 repo = remoteRepo; 117 encryption.mode = "none"; 118 startAt = [ ]; 119 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519"; 120 }; 121 122 commandFail = { 123 dumpCommand = "${pkgs.coreutils}/bin/false"; 124 repo = remoteRepo; 125 encryption.mode = "none"; 126 startAt = [ ]; 127 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519"; 128 }; 129 130 sleepInhibited = { 131 inhibitsSleep = true; 132 # Blocks indefinitely while "backing up" so that we can try to suspend the local system while it's hung 133 dumpCommand = pkgs.writeScript "sleepInhibited" '' 134 cat /dev/zero 135 ''; 136 repo = remoteRepo; 137 encryption.mode = "none"; 138 startAt = [ ]; 139 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519"; 140 }; 141 142 }; 143 }; 144 145 server = 146 { ... }: 147 { 148 services.openssh = { 149 enable = true; 150 settings = { 151 PasswordAuthentication = false; 152 KbdInteractiveAuthentication = false; 153 }; 154 }; 155 156 services.borgbackup.repos.repo1 = { 157 authorizedKeys = [ publicKey ]; 158 path = "/data/borgbackup"; 159 }; 160 161 # Second repo to make sure the authorizedKeys options are merged correctly 162 services.borgbackup.repos.repo2 = { 163 authorizedKeysAppendOnly = [ publicKeyAppendOnly ]; 164 path = "/data/borgbackup"; 165 quota = ".5G"; 166 }; 167 }; 168 }; 169 170 testScript = '' 171 start_all() 172 173 client.fail('test -d "${remoteRepo}"') 174 175 client.succeed( 176 "cp ${privateKey} /root/id_ed25519" 177 ) 178 client.succeed("chmod 0600 /root/id_ed25519") 179 client.succeed( 180 "cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly" 181 ) 182 client.succeed("chmod 0600 /root/id_ed25519.appendOnly") 183 184 client.succeed("mkdir -p ${dataDir}/${subDir}") 185 client.succeed("touch ${dataDir}/${excludeFile}") 186 client.succeed("touch '${dataDir}/${subDir}/.dont backup'") 187 client.succeed("touch ${dataDir}/${subDir}/${excludedSubDirFile}") 188 client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}") 189 190 with subtest("local"): 191 borg = "BORG_PASSPHRASE='${passphrase}' borg" 192 client.systemctl("start --wait borgbackup-job-local") 193 client.fail("systemctl is-failed borgbackup-job-local") 194 # Make sure exactly one archive has been created 195 assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0 196 # Make sure excludeFile has been excluded 197 client.fail( 198 "{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg) 199 ) 200 # Make sure excludedSubDirFile has been excluded 201 client.fail( 202 "{} list '${localRepo}::${archiveName}' | grep -qF '${subDir}/${excludedSubDirFile}".format(borg) 203 ) 204 # Make sure keepFile has the correct content 205 client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg)) 206 assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}") 207 # Make sure the same is true when using `borg mount` 208 client.succeed( 209 "mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format( 210 borg 211 ) 212 ) 213 assert "${keepFileData}" in client.succeed( 214 "cat /mnt/borg/${dataDir}/${keepFile}" 215 ) 216 217 # Make sure custom wrapper name works 218 client.succeed("command -v borg-main") 219 220 with subtest("localMount"): 221 # the file system for the repo should not be already mounted 222 client.fail("mount | grep ${localRepoMount}") 223 # ensure trying to write to the mountpoint before the fs is mounted fails 224 client.succeed("chattr +i ${localRepoMount}") 225 borg = "borg" 226 client.systemctl("start --wait borgbackup-job-localMount") 227 client.fail("systemctl is-failed borgbackup-job-localMount") 228 # Make sure exactly one archive has been created 229 assert int(client.succeed("{} list '${localRepoMount}' | wc -l".format(borg))) > 0 230 231 # Make sure disabling wrapper works 232 client.fail("command -v borg-job-localMount") 233 234 with subtest("remote"): 235 borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg" 236 server.wait_for_unit("sshd.service") 237 client.wait_for_unit("network.target") 238 client.systemctl("start --wait borgbackup-job-remote") 239 client.fail("systemctl is-failed borgbackup-job-remote") 240 241 # Make sure we can't access repos other than the specified one 242 client.fail("{} list borg\@server:wrong".format(borg)) 243 244 # Make sure default wrapper works 245 client.succeed("command -v borg-job-remote") 246 247 # TODO: Make sure that data is actually deleted 248 249 with subtest("remoteAppendOnly"): 250 borg = ( 251 "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg" 252 ) 253 server.wait_for_unit("sshd.service") 254 client.wait_for_unit("network.target") 255 client.systemctl("start --wait borgbackup-job-remoteAppendOnly") 256 client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly") 257 258 # Make sure we can't access repos other than the specified one 259 client.fail("{} list borg\@server:wrong".format(borg)) 260 261 # TODO: Make sure that data is not actually deleted 262 263 with subtest("commandSuccess"): 264 server.wait_for_unit("sshd.service") 265 client.wait_for_unit("network.target") 266 client.systemctl("start --wait borgbackup-job-commandSuccess") 267 client.fail("systemctl is-failed borgbackup-job-commandSuccess") 268 id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip() 269 client.succeed(f"borg-job-commandSuccess extract ::{id} stdin") 270 assert "test" == client.succeed("cat stdin") 271 272 with subtest("commandFail"): 273 server.wait_for_unit("sshd.service") 274 client.wait_for_unit("network.target") 275 client.systemctl("start --wait borgbackup-job-commandFail") 276 client.succeed("systemctl is-failed borgbackup-job-commandFail") 277 278 with subtest("sleepInhibited"): 279 server.wait_for_unit("sshd.service") 280 client.wait_for_unit("network.target") 281 client.fail("systemd-inhibit --list | grep -q borgbackup") 282 client.systemctl("start borgbackup-job-sleepInhibited") 283 client.wait_until_succeeds("systemd-inhibit --list | grep -q borgbackup") 284 client.systemctl("stop borgbackup-job-sleepInhibited") 285 ''; 286}