nix all the things

delete pds backup solution

Will need to design somthing better, this is doing a lot of aws ops

karitham.dev c8cd1664 55873897

verified
+75 -347
+75 -53
flake.lock
··· 7 7 ] 8 8 }, 9 9 "locked": { 10 - "lastModified": 1771587924, 11 - "narHash": "sha256-eVYOGmF8nQBhudJyU6lHdgJI87kvGz8JyCq5/Vi9Mjk=", 10 + "lastModified": 1772153824, 11 + "narHash": "sha256-T65qXmlcD9qFpPTi+mOXsn4dIkO2N8Ls67nqmuzepv0=", 12 12 "owner": "catppuccin", 13 13 "repo": "nix", 14 - "rev": "b0c65edbf31c2ad3d84438d82c2310f2c28373f3", 14 + "rev": "4b0f5b7bf7b3eeb484d49524f3c9791864ab9362", 15 15 "type": "github" 16 16 }, 17 17 "original": { ··· 22 22 }, 23 23 "crane": { 24 24 "locked": { 25 - "lastModified": 1771121070, 26 - "narHash": "sha256-aIlv7FRXF9q70DNJPI237dEDAznSKaXmL5lfK/Id/bI=", 25 + "lastModified": 1771796463, 26 + "narHash": "sha256-9bCDuUzpwJXcHMQYMS1yNuzYMmKO/CCwCexpjWOl62I=", 27 27 "owner": "ipetkov", 28 28 "repo": "crane", 29 - "rev": "a2812c19f1ed2e5ed5ce2ef7109798b575c180e1", 29 + "rev": "3d3de3313e263e04894f284ac18177bd26169bad", 30 30 "type": "github" 31 31 }, 32 32 "original": { ··· 111 111 ] 112 112 }, 113 113 "locked": { 114 - "lastModified": 1769996383, 115 - "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", 114 + "lastModified": 1772408722, 115 + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", 116 116 "owner": "hercules-ci", 117 117 "repo": "flake-parts", 118 - "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", 118 + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", 119 119 "type": "github" 120 120 }, 121 121 "original": { ··· 173 173 "zon2nix": "zon2nix" 174 174 }, 175 175 "locked": { 176 - "lastModified": 1771991459, 177 - "narHash": "sha256-VRbgXfW0DM3Kx3JEWFKj9LZQGng6FwfCDCfgoIweRVk=", 176 + "lastModified": 1772465579, 177 + "narHash": "sha256-dXfemWSyeQ6DHQXYSjDwYlvDQMyBoIyh7so849orUsw=", 178 178 "owner": "ghostty-org", 179 179 "repo": "ghostty", 180 - "rev": "4c8f2bc77b218349839b8e929a981a2bdf4734a8", 180 + "rev": "8cddd384c6ef614a373cf52b2a0e835c7e85134a", 181 181 "type": "github" 182 182 }, 183 183 "original": { ··· 236 236 ] 237 237 }, 238 238 "locked": { 239 - "lastModified": 1771851181, 240 - "narHash": "sha256-gFgE6mGUftwseV3DUENMb0k0EiHd739lZexPo5O/sdQ=", 239 + "lastModified": 1772380461, 240 + "narHash": "sha256-O3ukj3Bb3V0Tiy/4LUfLlBpWypJ9P0JeUgsKl2nmZZY=", 241 241 "owner": "nix-community", 242 242 "repo": "home-manager", 243 - "rev": "9a4b494b1aa1b93d8edf167f46dc8e0c0011280c", 243 + "rev": "f140aa04d7d14f8a50ab27f3691b5766b17ae961", 244 244 "type": "github" 245 245 }, 246 246 "original": { ··· 279 279 "rust-overlay": "rust-overlay" 280 280 }, 281 281 "locked": { 282 - "lastModified": 1771834715, 283 - "narHash": "sha256-5VI2KiMifx3Dca7nDJzctO3HpnS6zrvesdkLoZBrQRY=", 282 + "lastModified": 1772216104, 283 + "narHash": "sha256-1TnGN26vnCEQk5m4AavJZxGZTb/6aZyphemRPRwFUfs=", 284 284 "owner": "nix-community", 285 285 "repo": "lanzaboote", 286 - "rev": "b798c53da0f7e521317a5413335096a21070cf0b", 286 + "rev": "dbe5112de965bbbbff9f0729a9789c20a65ab047", 287 287 "type": "github" 288 288 }, 289 289 "original": { ··· 302 302 "xwayland-satellite-unstable": "xwayland-satellite-unstable" 303 303 }, 304 304 "locked": { 305 - "lastModified": 1771940378, 306 - "narHash": "sha256-qe5t8E8uK5eSgPTxtfcde3VO8fnIr/Tu+hn72FDry/E=", 305 + "lastModified": 1772433239, 306 + "narHash": "sha256-5pPusMALo7ZYEoW/iHUxK7rLk3Kg8sJ8Sdf7IcfK5HA=", 307 307 "owner": "sodiboo", 308 308 "repo": "niri-flake", 309 - "rev": "f8899e60a1425d21a03a05ac2c069a85398039b5", 309 + "rev": "c56af55f5563f7c7043ed45ed2566a69a638448d", 310 310 "type": "github" 311 311 }, 312 312 "original": { ··· 336 336 "niri-unstable": { 337 337 "flake": false, 338 338 "locked": { 339 - "lastModified": 1771849386, 340 - "narHash": "sha256-CFvjBjS2LxbBMR3Lu6wZhME6ck3CXyKUufRoJA5tlmw=", 339 + "lastModified": 1772207631, 340 + "narHash": "sha256-Jkkg+KqshFO3CbTszVVpkKN2AOObYz+wMsM3ONo1z5g=", 341 341 "owner": "YaLTeR", 342 342 "repo": "niri", 343 - "rev": "2dc6f4482c4eeed75ea8b133d89cad8658d38429", 343 + "rev": "e708f546153f74acf33eb183b3b2992587a701e5", 344 344 "type": "github" 345 345 }, 346 346 "original": { ··· 357 357 ] 358 358 }, 359 359 "locked": { 360 - "lastModified": 1771563879, 361 - "narHash": "sha256-vA5hocvdGhr+jfBN7A7ogeZqIz2qx01EixXwdVsQcnE=", 360 + "lastModified": 1772386632, 361 + "narHash": "sha256-sm6OpWZuoDwR53KNlsY482YOoHFWlWYwt0wHmqLkRGE=", 362 362 "owner": "nix-community", 363 363 "repo": "NixOS-WSL", 364 - "rev": "379d20c55f552e91fb9f3f0382e4a97d3f452943", 364 + "rev": "be894604b2aa2184c0b3d3b44995acd0da14dc0c", 365 365 "type": "github" 366 366 }, 367 367 "original": { ··· 385 385 }, 386 386 "nixpkgs-stable": { 387 387 "locked": { 388 - "lastModified": 1771903837, 389 - "narHash": "sha256-sdaqdnsQCv3iifzxwB22tUwN/fSHoN7j2myFW5EIkGk=", 388 + "lastModified": 1772047000, 389 + "narHash": "sha256-7DaQVv4R97cii/Qdfy4tmDZMB2xxtyIvNGSwXBBhSmo=", 390 390 "owner": "NixOS", 391 391 "repo": "nixpkgs", 392 - "rev": "e764fc9a405871f1f6ca3d1394fb422e0a0c3951", 392 + "rev": "1267bb4920d0fc06ea916734c11b0bf004bbe17e", 393 393 "type": "github" 394 394 }, 395 395 "original": { ··· 401 401 }, 402 402 "nixpkgs_2": { 403 403 "locked": { 404 - "lastModified": 1771848320, 405 - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", 404 + "lastModified": 1772198003, 405 + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", 406 406 "owner": "NixOS", 407 407 "repo": "nixpkgs", 408 - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", 408 + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", 409 409 "type": "github" 410 410 }, 411 411 "original": { ··· 417 417 }, 418 418 "nixpkgs_3": { 419 419 "locked": { 420 - "lastModified": 1771923393, 421 - "narHash": "sha256-lJiQtL47xYV37Vod9KgPS7dmi1x51A5+chJE48qKimw=", 422 - "rev": "ea7f1f06811ce7fcc81d6c6fd4213150c23edcf2", 420 + "lastModified": 1772419343, 421 + "narHash": "sha256-xAHiI07j8nMZuA53r40AoWgYH9bKCgaVowNq5MpkE3g=", 422 + "rev": "93178f6a00c22fcdee1c6f5f9ab92f2072072ea9", 423 423 "type": "tarball", 424 - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre953762.ea7f1f06811c/nixexprs.tar.xz?lastModified=1771923393&rev=ea7f1f06811ce7fcc81d6c6fd4213150c23edcf2" 424 + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre956909.93178f6a00c2/nixexprs.tar.xz" 425 425 }, 426 426 "original": { 427 427 "type": "tarball", ··· 448 448 "inputs": { 449 449 "nixpkgs": [ 450 450 "nixpkgs" 451 - ] 451 + ], 452 + "noctalia-qs": "noctalia-qs" 452 453 }, 453 454 "locked": { 454 - "lastModified": 1771979542, 455 - "narHash": "sha256-0hqvh2D8tdwFG9pT9Ds1EysZYPTKpVJCunnXQ31MUZ8=", 455 + "lastModified": 1772453415, 456 + "narHash": "sha256-8TCMSFCBZdutKryFKX72GOb/NWL9/vB5rswgWXV/EuM=", 456 457 "owner": "noctalia-dev", 457 458 "repo": "noctalia-shell", 458 - "rev": "fe8e23f1d0e6ed4bdefd061e6834c4d61f2266be", 459 + "rev": "8ebf2bf33220c62f3c5e937a318eceb25dd17228", 459 460 "type": "github" 460 461 }, 461 462 "original": { ··· 464 465 "type": "github" 465 466 } 466 467 }, 468 + "noctalia-qs": { 469 + "inputs": { 470 + "nixpkgs": [ 471 + "noctalia", 472 + "nixpkgs" 473 + ] 474 + }, 475 + "locked": { 476 + "lastModified": 1772227064, 477 + "narHash": "sha256-f821ZSoGpa/aXrWq0gPpea9qBnX8KDyavGKkptz2Mog=", 478 + "owner": "noctalia-dev", 479 + "repo": "noctalia-qs", 480 + "rev": "0741d27d2f7db567270f139c5d1684614ecf9863", 481 + "type": "github" 482 + }, 483 + "original": { 484 + "owner": "noctalia-dev", 485 + "repo": "noctalia-qs", 486 + "type": "github" 487 + } 488 + }, 467 489 "pre-commit": { 468 490 "inputs": { 469 491 "flake-compat": "flake-compat_2", ··· 474 496 ] 475 497 }, 476 498 "locked": { 477 - "lastModified": 1770726378, 478 - "narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=", 499 + "lastModified": 1771858127, 500 + "narHash": "sha256-Gtre9YoYl3n25tJH2AoSdjuwcqij5CPxL3U3xysYD08=", 479 501 "owner": "cachix", 480 502 "repo": "pre-commit-hooks.nix", 481 - "rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae", 503 + "rev": "49bbbfc218bf3856dfa631cead3b052d78248b83", 482 504 "type": "github" 483 505 }, 484 506 "original": { ··· 530 552 ] 531 553 }, 532 554 "locked": { 533 - "lastModified": 1771125043, 534 - "narHash": "sha256-ldf/s49n6rOAxl7pYLJGGS1N/assoHkCOWdEdLyNZkc=", 555 + "lastModified": 1771988922, 556 + "narHash": "sha256-Fc6FHXtfEkLtuVJzd0B6tFYMhmcPLuxr90rWfb/2jtQ=", 535 557 "owner": "oxalica", 536 558 "repo": "rust-overlay", 537 - "rev": "4912f951a26dc8142b176be2c2ad834319dc06e8", 559 + "rev": "f4443dc3f0b6c5e6b77d923156943ce816d1fcb9", 538 560 "type": "github" 539 561 }, 540 562 "original": { ··· 550 572 ] 551 573 }, 552 574 "locked": { 553 - "lastModified": 1771889317, 554 - "narHash": "sha256-YV17Q5lEU0S9ppw08Y+cs4eEQJBuc79AzblFoHORLMU=", 575 + "lastModified": 1772401007, 576 + "narHash": "sha256-YHykQg0h9hrlZGpMcywnaFzQ1Kn/5YNCCOSaaAl6z7Q=", 555 577 "owner": "Mic92", 556 578 "repo": "sops-nix", 557 - "rev": "b027513c32e5b39b59f64626b87fbe168ae02094", 579 + "rev": "d8be5ea4cd3bc363492ab5bc6e874ccdc5465fe4", 558 580 "type": "github" 559 581 }, 560 582 "original": { ··· 646 668 "xwayland-satellite-unstable": { 647 669 "flake": false, 648 670 "locked": { 649 - "lastModified": 1771787042, 650 - "narHash": "sha256-7bM6Y4KldhKnfopSALF8XALxcX7ehkomXH9sPl4MXp0=", 671 + "lastModified": 1772429643, 672 + "narHash": "sha256-M+bAeCCcjBnVk6w/4dIVvXvpJwOKnXjwi/lDbaN6Yws=", 651 673 "owner": "Supreeeme", 652 674 "repo": "xwayland-satellite", 653 - "rev": "33c344fee50504089a447a8fef5878cf4f6215fc", 675 + "rev": "10f985b84cdbcc3bbf35b3e7e43d1b2a84fa9ce2", 654 676 "type": "github" 655 677 }, 656 678 "original": {
-294
modules/pds/nixos.nix
··· 1 1 { 2 2 config, 3 3 lib, 4 - pkgs, 5 4 options, 6 5 ... 7 6 }: ··· 16 15 ; 17 16 18 17 cfg = config.services.pds-with-backups; 19 - 20 - pdsUser = "pds"; 21 - pdsGroup = "pds"; 22 - 23 18 inherit (cfg) secretsFiles; 24 - 25 - litestreamConfig = pkgs.writeText "litestream-pds-config.yml" '' 26 - dbs: 27 - - dir: ${cfg.dataDir} 28 - pattern: "*.sqlite" 29 - recursive: true 30 - watch: true 31 - replica: 32 - type: s3 33 - path: ${cfg.backupS3Prefix} 34 - bucket: ''${S3_BUCKET} 35 - ''; 36 - 37 - restoreScript = pkgs.writeShellApplication { 38 - name = "pds-litestream-restore"; 39 - runtimeInputs = with pkgs; [ 40 - awscli2 41 - litestream 42 - gnugrep 43 - gnused 44 - coreutils 45 - findutils 46 - gawk 47 - ]; 48 - excludeShellChecks = [ 49 - "SC1091" 50 - "SC2046" 51 - "SC2168" 52 - "SC1090" 53 - "SC2043" 54 - ]; 55 - text = '' 56 - main() { 57 - set -euo pipefail 58 - 59 - echo "[PDS Restore] Starting automatic restore from S3..." 60 - 61 - for f in ${toString secretsFiles}; do 62 - if [ -f "$f" ]; then 63 - set -a 64 - source "$f" 65 - set +a 66 - else 67 - echo "[PDS Restore] Error: Secrets file not found: $f" 68 - exit 1 69 - fi 70 - done 71 - 72 - if [ -z "''${S3_BUCKET:-}" ]; then 73 - echo "[PDS Restore] Error: S3_BUCKET not set in secrets file" 74 - exit 1 75 - fi 76 - 77 - s3Bucket="''${S3_BUCKET}" 78 - s3Prefix="${cfg.backupS3Prefix}" 79 - 80 - run_aws() { 81 - local envArgs=() 82 - if [ -n "''${AWS_ENDPOINT_URL:-}" ]; then 83 - envArgs+=("AWS_ENDPOINT_URL=''${AWS_ENDPOINT_URL}") 84 - fi 85 - env "''${envArgs[@]}" aws "$@" 86 - } 87 - 88 - run_litestream() { 89 - local envArgs=() 90 - if [ -n "''${AWS_ENDPOINT_URL:-}" ]; then 91 - envArgs+=("AWS_ENDPOINT_URL=''${AWS_ENDPOINT_URL}") 92 - fi 93 - env "''${envArgs[@]}" litestream "$@" 94 - } 95 - 96 - echo "[PDS Restore] Verifying S3 connectivity..." 97 - local retries=5 98 - local connected=false 99 - for i in $(seq 1 $retries); do 100 - if run_aws s3 ls "s3://$s3Bucket/" &>/dev/null; then 101 - connected=true 102 - break 103 - fi 104 - echo "[PDS Restore] Waiting for S3 bucket (attempt $i/$retries)..." 105 - sleep 2 106 - done 107 - 108 - if [ "$connected" = false ]; then 109 - echo "[PDS Restore] Error: Cannot connect to S3 bucket: $s3Bucket" 110 - exit 1 111 - fi 112 - 113 - echo "[PDS Restore] S3 connection verified" 114 - 115 - local objects 116 - objects=$(run_aws s3 ls "s3://$s3Bucket/$s3Prefix" --recursive 2>/dev/null | awk '{print $4}' || true) 117 - 118 - local databases 119 - databases=$(echo "$objects" | grep '\.sqlite/' | sed 's|.*/\([^/]*\.sqlite\).*|\1|' | sort -u || true) 120 - 121 - if [ -z "$databases" ]; then 122 - echo "[PDS Restore] No databases found in S3 at s3://$s3Bucket/$s3Prefix" 123 - echo "[PDS Restore] New deployment - skipping restore." 124 - exit 0 125 - fi 126 - 127 - echo "[PDS Restore] Found databases to restore:" 128 - echo "$databases" 129 - echo "" 130 - 131 - mkdir -p "${cfg.dataDir}" 132 - 133 - local restoredCount=0 134 - for db in $databases; do 135 - local localPath="${cfg.dataDir}/$db" 136 - local s3DbPath="$s3Prefix/$db" 137 - local s3DbUrl="s3://$s3Bucket/$s3DbPath" 138 - 139 - if [ -f "$localPath" ]; then 140 - echo "[PDS Restore] Database already exists locally: $db (skipping)" 141 - continue 142 - fi 143 - 144 - echo "[PDS Restore] Restoring database: $db" 145 - mkdir -p "$(dirname "$localPath")" 146 - 147 - if run_litestream restore -if-db-not-exists -if-replica-exists -o "$localPath" "$s3DbUrl"; then 148 - echo "[PDS Restore] Successfully restored: $db" 149 - restoredCount=$((restoredCount + 1)) 150 - 151 - if [ -f "$localPath" ]; then 152 - chown ${pdsUser}:${pdsGroup} "$localPath" 153 - chmod 644 "$localPath" 154 - fi 155 - else 156 - echo "[PDS Restore] Warning: Failed to restore $db" 157 - fi 158 - done 159 - 160 - echo "" 161 - echo "[PDS Restore] Restore completed. Restored $restoredCount database(s)." 162 - 163 - if [ $restoredCount -eq 0 ]; then 164 - echo "[PDS Restore] No new databases restored. PDS will start with fresh state." 165 - fi 166 - } 167 - 168 - main 169 - ''; 170 - }; 171 - 172 - healthCheckScript = pkgs.writeShellScript "pds-healthcheck" '' 173 - set -euo pipefail 174 - 175 - if ! systemctl is-active --quiet bluesky-pds; then 176 - echo "[PDS HealthCheck] PDS service is not running" 177 - exit 1 178 - fi 179 - 180 - if [ -f "${cfg.dataDir}/primary.sqlite" ]; then 181 - if ! systemctl is-active --quiet litestream-pds; then 182 - echo "[PDS HealthCheck] Litestream service is not running" 183 - exit 1 184 - fi 185 - fi 186 - 187 - echo "[PDS HealthCheck] All services healthy" 188 - exit 0 189 - ''; 190 19 in 191 20 { 192 21 options.services.pds-with-backups = { ··· 210 39 example = [ "/run/secrets/pds.env" ]; 211 40 }; 212 41 213 - backupS3Prefix = mkOption { 214 - type = types.strMatching "[^/].*[^/]"; 215 - default = "backups"; 216 - description = "S3 directory prefix for Litestream replicas."; 217 - example = "pds-backups"; 218 - }; 219 - 220 - backupLogDir = mkOption { 221 - type = types.path; 222 - default = "/var/log/pds-backup"; 223 - description = "Directory for backup and restore logs."; 224 - internal = true; 225 - }; 226 - 227 42 settings = mkOption { 228 43 inherit (options.services.bluesky-pds.settings) type; 229 44 default = { }; ··· 247 62 ]; 248 63 environmentFiles = secretsFiles; 249 64 }; 250 - 251 - users.users.${pdsUser} = { 252 - isSystemUser = true; 253 - group = pdsGroup; 254 - description = "Bluesky PDS service user"; 255 - }; 256 - users.groups.${pdsGroup} = { }; 257 - 258 - systemd.tmpfiles.rules = [ 259 - "d ${cfg.dataDir} 0755 ${pdsUser} ${pdsGroup} -" 260 - "d ${cfg.backupLogDir} 0755 ${pdsUser} ${pdsGroup} -" 261 - ]; 262 - 263 - systemd.services.bluesky-pds = { 264 - after = [ 265 - "network.target" 266 - "pds-restore.service" 267 - ]; 268 - wants = [ "pds-restore.service" ]; 269 - serviceConfig.Restart = lib.mkDefault "on-failure"; 270 - serviceConfig.RestartSec = "10s"; 271 - }; 272 - 273 - systemd.services.pds-restore = { 274 - description = "PDS Automatic Restore from S3"; 275 - wantedBy = [ "multi-user.target" ]; 276 - before = [ "bluesky-pds.service" ]; 277 - 278 - serviceConfig = { 279 - Type = "oneshot"; 280 - ExecStart = "${restoreScript}/bin/pds-litestream-restore"; 281 - EnvironmentFile = secretsFiles; 282 - User = pdsUser; 283 - Group = pdsGroup; 284 - RemainAfterExit = true; 285 - 286 - NoNewPrivileges = true; 287 - ProtectSystem = "strict"; 288 - ProtectHome = true; 289 - PrivateTmp = true; 290 - RestrictRealtime = true; 291 - }; 292 - }; 293 - 294 - systemd.services.litestream-pds = { 295 - description = "Litestream real-time replication for PDS databases"; 296 - after = [ 297 - "network.target" 298 - "pds-restore.service" 299 - "bluesky-pds.service" 300 - ]; 301 - requires = [ "bluesky-pds.service" ]; 302 - wantedBy = [ "multi-user.target" ]; 303 - 304 - serviceConfig = { 305 - ExecStart = "${pkgs.litestream}/bin/litestream replicate -config ${litestreamConfig}"; 306 - EnvironmentFile = secretsFiles; 307 - User = pdsUser; 308 - Group = pdsGroup; 309 - Restart = "on-failure"; 310 - RestartSec = "5s"; 311 - 312 - NoNewPrivileges = true; 313 - ProtectSystem = "strict"; 314 - ProtectHome = true; 315 - ReadWritePaths = [ 316 - cfg.dataDir 317 - cfg.backupLogDir 318 - ]; 319 - RestrictRealtime = true; 320 - MemoryDenyWriteExecute = true; 321 - }; 322 - }; 323 - 324 - systemd.services.pds-healthcheck = { 325 - description = "PDS Health Check"; 326 - after = [ 327 - "bluesky-pds.service" 328 - "litestream-pds.service" 329 - ]; 330 - requires = [ "bluesky-pds.service" ]; 331 - 332 - serviceConfig = { 333 - Type = "oneshot"; 334 - ExecStart = healthCheckScript; 335 - User = pdsUser; 336 - Group = pdsGroup; 337 - 338 - NoNewPrivileges = true; 339 - ProtectSystem = "strict"; 340 - ProtectHome = true; 341 - }; 342 - 343 - startAt = "hourly"; 344 - }; 345 - 346 - systemd.timers.pds-healthcheck = { 347 - wantedBy = [ "timers.target" ]; 348 - timerConfig = { 349 - OnCalendar = "hourly"; 350 - Persistent = true; 351 - }; 352 - }; 353 - 354 - environment.systemPackages = with pkgs; [ 355 - litestream 356 - restoreScript 357 - awscli2 358 - ]; 359 65 }; 360 66 }