main
1{den, ...}: {
2 den.quirks.backup.description = ''
3 Restic backup entries collected from aspects (host)
4
5 Each aspect contributes a name -> services.restic.backups options map:
6 backup = {
7 foo = {
8 paths = ["/var/lib/foo"];
9 backupPrepareCommand = "…";
10 };
11 };
12
13 Names must be unique across aspects.
14 '';
15
16 den.aspects.core.includes = [den.aspects.core.backups];
17 den.aspects.core.backups = {
18 nixos = {
19 host,
20 backup,
21 lib,
22 config,
23 ...
24 }: let
25 mergeBackups = backups:
26 lib.foldl' (
27 acc: entry: let
28 duplicates = lib.intersectAttrs entry acc;
29 in
30 if duplicates != {}
31 then
32 throw ("core.backups: duplicate backup names collected from aspects:"
33 + "${lib.concatStringsSep ", " (lib.attrNames duplicates)}")
34 else acc // entry
35 ) {}
36 backups;
37
38 allBackups = mergeBackups backup;
39 in
40 lib.mkIf (allBackups != {}) {
41 services.restic.backups = lib.mapAttrs (name: value:
42 {
43 user = "root";
44 repository = "s3:https://584bf08b6f2fa0a8918cae5af0ee45bc.r2.cloudflarestorage.com/backups/${host.name}/${name}";
45 initialize = true;
46 environmentFile = config.vaultix.templates.restic-backup-env.path;
47 passwordFile = config.vaultix.secrets.restic-backup-password.path;
48 timerConfig = {
49 OnCalendar = "04:00";
50 RandomizedDelaySec = "1h";
51 Persistent = true;
52 };
53 pruneOpts = [
54 "--keep-daily 7"
55 "--keep-weekly 4"
56 "--keep-monthly 3"
57 ];
58 }
59 // value)
60 allBackups;
61
62 vaultix.secrets = {
63 restic-backup-r2-id.file = ./r2-id.age;
64 restic-backup-r2-key.file = ./r2-key.age;
65 restic-backup-password.file = ./password.age;
66 };
67
68 vaultix.templates.restic-backup-env.content = ''
69 AWS_ACCESS_KEY_ID=${config.vaultix.placeholder.restic-backup-r2-id}
70 AWS_SECRET_ACCESS_KEY=${config.vaultix.placeholder.restic-backup-r2-key}
71 '';
72 };
73 };
74}