Commit 26df56fc3e9f
Changed files (5)
secrets
secrets/aion/restic-aix-password.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> piv-p256 ItIHHA A358ugWZLfOwRtt7zNALZHMybJas7HvoQqzh+z47lpzs
+kw+adVWVvGyta9u8yyngYXzyGgqVYBTN0xYCcwhv66k
+-> piv-p256 ViCCtQ At82oGi0N1eaUniWXoEA0sJ4y8VSaE97TS+rmFjLa6ew
+y7MVJnP6YCJyk9Jmg5Ocf/piXTjW8JZZEjsLB/Pltio
+-> ssh-ed25519 5bXRbA R1Kw1VQZ6T8qWN7yf+77CD+i8IM64jD2btvOzZwpIy0
+cFjHFXPFAdPnnHCXMQ+TMh4TQMpu3DIZ2AHfTVVnmh0
+--- aDfkx2Pz1h9kzdXft3oPpldYz+GacXwrWAlxjNTPhcU
+SP��9qu<�NK��oFu�b��x m��)Z�� �&n\'P"m��h���:�=�����lS�D�b�
\ No newline at end of file
secrets/rhea/restic-aix-password.age
@@ -0,0 +1,11 @@
+age-encryption.org/v1
+-> piv-p256 ItIHHA AqG+sM7Nht5jBimmNVQp2aIYGAIr+HpJD6XnZDRezIaf
+VXmDKQWzDQfIE7PKorSDwyUtTfqO9PU7RqV2Md2hqn4
+-> piv-p256 ViCCtQ Aiu8xAmC1nfcfUKtKIw2haYdfmKsGPnEVsEfGPlHV4qH
+9GH6ZrO1ByuDDAgobzBL3M7ICMFHfSlF9rVKG61cbtM
+-> ssh-ed25519 EboMJg 4q9dnqs1ACqF9+64gwy9b2izU5JGLDadwvcOMQLy7DI
+HVVZ0nWKBN1TkF3HQ2KqB6FokwNygg3kkm71dRXu2+s
+--- 058fqEgNV+RnfBWo/mRWdoWeZdd0+2C7v3ROwo2Ztfo
+��@yz3A$>�>
+��'8!�8s�W�/N����
+y���2@���C�ٟ*��o(�"}'�.�������K
\ No newline at end of file
systems/aion/extra.nix
@@ -3,6 +3,7 @@
globals,
lib,
pkgs,
+ config,
...
}:
let
@@ -64,6 +65,18 @@ in
mode = "440";
group = "homepage";
};
+ "restic-aix-password" = {
+ file = ../../secrets/aion/restic-aix-password.age;
+ mode = "400";
+ owner = "vincent";
+ group = "users";
+ };
+ "ntfy-token" = {
+ file = ../../secrets/sakhalin/ntfy-token.age;
+ mode = "400";
+ owner = "vincent";
+ group = "users";
+ };
};
services = {
@@ -124,6 +137,85 @@ in
};
};
+ # Restic backup to aix (off-site backup)
+ # Note: Photos are already rsync'd to aix daily via aix's pull job
+ # This backup focuses on critical versioned data only
+ restic.backups.aix-critical = {
+ user = "vincent";
+ repository = "sftp:vincent@aix.sbr.pm:/data/backup/restic/aion";
+
+ # Use password-based encryption
+ passwordFile = config.age.secrets."restic-aix-password".path;
+
+ paths = [
+ "/neo/pictures/photos/backups" # Immich database dumps only (~100MB, versioned)
+ "/home/vincent/desktop/org" # Org files (<1GB)
+ "/home/vincent/desktop/documents" # Personal docs (~113GB)
+ "/var/lib/lidarr" # Lidarr database and config (~4.6GB)
+ "/var/lib/audiobookshelf" # Audiobookshelf database and config (~30MB)
+ ];
+
+ # Backup schedule - weekly for large dataset
+ timerConfig = {
+ OnCalendar = "weekly";
+ Persistent = true;
+ RandomizedDelaySec = "1h"; # Avoid VPN congestion
+ };
+
+ # Retention policy
+ pruneOpts = [
+ "--keep-daily 7" # Last 7 days
+ "--keep-weekly 4" # Last 4 weeks
+ "--keep-monthly 12" # Last 12 months
+ "--keep-yearly 3" # Last 3 years
+ ];
+
+ # Backup options
+ extraBackupArgs = [
+ "--exclude-caches"
+ "--exclude='*.Trash-*'"
+ "--exclude='lost+found'"
+ "--exclude='.sync-conflict-*'" # Syncthing conflicts
+ "--verbose"
+ ];
+
+ # Check repository integrity after backup
+ checkOpts = [
+ "--read-data-subset=5%" # Verify 5% of data each run
+ ];
+
+ # Backup monitoring with ntfy.sh
+ backupPrepareCommand = ''
+ ${pkgs.curl}/bin/curl \
+ -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${
+ config.age.secrets."ntfy-token".path
+ })" \
+ -H "Title: Restic Backup Starting (aion)" \
+ -d "Starting backup to aix (critical data only)" \
+ https://ntfy.sbr.pm/backups
+ '';
+
+ backupCleanupCommand = ''
+ ${pkgs.curl}/bin/curl \
+ -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${
+ config.age.secrets."ntfy-token".path
+ })" \
+ -H "Title: Restic Backup Complete (aion)" \
+ -H "Tags: white_check_mark" \
+ -d "Backup to aix completed successfully" \
+ https://ntfy.sbr.pm/backups || \
+ ${pkgs.curl}/bin/curl \
+ -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${
+ config.age.secrets."ntfy-token".path
+ })" \
+ -H "Title: Restic Backup Failed (aion)" \
+ -H "Tags: x,warning" \
+ -H "Priority: high" \
+ -d "Backup to aix failed! Check logs: journalctl -u restic-backups-aix-critical.service" \
+ https://ntfy.sbr.pm/backups
+ '';
+ };
+
music-playlist-dl = {
enable = true; # Enable on music migration day
user = "vincent";
@@ -158,7 +250,7 @@ in
};
};
- transmission = {
+ transmission = serviceDefaults // {
enable = true; # Enable on music migration day
package = pkgs.transmission_4;
openRPCPort = true; # Open firewall for RPC (port 9091)
systems/rhea/extra.nix
@@ -100,6 +100,18 @@ in
mode = "400";
owner = "jellyfin-auto-collections";
};
+ "restic-aix-password" = {
+ file = ../../secrets/rhea/restic-aix-password.age;
+ mode = "400";
+ owner = "vincent";
+ group = "users";
+ };
+ "ntfy-token" = {
+ file = ../../secrets/sakhalin/ntfy-token.age;
+ mode = "400";
+ owner = "vincent";
+ group = "users";
+ };
}
// lib.mapAttrs' (
name: _cfg:
@@ -652,6 +664,88 @@ in
apiKeyFile = config.age.secrets."exportarr-${name}-apikey".path;
}
) exportarrServices;
+
+ # Restic backup to aix (off-site backup)
+ # Note: Media files are rsync'd (rhea → aion → aix)
+ # This backup focuses on arr service databases and configs
+ restic.backups.aix-critical = {
+ user = "vincent";
+ repository = "sftp:vincent@aix.sbr.pm:/data/backup/restic/rhea";
+
+ # Use password-based encryption
+ passwordFile = config.age.secrets."restic-aix-password".path;
+
+ paths = [
+ "/var/lib/sonarr" # Sonarr database and config (~501MB)
+ "/var/lib/radarr" # Radarr database and config (~729MB)
+ "/var/lib/bazarr" # Bazarr database and config (~25MB)
+ "/var/lib/readarr" # Readarr database and config (~6MB)
+ "/var/lib/prowlarr" # Prowlarr database and config
+ "/var/lib/jellyfin" # Jellyfin database and config
+ # "/var/lib/immich" # Immich app data # Already handled in aion
+ # "/var/lib/traefik" # Traefik acme.json (Let's Encrypt certs)
+ ];
+
+ # Backup schedule - weekly for moderate dataset
+ timerConfig = {
+ OnCalendar = "weekly";
+ Persistent = true;
+ RandomizedDelaySec = "2h"; # Avoid conflict with aion backup
+ };
+
+ # Retention policy
+ pruneOpts = [
+ "--keep-daily 7" # Last 7 days
+ "--keep-weekly 4" # Last 4 weeks
+ "--keep-monthly 12" # Last 12 months
+ "--keep-yearly 3" # Last 3 years
+ ];
+
+ # Backup options
+ extraBackupArgs = [
+ "--exclude-caches"
+ "--exclude='*.Trash-*'"
+ "--exclude='lost+found'"
+ "--exclude='logs.db'" # Exclude log databases (large, not critical)
+ "--verbose"
+ ];
+
+ # Check repository integrity after backup
+ checkOpts = [
+ "--read-data-subset=5%" # Verify 5% of data each run
+ ];
+
+ # Backup monitoring with ntfy.sh
+ backupPrepareCommand = ''
+ ${pkgs.curl}/bin/curl \
+ -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${
+ config.age.secrets."ntfy-token".path
+ })" \
+ -H "Title: Restic Backup Starting (rhea)" \
+ -d "Starting backup to aix (arr services + configs)" \
+ https://ntfy.sbr.pm/backups
+ '';
+
+ backupCleanupCommand = ''
+ ${pkgs.curl}/bin/curl \
+ -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${
+ config.age.secrets."ntfy-token".path
+ })" \
+ -H "Title: Restic Backup Complete (rhea)" \
+ -H "Tags: white_check_mark" \
+ -d "Backup to aix completed successfully" \
+ https://ntfy.sbr.pm/backups || \
+ ${pkgs.curl}/bin/curl \
+ -H "Authorization: Bearer $(${pkgs.coreutils}/bin/tr -d '\n' < ${
+ config.age.secrets."ntfy-token".path
+ })" \
+ -H "Title: Restic Backup Failed (rhea)" \
+ -H "Tags: x,warning" \
+ -H "Priority: high" \
+ -d "Backup to aix failed! Check logs: journalctl -u restic-backups-aix-critical.service" \
+ https://ntfy.sbr.pm/backups
+ '';
+ };
};
security.acme = {
secrets.nix
@@ -18,13 +18,14 @@ let
# wakasu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrAh07USjRnAdS3mMNGdKee1KumjYDLzgXaiZ5LYi2D"; # ssh-keyscan -q -t ed25519 wakasu.sbr.pm
kyushu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINd795m+P54GlGJdMaGci9pQ9N942VUz8ri2F14+LWxg"; # ssh-keyscan -q -t ed25519 kyushu.sbr.pm
aion = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXDNi2KtoRU83y/V5OWnMbFWmxwBknPmrNWV4RChE7R"; # ssh-keyscan -q -t ed25519 aion.sbr.pm
+ aix = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEoUicDySCGETPAgmI0P3UrgZEXXw3zNsyCIylUP0bML"; # ssh-keyscan -q -t ed25519 aix.sbr.pm
# TODO: kobe
- # TODO: aix
desktops = [
kyushu
];
servers = [
aion
+ aix
aomi
athena
demeter
@@ -122,7 +123,13 @@ in
"secrets/rhea/jellyfin-auto-collections-jellyseerr-password.age".publicKeys = users ++ [ rhea ];
"secrets/rhea/webdav-password.age".publicKeys = users ++ [ rhea ];
"secrets/sakhalin/grafana-admin-password.age".publicKeys = users ++ [ sakhalin ];
- "secrets/sakhalin/ntfy-token.age".publicKeys = users ++ [ sakhalin ];
+ "secrets/sakhalin/ntfy-token.age".publicKeys = users ++ [
+ sakhalin
+ aion
+ rhea
+ ];
"secrets/sakhalin/homeassistant-prometheus-token.age".publicKeys = users ++ [ sakhalin ];
"secrets/demeter/mosquitto-homeassistant-password.age".publicKeys = users ++ [ demeter ];
+ "secrets/aion/restic-aix-password.age".publicKeys = users ++ [ aion ];
+ "secrets/rhea/restic-aix-password.age".publicKeys = users ++ [ rhea ];
}