diff --git a/machines/atlas/configuration.nix b/machines/atlas/configuration.nix index 6b28f8d..76c32ca 100644 --- a/machines/atlas/configuration.nix +++ b/machines/atlas/configuration.nix @@ -5,6 +5,15 @@ users.users.root.openssh.authorizedKeys.keys = config.pim.ssh.keys.pim ++ config.pim.ssh.keys.niels; pim.k3s.serverAddr = "https://jefke.dmz:6443"; + pim.backups.borgBackups = { + freshrss = { + repo = "ssh://ty1l82m0@ty1l82m0.repo.borgbase.com/./repo"; + paths = ["/mnt/longhorn/persistent/volumes/freshrss"]; + deploymentName = "server"; + deploymentNamespace = "freshrss"; + }; + }; + deployment = { targetHost = "atlas"; targetUser = "root"; diff --git a/nixos/backups-ng.nix b/nixos/backups-ng.nix new file mode 100644 index 0000000..1cb100f --- /dev/null +++ b/nixos/backups-ng.nix @@ -0,0 +1,69 @@ +{ + lib, + config, + pkgs, + ... +}: let + borgBackupOpts = { + options = { + repo = lib.mkOption { + type = lib.types.str; + }; + paths = lib.mkOption { + type = with lib.types; listOf str; + }; + deploymentName = lib.mkOption { + type = lib.types.str; + }; + deploymentNamespace = lib.mkOption { + type = lib.types.str; + }; + replicaCount = lib.mkOption { + type = lib.types.int; + default = 1; + }; + }; + }; +in { + options.pim.backups = { + borgBackups = lib.mkOption { + type = with lib.types; attrsOf (submodule borgBackupOpts); + default = {}; + }; + }; + + # TODO: should have some timeout and alerting? + config = { + services.borgbackup.jobs = + lib.mapAttrs (_name: c: { + inherit (c) repo paths; + startAt = "*-*-* 00:00:00"; + # TODO: low benefit, but we could set borgbase's host keys here as they are published online. + environment.BORG_RSH = "ssh -i ${config.sops.secrets."borg/borgbasePrivateKey".path} -o StrictHostKeychecking=no"; + postHook = "${pkgs.k3s}/bin/kubectl scale deployment -n ${c.deploymentNamespace} ${c.deploymentName} --replicas=${toString c.replicaCount}"; + + prune.keep = { + within = "7d"; + weekly = 4; + monthly = 6; + }; + + preHook = '' + ${pkgs.k3s}/bin/kubectl scale deployment -n ${c.deploymentNamespace} ${c.deploymentName} --replicas=0 + + while [ -n "$(${pkgs.k3s}/bin/kubectl get deployment -n ${c.deploymentNamespace} ${c.deploymentName} -o jsonpath='{.status.replicas}')" ]; do + echo "Waiting for replicas to scale down to 0..." + sleep 2 + done + ''; + + encryption = { + passCommand = "cat ${config.sops.secrets."borg/borgPassphrase".path}"; + mode = "repokey-blake2"; + }; + }) + config.pim.backups.borgBackups; + + systemd.timers = lib.mapAttrs' (name: _c: lib.nameValuePair "borgbackup-job-${name}" {timerConfig.RandomizedDelaySec = "1h";}) config.pim.backups.borgBackups; + }; +} diff --git a/nixos/default.nix b/nixos/default.nix index 505c879..1665269 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -26,6 +26,7 @@ ./kubernetes ./data-sharing.nix ./backups.nix + ./backups-ng.nix ]; options = { diff --git a/nixos/server.nix b/nixos/server.nix index d13875b..282edb6 100644 --- a/nixos/server.nix +++ b/nixos/server.nix @@ -63,8 +63,10 @@ }; }; - sops.secrets."tailscale/authKey" = { - sopsFile = "${self}/secrets/servers.yaml"; + sops.secrets = { + "tailscale/authKey".sopsFile = "${self}/secrets/servers.yaml"; + "borg/borgPassphrase".sopsFile = "${self}/secrets/servers.yaml"; + "borg/borgbasePrivateKey".sopsFile = "${self}/secrets/servers.yaml"; }; }; } diff --git a/secrets/servers.yaml b/secrets/servers.yaml index 5c7d0b4..d947814 100644 --- a/secrets/servers.yaml +++ b/secrets/servers.yaml @@ -1,5 +1,8 @@ tailscale: authKey: ENC[AES256_GCM,data:3eXxQBY6AVqU4R1NlsyhGCfXW5wL58ODRH/f+zo5YFRad/ys1vB9JeKagq0SJSj/w4zxRAEpCf1o47Ypww==,iv:QklyIFuXlbH6cM/I0gqDH/Xeay9gqxqeyulQ7W/dbig=,tag:E/3UqtsfSVOi6otSlReO0Q==,type:str] +borg: + borgPassphrase: ENC[AES256_GCM,data:UWA2sBLPi63MRVOPTYPWYLujF2M=,iv:FQq/IsZK7LWo30gZc7oT2E9feCLn7Oeg6wDGuezkhu8=,tag:fWYaZUwJrM8x6cemXzz6xg==,type:str] + borgbasePrivateKey: ENC[AES256_GCM,data:O7eIY1yvbnBTS96tt5a8vcOEOzit4tEbIHmxnSbNsowC7YNk2g6MShQ6ll86GDiunLY33/Px0bqq9+4z/dk4N3FWQ1v5KQjr/gh+CS8VpIrv9zLv+Ru9UzeWQusbYxqnCu/IAQ1aB8UGV2LSCesQ0r/B6XEe51Phi65uWkbUYa/8voSiws3T3hnNrUDqiHdzfBgWZIQszz7zD92Q+aXu/kGNSxVKbXjWVfqBiyDEtuLEWoC1eENeEs41Ov5YT0Lm6+CUWadPqEwkDSvZWnBhoPwPLTZ/+ftZ/nizXUujsTdRwjcbOwJER+ObhgWDxbJE2WifxFOmHt3ggfSmAN842u5PjfT5gqpXMlTdCwYAYEJvDMqGsADe4p7+vPWJbetaahFrFGN8uBw7rs7W2wIiUKB5bAbAG0o6hdTpWfysuzMOFK/fROvCJsNhhKbbdiQbI0+SogtCkwv69+3uaRTFi33uqKCO6PQ6rMIahjo1lutm9iWq2nX4oI40W1KPC6EU/wF2,iv:rzkjjSnyrs58ZEO8XLsCSFsPHbtnL39SF6NJ6lUg3Ww=,tag:q0sunVc+9bLFoSdeykuT6g==,type:str] sops: kms: [] gcp_kms: [] @@ -69,8 +72,8 @@ sops: Y2cwK05uWXFhbndyRlhrSFNjYUlmZ1UKZ1vFRu1QhGGf7BIP8TxK2BIlMZlP3muA R3qLr1lEQmob4O0ilwn65nSCEd1/9W6dUWqeSlJ6CavjG59AvSHfIA== -----END AGE ENCRYPTED FILE----- - lastmodified: "2024-11-30T18:44:29Z" - mac: ENC[AES256_GCM,data:SG6a5pWa3gMaSz9d9fOchUXtXbRTpMOXmbOjZo5Fdx8Es1MEDwezwscQaj9p1dzmGa+7U8UUUzMYxlg2SmGgGdPgCs0a5RQVYvQFNdgpRiuknflFMcdgXLv7XFsTqsqSmbN0O662YDvCcz4DWRKjNCZAimlLym8pwDihj1D8dcU=,iv:JmCbcazDK2KPyYsoVy39sr4IbfiGfmGoopit5ojVADk=,tag:6tKYfMkJBjsThaa4qLqobw==,type:str] + lastmodified: "2025-05-25T14:53:30Z" + mac: ENC[AES256_GCM,data:jzjF+qjdptTI0Y1wNteZgYBGwF5dFWEBIFY3+k4Ty0YU/WB5AyUL6A8v0+PyoxoJK3pL+NAJEmLmAPFVh3+ExDlU9g3TAgpkOs7EsbJtWcjo8Ah08Hl8zoWqcMFcQhZ+aLnVKAE+tIBT4dWyV0AvOWmU8luvarsCp2tQ2OoBH20=,iv:PmbLg91onGz3kjxXMua/Thb904qDkWjHJcBY2dMAios=,tag:e0+fQqNysdiGvaodcimMVQ==,type:str] pgp: [] unencrypted_suffix: _unencrypted - version: 3.9.1 + version: 3.9.4