Commit ccd0705

HPCesia <me@hpcesia.com>
2026-09-23 07:40:28
backup: init
1 parent e54db8b
.secrets/cache/cyrene/90368eb6941a8944e0fc24966be3ddb20d484915de5a62ab2a94f00d5a169045
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 7JvPpg vtJRmGRwgyG9QjPgkPBWspvPGe+mymm8Rdq8W64qCDg
+XXg9k8KwZqO3r3gSAuuMCvDELuXwWTAk13O8TKP3FfA
+-> z?/g3/}-grease eKsO)d]
+eZa83HBk3yiQ1rvunAHVzw0SrVVo1G26z3zUoELIjTmy6n74c8lhpx5RbFA4uFqn
+CA
+--- P1mwhVKWk94xM3SfIE/NctqmIsv6AMJ8//tbs/XOLpY
+�
+�
�v�_�-%l��;�8=ņ�g]���l��������!��%�����gN�_K�N3a����
\ No newline at end of file
.secrets/cache/cyrene/e0e0e5e5363924900f12a6c553c8b891f51b8f9be9f0f4a384a4a3fa687c8bad
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 7JvPpg be38WCi3U6iK0CKufOr7LA+VPOaf1T7IbEswOFv4Owg
+bxC3a0Mfkbg44uDT5ihf6cK9Mgfhkk1AclOqbHtDN9c
+-> sG_f]_b-grease h,IlbYe# "E ALJ.',B
+HZzcdenfgVknnsnLbDnjEfAs/4BAU8INEe+BmxC6vDiF9aTzTA
+--- 4fSfOZ9rNAr4dSuUfqJEf2ilLdFewqQNALVKEgKFXzw
+L�n������L�z�6���0e5���dE����X�����O�z��K^,��1ĠbȮ���v���J�W �f�	Z%���&�/���C�ȪeG����
\ No newline at end of file
.secrets/cache/cyrene/fe55ab39098c279df452ce8ba0de2943572a59e97f577bf48976821ba60a8cf9
Binary file
.secrets/cache/mobius/2acb558122acbeff93466f6f6c4d55e8ee77d5a5f30856e38a803e4690bd02ea
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 qzANJQ 2uWq50dxyjZtUC/XyOGX3d9DzSy7G1M+MuI8aLitOE0
+YhsyeNfk34kUL/ADnVS651KZMLlXEFoAnlETFOJ+HIE
+-> _~Mg$oL-grease
+WPHRQHpqqoYqq+7sKA1luLH5T1QTs0hL15ClC16gVhixrCsdSx3YUc9A9m0FCzjV
+KBYEUOJJiijms+q1otvMtKSFExJjcJ4
+--- j58vfyrC2ig5spGn2bkdFnvRjA0k5xs/FwtQxF/pKdA
+�H�햠eG��[Gdّ��s�A�E������v�9�q����>�ܾ�C�_��<qk�?9�ƈ
\ No newline at end of file
.secrets/cache/mobius/70f424563d1267ee11ee7df7c226d5ffc0d055a0bb41234da301c79a3ca62307
Binary file
.secrets/cache/mobius/c109f6ea9904d52dc5e9260e099b761b6c4e088bba4f6a0b4057b26d5a13eadd
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 qzANJQ 7u7ES7FTd0L9y+oGw5cArYrKYQVi6YkW0dy/tUzAXlc
+uq7IxydM2INTnFnr09521WiTVdmMXEbAhOesOBuLaGE
+-> Y[5n-grease V=r9]#, ?E5 IB
+NQjiZCxrD9qqaA7L8WU35uZShu0
+--- BVrANARdteLiJOj1YjG6oo/7iMRnZgxZPAnnUB/7SnQ
+��N׬1�L��wr�������z�`u/Y)���t*_�35�E9���;�r~�;܍����b�_11
�i�����[����m9��kS���1��Q�
\ No newline at end of file
modules/core/backups/default.nix
@@ -0,0 +1,74 @@
+{den, ...}: {
+  den.quirks.backup.description = ''
+    Restic backup entries collected from aspects (host)
+
+    Each aspect contributes a name -> services.restic.backups options map:
+      backup = {
+        foo = {
+          paths = ["/var/lib/foo"];
+          backupPrepareCommand = "…";
+        };
+      };
+
+    Names must be unique across aspects.
+  '';
+
+  den.aspects.core.includes = [den.aspects.core.backups];
+  den.aspects.core.backups = {
+    nixos = {
+      host,
+      backup,
+      lib,
+      config,
+      ...
+    }: let
+      mergeBackups = backups:
+        lib.foldl' (
+          acc: entry: let
+            duplicates = lib.intersectAttrs entry acc;
+          in
+            if duplicates != {}
+            then
+              throw ("core.backups: duplicate backup names collected from aspects:"
+                + "${lib.concatStringsSep ", " (lib.attrNames duplicates)}")
+            else acc // entry
+        ) {}
+        backups;
+
+      allBackups = mergeBackups backup;
+    in
+      lib.mkIf (allBackups != {}) {
+        services.restic.backups = lib.mapAttrs (name: value:
+          {
+            user = "root";
+            repository = "s3:https://584bf08b6f2fa0a8918cae5af0ee45bc.r2.cloudflarestorage.com/backups/${host.name}/${name}";
+            initialize = true;
+            environmentFile = config.vaultix.templates.restic-backup-env.path;
+            passwordFile = config.vaultix.secrets.restic-backup-password.path;
+            timerConfig = {
+              OnCalendar = "04:00";
+              RandomizedDelaySec = "1h";
+              Persistent = true;
+            };
+            pruneOpts = [
+              "--keep-daily 7"
+              "--keep-weekly 4"
+              "--keep-monthly 3"
+            ];
+          }
+          // value)
+        allBackups;
+
+        vaultix.secrets = {
+          restic-backup-r2-id.file = ./r2-id.age;
+          restic-backup-r2-key.file = ./r2-key.age;
+          restic-backup-password.file = ./password.age;
+        };
+
+        vaultix.templates.restic-backup-env.content = ''
+          AWS_ACCESS_KEY_ID=${config.vaultix.placeholder.restic-backup-r2-id}
+          AWS_SECRET_ACCESS_KEY=${config.vaultix.placeholder.restic-backup-r2-key}
+        '';
+      };
+  };
+}
modules/core/backups/password.age
Binary file
modules/core/backups/r2-id.age
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> piv-p256 xCEwtQ A7PYfRdi+SPgO7EMEeMJ154LYhWZKDZ+0eB9iR+YjiM0
+xaT2Op2qG8ibtNmIzJ29US42qI8yQi2V0JaVOZlq0XE
+-> "P{^>-grease `= .c
+kkkA1fbaScW0SNW7yNdLzCmreuypkEHxG/pYDmWxbZ0K6ZZgABAWnuG/U+PcbSFF
+whbXUgnOvtHjrcc
+--- O6yuX0BAVkg2z0l6PS6OXOevgOqwPxFpu9lBxiLOrG8
+�iH���In`ߋ�E{*�]A��Pvf�=:����H���A$=C<3����2W!U�"��(��
\ No newline at end of file
modules/core/backups/r2-key.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> piv-p256 xCEwtQ AhHLKGZshKfDALRyrRV04TE+yQSZB1rOyqXoE0e4izwL
+k42kRO4QR1kJjyTHbcGhvn3g2fToUV7VYgKvcehBwL8
+-> h.r.QMtj-grease 3{m -KtBe -]Xr`yV (Q9+/P*f
+p7bvAoEcW/lhcvDax8uVQ+esucOktPskLheaopOFoKLY0B/ArnlmYe1EtXk
+--- 5aK3NPrIR0OFamf5mE6v0meDH4P5hK8QeC5mjyJhj4o
+I�v�8p���C�Mڈ�ih�N
��9��^*�)�b*+�f��p�is�������ֵ�T��3�{�.r��X�ew1B1�K�������)r�A�2k`G\
\ No newline at end of file
modules/services/forgejo/server.nix
@@ -33,6 +33,45 @@
       ];
     };
 
+    backup = {
+      pkgs,
+      lib,
+      config,
+      ...
+    }: {
+      forgejo = let
+        backupDir = "/tmp/backups/forgejo";
+      in {
+        paths = [backupDir];
+
+        backupPrepareCommand = lib.getExe (pkgs.writeShellApplication {
+          name = "backup-vaultwarden-prepare";
+          runtimeInputs = [
+            config.services.forgejo.package
+            pkgs.sudo
+          ];
+          text = ''
+            set -euo pipefail
+
+            DATA_DIR="${config.services.forgejo.stateDir}"
+            FORGEJO_USER="${config.services.forgejo.user}"
+
+            mkdir -p "${backupDir}"
+            chown "$FORGEJO_USER" "${backupDir}"
+
+            sudo -u "$FORGEJO_USER" forgejo dump \
+              --work-path "$DATA_DIR" \
+              --type "zip" \
+              --file "${backupDir}/forgejo-dump.zip" \
+              --verbose
+          '';
+        });
+        backupCleanupCommand = ''
+          rm -r "${backupDir}"
+        '';
+      };
+    };
+
     nixos = {
       host,
       pkgs,
modules/services/grafana/default.nix
@@ -42,6 +42,42 @@
       };
     };
 
+    backup = {
+      pkgs,
+      lib,
+      ...
+    }: {
+      grafana = let
+        backupDir = "/tmp/backups/grafana";
+      in {
+        paths = [backupDir];
+
+        # See https://grafana.com/docs/grafana/latest/administration/back-up-grafana/
+        backupPrepareCommand = lib.getExe (pkgs.writeShellApplication {
+          name = "backup-grafana-prepare";
+          runtimeInputs = [
+            pkgs.sqlite
+          ];
+          text = ''
+            set -euo pipefail
+
+            DATA_DIR="/var/lib/grafana/data"
+
+            mkdir -p "${backupDir}"
+
+            sqlite3 "$DATA_DIR/grafana.db" ".backup '${backupDir}/grafana.db'"
+
+            if [ -d "$DATA_DIR/plugins" ]; then
+              cp -a "$DATA_DIR/plugins" "${backupDir}/"
+            fi
+          '';
+        });
+        backupCleanupCommand = ''
+          rm -r "${backupDir}"
+        '';
+      };
+    };
+
     nixos = {
       host,
       config,
modules/services/vaultwarden.nix
@@ -33,6 +33,42 @@
       };
     };
 
+    backup = {
+      pkgs,
+      lib,
+      ...
+    }: {
+      vaultwarden = let
+        backupDir = "/tmp/backups/vaultwarden";
+      in {
+        paths = [backupDir];
+
+        # See https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault
+        backupPrepareCommand = lib.getExe (pkgs.writeShellApplication {
+          name = "backup-vaultwarden-prepare";
+          runtimeInputs = [
+            pkgs.sqlite
+          ];
+          text = ''
+            set -euo pipefail
+
+            DATA_DIR="/var/lib/vaultwarden"
+
+            mkdir -p "${backupDir}"
+
+            sqlite3 "$DATA_DIR/db.sqlite3" ".backup '${backupDir}/db.sqlite3'"
+
+            if [ -d "$DATA_DIR/attachments" ]; then
+              cp -a "$DATA_DIR/attachments" "${backupDir}/"
+            fi
+          '';
+        });
+        backupCleanupCommand = ''
+          rm -r "${backupDir}"
+        '';
+      };
+    };
+
     nixos = {host, ...}: let
       cfg = host.settings.services.vaultwarden;
     in {
modules/services/wakapi.nix
@@ -42,6 +42,12 @@
       };
     };
 
+    backup = {
+      wakapi = {
+        paths = ["/var/lib/private/wakapi"];
+      };
+    };
+
     nixos = {
       host,
       config,