From b162b2024114040fd0d559642255ca899b149786 Mon Sep 17 00:00:00 2001 From: oddlama Date: Mon, 15 Jan 2024 03:28:53 +0100 Subject: [PATCH] feat: add vaultwarden restic backups --- flake.lock | 251 +++++++++++------- flake.nix | 2 +- hosts/sire/guests/samba.nix | 2 +- hosts/ward/guests/vaultwarden.nix | 37 +++ .../restic-encryption-password.age | 10 + .../ward-vaultwarden/restic-ssh-privkey.age | Bin 0 -> 786 bytes secrets/global.nix.age | Bin 1584 -> 1731 bytes 7 files changed, 200 insertions(+), 102 deletions(-) create mode 100644 secrets/generated/ward-vaultwarden/restic-encryption-password.age create mode 100644 secrets/generated/ward-vaultwarden/restic-ssh-privkey.age diff --git a/flake.lock b/flake.lock index 530481c..fc9230c 100644 --- a/flake.lock +++ b/flake.lock @@ -66,28 +66,6 @@ "type": "github" } }, - "alejandra": { - "inputs": { - "flakeCompat": "flakeCompat", - "nixpkgs": [ - "wired-notify", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1652974241, - "narHash": "sha256-0AolxQtKj3Oek0WSbODDpPVO5Ih8PXHOA3qXEKPB4dQ=", - "owner": "kamadorueda", - "repo": "alejandra", - "rev": "0be1462419fc73270a5dc0f84f8092603890b029", - "type": "github" - }, - "original": { - "owner": "kamadorueda", - "repo": "alejandra", - "type": "github" - } - }, "base16": { "inputs": { "fromYaml": "fromYaml" @@ -315,11 +293,11 @@ ] }, "locked": { - "lastModified": 1704648272, - "narHash": "sha256-zCDhWGl3bVpBKpDZ0p3NuGksZVg69BAChsY5W4KARL4=", + "lastModified": 1705240333, + "narHash": "sha256-s9h2h44fCi54sSIT9ktd3eDik9JDpQE9DeYuXcA44u4=", "owner": "numtide", "repo": "devshell", - "rev": "f54745fd4aae92443817ddc566ce06572b178b5a", + "rev": "ca1ff587c602b934afe830ea3cb26d0fbde4c395", "type": "github" }, "original": { @@ -357,11 +335,11 @@ ] }, "locked": { - "lastModified": 1704741201, - "narHash": "sha256-Y420NeqPWRSpxHpXsxhKILfTxT5exjtTgCgDwSpcEfU=", + "lastModified": 1705281959, + "narHash": "sha256-9NZiSMAduz4qbFu77Cg9RNFcrjgS9UOjriD+v8FeueY=", "owner": "nix-community", "repo": "disko", - "rev": "f0a3425a7b173701922e7959d8bfb136ef53aa54", + "rev": "2a561be6b5dd049182af1973bb7e28f7a0ac9be2", "type": "github" }, "original": { @@ -476,11 +454,11 @@ "flake-compat_6": { "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "owner": "edolstra", "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { @@ -534,6 +512,24 @@ "nixpkgs" ] }, + "locked": { + "lastModified": 1704982712, + "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "07f6395285469419cf9d078f59b5b49993198c00", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, "locked": { "lastModified": 1704152458, "narHash": "sha256-DS+dGw7SKygIWf9w4eNBUZsK+4Ug27NwEWmn2tnbycg=", @@ -638,19 +634,21 @@ "type": "github" } }, - "flakeCompat": { - "flake": false, + "flake-utils_6": { + "inputs": { + "systems": "systems_9" + }, "locked": { - "lastModified": 1648199409, - "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "numtide", + "repo": "flake-utils", "type": "github" } }, @@ -744,11 +742,11 @@ ] }, "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "lastModified": 1703887061, + "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", "type": "github" }, "original": { @@ -764,11 +762,11 @@ ] }, "locked": { - "lastModified": 1704809957, - "narHash": "sha256-Z8sBeoeeY2O+BNqh5C+4Z1h1F1wQ2mij7yPZ2GY397M=", + "lastModified": 1705269478, + "narHash": "sha256-j7Rp8Y3ckBHOlIzqe0g2+/BVce9SU/dVtn4Eb0rMuY4=", "owner": "nix-community", "repo": "home-manager", - "rev": "e13aa9e287b3365473e5897e3667ea80a899cdfb", + "rev": "846200eb574faa2af808ed02e653c2b8ed51fd71", "type": "github" }, "original": { @@ -785,11 +783,11 @@ ] }, "locked": { - "lastModified": 1704498488, - "narHash": "sha256-yINKdShHrtjdiJhov+q0s3Y3B830ujRoSbHduUNyKag=", + "lastModified": 1705104164, + "narHash": "sha256-pllCu3Hcm1wP/B0SUxgUXvHeEd4w8s2aVrEQRdIL1yo=", "owner": "nix-community", "repo": "home-manager", - "rev": "51e44a13acea71b36245e8bd8c7db53e0a3e61ee", + "rev": "0912d26b30332ae6a90e1b321ff88e80492127dd", "type": "github" }, "original": { @@ -856,11 +854,11 @@ "spectrum": "spectrum" }, "locked": { - "lastModified": 1704120350, - "narHash": "sha256-s5BOPAnVc4e/4WvGDeeF3VSLAWzBUB+YW6fJb3pFbRw=", + "lastModified": 1705263072, + "narHash": "sha256-DCqqaNWn9G81U+0Myyr36JrOKitcmS34oBWxqiHjabk=", "owner": "astro", "repo": "microvm.nix", - "rev": "d5553b1388f2947915c4cec6249b89474046573a", + "rev": "088ba565537eaef1041a87be5a44ca0daa4e1908", "type": "github" }, "original": { @@ -898,11 +896,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1703466376, - "narHash": "sha256-Wy8iF8u5KSzrTxg1hStTBmUjzzKdKyCyMOg8b/eTvVQ=", + "lastModified": 1705080892, + "narHash": "sha256-TLj334vRwFtSym3m+NnKcNCnKKPNoTC/TDZL40vmOso=", "owner": "nix-community", "repo": "nix-eval-jobs", - "rev": "64104a3c55593c903af78af86a4c9d2e5487a2d7", + "rev": "69371f7bae49d5d55bcee9fd829585148215bedb", "type": "github" }, "original": { @@ -940,11 +938,11 @@ ] }, "locked": { - "lastModified": 1704596958, - "narHash": "sha256-BK3Ohsz7m8X6qVKFxDtr8KVcHipfr5hYE9PDIJevHbQ=", + "lastModified": 1705282324, + "narHash": "sha256-LnURMA7yCM5t7et9O2+2YfGQh0FKAfE5GyahNDDzJVM=", "owner": "Mic92", "repo": "nix-index-database", - "rev": "f46800ac5a6e9f892fe36e50821c5d85794ecc62", + "rev": "49aaeecf41ae0a0944e2c627cb515bcde428a1d1", "type": "github" }, "original": { @@ -981,11 +979,11 @@ "pre-commit-hooks": "pre-commit-hooks_3" }, "locked": { - "lastModified": 1705279209, - "narHash": "sha256-Lfd9gpDcsF5EaBdHNVrSQtXqs1B7wx1lXiW4nKvxrQw=", + "lastModified": 1705283066, + "narHash": "sha256-uYvo7hr28saTQuzZ+t0v2dPAxfcVLs4WirMuFl/ykAA=", "owner": "oddlama", "repo": "nixos-extra-modules", - "rev": "a776d7c47663029588aec52fb7ac941fa8bbd8bd", + "rev": "cab2f4b0408cc072a8f9405daa542298b11ea87b", "type": "github" }, "original": { @@ -1017,11 +1015,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1704786394, - "narHash": "sha256-aJM0ln9fMGWw1+tjyl5JZWZ3ahxAA2gw2ZpZY/hkEMs=", + "lastModified": 1705187059, + "narHash": "sha256-dSj+iIYqLA+7/5rLXWfUxw9IXRm0w8Mrm39af8klUH0=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "b34a6075e9e298c4124e35c3ccaf2210c1f3a43b", + "rev": "ef811636cc847355688804593282078bac7758d4", "type": "github" }, "original": { @@ -1053,11 +1051,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1704722960, - "narHash": "sha256-mKGJ3sPsT6//s+Knglai5YflJUF2DGj7Ai6Ynopz0kI=", + "lastModified": 1705133751, + "narHash": "sha256-rCIsyE80jgiOU78gCWN3A0wE0tR2GI5nH6MlS+HaaSQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "317484b1ead87b9c1b8ac5261a8d2dd748a0492d", + "rev": "9b19f5e77dd906cb52dade0b7bd280339d2a1f3d", "type": "github" }, "original": { @@ -1082,6 +1080,24 @@ "type": "github" } }, + "nixpkgs-lib_2": { + "locked": { + "dir": "lib", + "lastModified": 1703961334, + "narHash": "sha256-M1mV/Cq+pgjk0rt6VxoyyD+O8cOUiai8t9Q6Yyq4noY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b0d36bd0a420ecee3bc916c91886caca87c894e9", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-stable": { "locked": { "lastModified": 1685801374, @@ -1132,16 +1148,16 @@ }, "nixpkgs-stable_4": { "locked": { - "lastModified": 1685801374, - "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.05", + "ref": "nixos-23.11", "repo": "nixpkgs", "type": "github" } @@ -1156,11 +1172,11 @@ ] }, "locked": { - "lastModified": 1704684968, - "narHash": "sha256-h+lSV/cfnfE5//dHefL154mgvaEmTz13ehI7eb/Hph0=", + "lastModified": 1705080950, + "narHash": "sha256-ltAQAwwE6UyUcVh6PIf+RYpuxvMSLgc7Dqwfox6HwPg=", "owner": "nix-community", "repo": "nixpkgs-wayland", - "rev": "17d7827cd61e7e6bdc732c4817ea4da26ab0b47b", + "rev": "8621ab0a5a9953c719aa21d3d078532613accdcb", "type": "github" }, "original": { @@ -1185,6 +1201,22 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixvim": { "inputs": { "flake-parts": "flake-parts_2", @@ -1198,11 +1230,11 @@ ] }, "locked": { - "lastModified": 1704812275, - "narHash": "sha256-uRe8BdZhuSiupXOxohaVP8LzJtBRG+ETP9PgzR60orI=", + "lastModified": 1705268857, + "narHash": "sha256-IMaCyPTp5Za0xVUorHRxq39VaUrEDuWA9MbV1z6eHR8=", "owner": "nix-community", "repo": "nixvim", - "rev": "b4ddb322889e2daf41333b4dbca2555da2e8bb7e", + "rev": "9e04eb3c3c6fcb6ea31e4d3633ea5fd7378906cb", "type": "github" }, "original": { @@ -1308,11 +1340,11 @@ "nixpkgs-stable": "nixpkgs-stable_4" }, "locked": { - "lastModified": 1704725188, - "narHash": "sha256-qq8NbkhRZF1vVYQFt1s8Mbgo8knj+83+QlL5LBnYGpI=", + "lastModified": 1705229514, + "narHash": "sha256-itILy0zimR/iyUGq5Dgg0fiW8plRDyxF153LWGsg3Cw=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "ea96f0c05924341c551a797aaba8126334c505d2", + "rev": "ffa9a5b90b0acfaa03b1533b83eaf5dead819a05", "type": "github" }, "original": { @@ -1373,6 +1405,25 @@ "type": "github" } }, + "rust-overlay_2": { + "inputs": { + "flake-utils": "flake-utils_6", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1702865809, + "narHash": "sha256-K7caQe+KqjqTBFmJawmBjmm25S6bza5CXhAqbXFLyH8=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "b2aafcee4a8842cecfc877ff7dd271f333dc0fa8", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, "spectrum": { "flake": false, "locked": { @@ -1541,6 +1592,21 @@ "type": "github" } }, + "systems_9": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "templates": { "locked": { "lastModified": 1704737624, @@ -1578,35 +1644,20 @@ "type": "github" } }, - "utils": { - "locked": { - "lastModified": 1652776076, - "narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "wired-notify": { "inputs": { - "alejandra": "alejandra", + "flake-parts": "flake-parts_3", "nixpkgs": [ "nixpkgs" ], - "utils": "utils" + "rust-overlay": "rust-overlay_2" }, "locked": { - "lastModified": 1699064982, - "narHash": "sha256-BAKfy2O0Df1JdNWn2rPsrjbIyOdGjJZeGxXZkvMZzvU=", + "lastModified": 1705141537, + "narHash": "sha256-CjcrCvhrtiQIozs7Ns6yWpcw5eOozjZ3XK1PU2pO/Y0=", "owner": "Toqozz", "repo": "wired-notify", - "rev": "9f2e1420e122030953734f795eaf8cf000002abd", + "rev": "f0bca119f7914142e3bef1e019511f8ea7681fd4", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 0ea19ca..639f5c2 100644 --- a/flake.nix +++ b/flake.nix @@ -160,7 +160,7 @@ ; } // flake-utils.lib.eachDefaultSystem (system: rec { - apps.setupHetznerStorageBoxes = import (nixos-extra-modules + "/apps/setup-hetzner-storage-box.nix") { + apps.setupHetznerStorageBoxes = import (nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix") { inherit pkgs; nixosConfigurations = self.nodes; decryptIdentity = builtins.head self.secretsConfig.masterIdentities; diff --git a/hosts/sire/guests/samba.nix b/hosts/sire/guests/samba.nix index 0c2c601..55216be 100644 --- a/hosts/sire/guests/samba.nix +++ b/hosts/sire/guests/samba.nix @@ -223,7 +223,7 @@ in { enable = true; inherit (box) mainUser; inherit (box.users.samba) subUid path; - sshPrivateKeyFile = config.age.secrets.restic-ssh-privkey.rekeyFile; + sshAgeSecret = "restic-ssh-privkey"; }; user = "root"; diff --git a/hosts/ward/guests/vaultwarden.nix b/hosts/ward/guests/vaultwarden.nix index 5c21c95..ffa6249 100644 --- a/hosts/ward/guests/vaultwarden.nix +++ b/hosts/ward/guests/vaultwarden.nix @@ -51,6 +51,10 @@ in { services.vaultwarden = { enable = true; dbBackend = "sqlite"; + # WARN: Careful! The backup script does not remove files in the backup location + # if they were removed in the original location! Therefore, we use a directory + # that is not persisted and thus clean on every reboot. + backupDir = "/var/cache/vaultwarden-backup"; config = { dataFolder = lib.mkForce "/var/lib/vaultwarden"; extendedLogging = true; @@ -80,4 +84,37 @@ in { StateDirectory = lib.mkForce "vaultwarden"; RestartSec = "600"; # Retry every 10 minutes }; + + # Backups + # ======================================================================== + + age.secrets.restic-encryption-password.generator.script = "alnum"; + age.secrets.restic-ssh-privkey.generator.script = "ssh-ed25519"; + + services.restic.backups.main = { + hetznerStorageBox = let + box = config.repo.secrets.global.hetzner.storageboxes.dusk; + in { + enable = true; + inherit (box) mainUser; + inherit (box.users.vaultwarden) subUid path; + sshAgeSecret = "restic-ssh-privkey"; + }; + + user = "root"; + timerConfig = { + OnCalendar = "06:15"; + RandomizedDelaySec = "3h"; + Persistent = true; + }; + initialize = true; + passwordFile = config.age.secrets.restic-encryption-password.path; + paths = [config.services.vaultwarden.backupDir]; + pruneOpts = [ + "--keep-daily 14" + "--keep-weekly 7" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; } diff --git a/secrets/generated/ward-vaultwarden/restic-encryption-password.age b/secrets/generated/ward-vaultwarden/restic-encryption-password.age new file mode 100644 index 0000000..f68aa4c --- /dev/null +++ b/secrets/generated/ward-vaultwarden/restic-encryption-password.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> X25519 cyCP17wFsTKBZv0uAeHIXk3xiMUCzwDQ5O4ObRpmcQg +uaLCoFo4qjgr2hocVFsbAUEf0OK8hZCVYjSlXOA1mCQ +-> piv-p256 xqSe8Q AvJBo83Vngkv1Y0czPaAj0DoHRmhPF2Yq1AhQB+Ztvq8 +leN6QRaenTmq+1sUF64cx7I6EGfuWjW5kO9gS4D1htE +-> k,-grease +8bQYuOSk3TkNV7ViXmhag/8+lZch6Q +--- pWjIBQehKUCNox8IGv0TM1schPlj0q40nthpCqnWz2I +ƓsZ]z6l wCAjT 4UOٻ# +d)1yk<4 ><lKPzf \ No newline at end of file diff --git a/secrets/generated/ward-vaultwarden/restic-ssh-privkey.age b/secrets/generated/ward-vaultwarden/restic-ssh-privkey.age new file mode 100644 index 0000000000000000000000000000000000000000..aa719202730e137db7f05297e10d684a83e7216b GIT binary patch literal 786 zcmV+t1MU1_XJsvAZewzJaCB*JZZ2&?EHEL5zaB4b7deUAV_poPfB@b3Pv|& zF>Y;ZOG9c_Mm0-!ZbxisX--Zs3N0-yAVE(>T3U8zGE6utMM^|!Y*%$;V?|nXG%rd| zZEbi=X>d?sQ*=sNSVclG3Nb)j8phUj#nCMjW^>8pzdP*{iW#Q|nzm#fzmJLEu;lGN ziF0*?3f>zS`9IGO$?thjmo_mDT{)N}Jv~n-VfUWjCTC(G1&=Hl;(cA1iEKaq;qUXRk?E zu-3Qf>#y`tlIh^9a-mgbWm*^tJz9pH4~vnm1zW>YWfBi{mlxNgA{M>ZMb~vWm-p>O ztS%9f+!=sKMi2QgJS7Sp-~X%w7Wbt!l2LsaqIcX|{VCiPlw;D5rx9g{%P$-V7x)sO zUwB`|LLw|a^MC<%f+*jCdH?dhE2Fx2SJq+a38X)@TToh@HkU5Lsqpn{HA+zz9y+{6 z%t@o!CmWs(s}|m`<9?&<0QvYsv>Dqvw*TmTYm7=KRz|YC5Dly>mNixq`@JhOjH?_> zRY3uoTl;I61Ee$)m*9Lcqr{$wiLgm?NUDe*re(ab9YJ5|xJ3!8hnm=O=ueuL!b}2~ z$(B3e>!&GDy+=;%K40$I zPG(v}YYJ^?X>UhyZd6ujWk@+{R8V+GVmLW*L32b_Q#mnMayfTOHZN;MP*X1}YYHts zAaH4REpRe5HXwL$Q)M_&AVG65RZvD~Q&dcNOf+sdQD$U$bbnz;c~&+rY-DIgWL7Xj zP&P1jbTxQRV@V2AaBpjAFf(X%WlcFraA-j>SaxGIb!{m9R#$SMdzv)UJy zp}9rw$tsOJ(;H*i@3Sa_#}*tBtmPeTuY%JPiBmS1t$#OUo5M%mwL~d%@#7?JbCgsr z5Vteuh>I9p{*PS>S3b)fGyPUc=(}4vRQ=Y_EpIk(^Z~6@hMW9$PV-@Z5n4X}G%AJc zQ419syHA5W3Y}p%4GsXWURKQ{lTlk?@U;MAW;{T2bk>xi?yAk1*Glp%5c~M61+&!I z?2Yepg@3k;BBwGR2f;SNXG2;R?sQqBONX319__JVwb+~4m5jZ9^CgZ9 z>4j}MDvQUfmyy@3Cs*Q+SsVh#1I1@mf%@n0_MF5H@_96JeE10e7jbtgKL@O3Vyj{-PDrgA$K2s#1YXYbA3oYZRs*Sc>BHF(LDiLGDDq9-e z^p#iX1KF^2m7&rcYwlZ+5pLr0$WW+Qyqr9s(#6#4UZKH7#Sgd_l`C~FP=9)>8la?; zT_tE8!Sys2o$B>OU?y3N;*7!=bL;P@S7}bUjWRvI23A_k-46aPY|^*CfnuFY4ur+i zYnpU#I47nvU*57n;wGc^2|g*dI*IK zPU=!m7|m4~rq;At5_x~eCVv#3pK9!P(zzDDW6m#`30iO5clX8Ey7S}vQbli&0DwVL zG@p@Sw4%cLtco9vN=~M>%gS9bb#y%q$KZ}m7FYn|Gm~NBh(^^_gq@ZRcBSn{z>;{j z#KGwMPMrz%E(R9KottzNWylp6T9F^?U!LANSyXS3TE))$DSP+^q<n(EaLgjnL8lsrNeo?x{I_ys|!Df?U(m=l^bhC7rec z@Dw{t6tcm5F;^VYcl#PPTPMYZz9bU8j{T0q5jU@kR&l*I N&ak5~Ks&JZnnJb~9wz_* delta 1571 zcmV+;2Hg3>4X_N5Ab(h6WKT0sYh+hbT4!!zHd!)QOKDV3R5(;uXmxfsHbyUWN>+Jo zRZ4AlYYI|naBOT%abq!1VoqZ+FhV&+Sz2ynSXfPLIA~~XHD)OmZ`ESxrSUPik0kVr@xv zL2C*vJ|Jc-XL4m>b7dfG3TZh}K?*G`Eg)rhSz=OUYIS5%F=9wqYD#WFRYpNLc6U{F zF>5(EGHrEDdVg|HbxCP?Zb1qPcA*#zc=7Qn{&~mX)Z^+8o(Eq295?1;)F_{b6M<3P z^V-R>ZB)j}&144yi3UwZOim9k#b^dMv&cjTtW%YD@9;gCJMAyaB%bz9G{PReij>6u z5KVCnafn#5tnh$mN|xe%Q`zWS$k+f(e4!Es(YzyP!GCxIB*;|7p$d?+%p3py6Z)=y&$i{lOu@PfBlS6T{ z?@fie+C_qbeP(whF8N3_d&nBsRrze@yszIXL_{ObzpEVFnwKU2z$SCX37Zpsu|IZ! z>?v2$CVvk$M`5Yz%D(c1rrO%>4QM9bcWz-!4mxE>H@WjWr2Q2K zQJFkjhw%(PeU}YcAC}g3T-e?uqz+>o`jtW+qO2~C)S{x!7uMKPI5MbBuh(ts=|8_S zZE?8_2G5PLd0jkbK%=A*GP&sG!AV~W`k0fz*niD7(RT69){<^BfoK0Kb+tC3c#Trf zipk|uD}hh}noi}K5+}S`rXL-?Q5_}Biiu$;RI-c@OC>F`bn}kHTRWQI%lrH9x#;V7 z3%q}`qb0Yt6(jDM7DpI8R9RwwZsx1d1{ZT}vo|9y55Zm-4UilrWu+O&9-*fIAQO(^ z4}ayr&Mb|~G_m=Vog)wT!|U5SFm};rrl2P@EP0X|RBn6Msgiu#DVImFm2=9jZmV;^0dX0D!Ny|Kmy6 zv1rJ7EF0ogn;Bp#!hcUqqbT&^zaq?&hB2|Xb2PkEsowE#0M#O+x`_qkBifhe-uiY0 z2Qo;NvKL8-?i=O35S8eVXUZUtyYsto04M>l{wYY#IkF3~gZ*D`Q@z4=>#^YLW%p@j*{;raJxd;Sp!*)>7 z&(Me^K(t+n5~A|GzSF(cWV8eAi>=Dn^wFP_{M%_Bgj5&rcG@f{h{_c`a z0h5ol{yNYmuGO45l;N2dGNq%^`m1&qe8W;ly4uDYT^SKepJ+3k3Om!lMTu>s4Rak+ zG;RMF=1pcqK9q8>>i`M_Bs?L1bdNguS~B=o0Z#9>Zt?kRIsLGoh8(c52i-T