at master 12 kB view raw
1{ pkgs, ... }: 2let 3 inherit (import ./ssh-keys.nix pkgs) 4 snakeOilEd25519PrivateKey 5 snakeOilEd25519PublicKey 6 ; 7 8 remoteRepository = "/root/restic-backup"; 9 remoteFromFileRepository = "/root/restic-backup-from-file"; 10 remoteFromCommandRepository = "/root/restic-backup-from-command"; 11 remoteInhibitTestRepository = "/root/restic-backup-inhibit-test"; 12 remoteNoInitRepository = "/root/restic-backup-no-init"; 13 rcloneRepository = "rclone:local:/root/restic-rclone-backup"; 14 sftpRepository = "sftp:alice@sftp:backups/test"; 15 16 backupPrepareCommand = '' 17 touch /root/backupPrepareCommand 18 test ! -e /root/backupCleanupCommand 19 ''; 20 21 backupCleanupCommand = '' 22 rm /root/backupPrepareCommand 23 touch /root/backupCleanupCommand 24 ''; 25 26 testDir = pkgs.stdenvNoCC.mkDerivation { 27 name = "test-files-to-backup"; 28 unpackPhase = "true"; 29 installPhase = '' 30 mkdir $out 31 echo some_file > $out/some_file 32 echo some_other_file > $out/some_other_file 33 mkdir $out/a_dir 34 echo a_file > $out/a_dir/a_file 35 echo a_file_2 > $out/a_dir/a_file_2 36 ''; 37 }; 38 39 passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}"; 40 paths = [ "/opt" ]; 41 exclude = [ "/opt/excluded_file_*" ]; 42 pruneOpts = [ 43 "--keep-daily 2" 44 "--keep-weekly 1" 45 "--keep-monthly 1" 46 "--keep-yearly 99" 47 ]; 48 commandString = "testing"; 49 command = [ 50 "echo" 51 "-n" 52 commandString 53 ]; 54in 55{ 56 name = "restic"; 57 58 meta = with pkgs.lib.maintainers; { 59 maintainers = [ 60 bbigras 61 i077 62 ]; 63 }; 64 65 nodes = { 66 sftp = 67 # Copied from openssh.nix 68 { pkgs, ... }: 69 { 70 services.openssh = { 71 enable = true; 72 extraConfig = '' 73 Match Group sftponly 74 ChrootDirectory /srv/sftp 75 ForceCommand internal-sftp 76 ''; 77 }; 78 79 users.groups = { 80 sftponly = { }; 81 }; 82 users.users = { 83 alice = { 84 isNormalUser = true; 85 createHome = false; 86 group = "sftponly"; 87 shell = "/run/current-system/sw/bin/nologin"; 88 openssh.authorizedKeys.keys = [ snakeOilEd25519PublicKey ]; 89 }; 90 }; 91 }; 92 93 restic = 94 { pkgs, ... }: 95 { 96 services.restic.backups = { 97 remotebackup = { 98 inherit 99 passwordFile 100 paths 101 exclude 102 pruneOpts 103 backupPrepareCommand 104 backupCleanupCommand 105 ; 106 repository = remoteRepository; 107 initialize = true; 108 timerConfig = null; # has no effect here, just checking that it doesn't break the service 109 }; 110 remote-sftp = { 111 inherit 112 passwordFile 113 paths 114 exclude 115 pruneOpts 116 ; 117 repository = sftpRepository; 118 initialize = true; 119 timerConfig = null; # has no effect here, just checking that it doesn't break the service 120 extraOptions = [ 121 "sftp.command='ssh alice@sftp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -s sftp'" 122 ]; 123 }; 124 remote-from-file-backup = { 125 inherit passwordFile exclude pruneOpts; 126 initialize = true; 127 repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository; 128 paths = [ 129 "/opt/a_dir/a_file" 130 "/opt/a_dir/a_file_2" 131 ]; 132 dynamicFilesFrom = '' 133 find /opt -mindepth 1 -maxdepth 1 ! -name a_dir # all files in /opt except for a_dir 134 ''; 135 }; 136 remote-from-command-backup = { 137 inherit 138 passwordFile 139 pruneOpts 140 command 141 ; 142 initialize = true; 143 repository = remoteFromCommandRepository; 144 }; 145 inhibit-test = { 146 inherit 147 passwordFile 148 paths 149 exclude 150 pruneOpts 151 ; 152 repository = remoteInhibitTestRepository; 153 initialize = true; 154 inhibitsSleep = true; 155 }; 156 remote-noinit-backup = { 157 inherit 158 passwordFile 159 exclude 160 pruneOpts 161 paths 162 ; 163 initialize = false; 164 repository = remoteNoInitRepository; 165 }; 166 rclonebackup = { 167 inherit 168 passwordFile 169 paths 170 exclude 171 pruneOpts 172 ; 173 initialize = true; 174 repository = rcloneRepository; 175 rcloneConfig = { 176 type = "local"; 177 one_file_system = true; 178 }; 179 180 # This gets overridden by rcloneConfig.type 181 rcloneConfigFile = pkgs.writeText "rclone.conf" '' 182 [local] 183 type=ftp 184 ''; 185 }; 186 remoteprune = { 187 inherit passwordFile; 188 repository = remoteRepository; 189 pruneOpts = [ "--keep-last 1" ]; 190 }; 191 custompackage = { 192 inherit passwordFile paths; 193 repository = "some-fake-repository"; 194 package = pkgs.writeShellScriptBin "restic" '' 195 echo "$@" >> /root/fake-restic.log; 196 ''; 197 198 pruneOpts = [ "--keep-last 1" ]; 199 checkOpts = [ "--some-check-option" ]; 200 }; 201 }; 202 203 environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local"; 204 }; 205 }; 206 207 testScript = '' 208 restic.start() 209 sftp.start() 210 restic.wait_for_unit("dbus.socket") 211 sftp.wait_for_unit("sshd.service") 212 213 restic.systemctl("start network-online.target") 214 restic.wait_for_unit("network-online.target") 215 216 sftp.succeed( 217 "mkdir -p /srv/sftp/backups", 218 "chown alice:sftponly /srv/sftp/backups", 219 "chmod 0755 /srv/sftp/backups", 220 ) 221 222 restic.succeed( 223 "mkdir -p /root/.ssh/", 224 "cat ${snakeOilEd25519PrivateKey} > /root/.ssh/id_ed25519", 225 "chmod 0600 /root/.ssh/id_ed25519", 226 ) 227 228 restic.fail( 229 "restic-remotebackup snapshots", 230 "restic-remote-sftp snapshots", 231 'restic-remote-from-file-backup snapshots"', 232 "restic-rclonebackup snapshots", 233 "grep 'backup.* /opt' /root/fake-restic.log", 234 ) 235 restic.succeed( 236 # set up 237 "cp -rT ${testDir} /opt", 238 "touch /opt/excluded_file_1 /opt/excluded_file_2", 239 "mkdir -p /root/restic-rclone-backup", 240 ) 241 242 restic.fail( 243 # test that noinit backup in fact does not initialize the repository 244 # and thus fails without a pre-initialized repository 245 "systemctl start restic-backups-remote-noinit-backup.service", 246 ) 247 248 restic.succeed( 249 # test that remotebackup runs custom commands and produces a snapshot 250 "timedatectl set-time '2016-12-13 13:45'", 251 "systemctl start restic-backups-remotebackup.service", 252 "rm /root/backupCleanupCommand", 253 'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 254 ) 255 256 restic.succeed( 257 # test that remotebackup runs custom commands and produces a snapshot 258 "timedatectl set-time '2016-12-13 13:45'", 259 "systemctl start restic-backups-remotebackup.service", 260 "rm /root/backupCleanupCommand", 261 'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 262 263 # test that restoring that snapshot produces the same directory 264 "mkdir /tmp/restore-1", 265 "restic-remotebackup restore latest -t /tmp/restore-1", 266 "diff -ru ${testDir} /tmp/restore-1/opt", 267 268 # test that remote-from-file-backup produces a snapshot 269 "systemctl start restic-backups-remote-from-file-backup.service", 270 'restic-remote-from-file-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 271 "mkdir /tmp/restore-2", 272 "restic-remote-from-file-backup restore latest -t /tmp/restore-2", 273 "diff -ru ${testDir} /tmp/restore-2/opt", 274 275 # test that remote-noinit-backup produces a snapshot once initialized 276 "restic-remote-noinit-backup init", 277 "systemctl start restic-backups-remote-noinit-backup.service", 278 'restic-remote-noinit-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 279 280 # test that restoring that snapshot produces the same directory 281 "mkdir /tmp/restore-3", 282 "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-3", 283 "diff -ru ${testDir} /tmp/restore-3/opt", 284 285 # test that remote-from-command-backup produces a snapshot, with the expected contents 286 "systemctl start restic-backups-remote-from-command-backup.service", 287 'restic-remote-from-command-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 288 '[[ $(restic-remote-from-command-backup dump --path /stdin latest stdin) == ${commandString} ]]', 289 290 # test that rclonebackup produces a snapshot 291 "systemctl start restic-backups-rclonebackup.service", 292 'restic-rclonebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 293 294 # test that custompackage runs both `restic backup` and `restic check` with reasonable commandlines 295 "systemctl start restic-backups-custompackage.service", 296 "grep 'backup' /root/fake-restic.log", 297 "grep 'check.* --some-check-option' /root/fake-restic.log", 298 299 # test that we can create four snapshots in remotebackup and rclonebackup 300 "timedatectl set-time '2017-12-13 13:45'", 301 "systemctl start restic-backups-remotebackup.service", 302 "rm /root/backupCleanupCommand", 303 "systemctl start restic-backups-rclonebackup.service", 304 305 "timedatectl set-time '2018-12-13 13:45'", 306 "systemctl start restic-backups-remotebackup.service", 307 "rm /root/backupCleanupCommand", 308 "systemctl start restic-backups-rclonebackup.service", 309 310 "timedatectl set-time '2018-12-14 13:45'", 311 "systemctl start restic-backups-remotebackup.service", 312 "rm /root/backupCleanupCommand", 313 "systemctl start restic-backups-rclonebackup.service", 314 315 "timedatectl set-time '2018-12-15 13:45'", 316 "systemctl start restic-backups-remotebackup.service", 317 "rm /root/backupCleanupCommand", 318 "systemctl start restic-backups-rclonebackup.service", 319 320 "timedatectl set-time '2018-12-16 13:45'", 321 "systemctl start restic-backups-remotebackup.service", 322 "rm /root/backupCleanupCommand", 323 "systemctl start restic-backups-rclonebackup.service", 324 325 'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"', 326 'restic-rclonebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"', 327 328 # test that SFTP backup works by copying from the remotebackup 329 'restic-remote-sftp init --from-repo ${remoteRepository} --from-password-file ${passwordFile} --copy-chunker-params', 330 'restic-remote-sftp copy --from-repo ${remoteRepository} --from-password-file ${passwordFile}', 331 'restic-remote-sftp snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"', 332 333 # test that remoteprune brings us back to 1 snapshot in remotebackup 334 "systemctl start restic-backups-remoteprune.service", 335 'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 336 337 # test that remoteprune brings us back to 1 snapshot in remotebackup 338 "systemctl start restic-backups-remoteprune.service", 339 'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', 340 ) 341 342 # test that the inhibit option is working 343 restic.systemctl("start --no-block restic-backups-inhibit-test.service") 344 restic.wait_until_succeeds( 345 "systemd-inhibit --no-legend --no-pager | grep -q restic", 346 5 347 ) 348 # test that the inhibit option is working 349 restic.systemctl("start --no-block restic-backups-inhibit-test.service") 350 restic.wait_until_succeeds( 351 "systemd-inhibit --no-legend --no-pager | grep -q restic", 352 5 353 ) 354 ''; 355}