diff --git a/flake.nix b/flake.nix index c381540..96cf412 100644 --- a/flake.nix +++ b/flake.nix @@ -160,6 +160,8 @@ ; } // flake-utils.lib.eachDefaultSystem (system: rec { + apps.setupHetznerStorageBoxes = import ./nix/setup-hetzner-storage-boxes.nix self; + pkgs = import nixpkgs { inherit system; config.allowUnfree = true; diff --git a/hosts/sire/fs.nix b/hosts/sire/fs.nix index bce561b..e6d0f7b 100644 --- a/hosts/sire/fs.nix +++ b/hosts/sire/fs.nix @@ -80,6 +80,7 @@ in { "rpool/local/state<" = true; "rpool/safe<" = true; "storage/safe<" = true; + "storage/bunker<" = true; }; snapshotting = { type = "periodic"; diff --git a/hosts/sire/guests/samba.nix b/hosts/sire/guests/samba.nix index 750cf03..1e9c9c7 100644 --- a/hosts/sire/guests/samba.nix +++ b/hosts/sire/guests/samba.nix @@ -209,4 +209,37 @@ in { ]; users.groups = lib.mapAttrs (_: cfg: {gid = cfg.id;}) (smbUsers // smbGroups); + + # 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.samba) subUid path; + sshPrivateKeyFile = config.age.secrets.restic-ssh-privkey.path; + }; + + user = "root"; + timerConfig = { + OnCalendar = "06:15"; + RandomizedDelaySec = "3h"; + Persistent = true; + }; + initialize = true; + passwordFile = config.age.secrets.restic-encryption-password.path; + paths = ["/bunker"]; + pruneOpts = [ + "--keep-daily 14" + "--keep-weekly 7" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; } diff --git a/modules/default.nix b/modules/default.nix index 337663b..d8c96bb 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -34,6 +34,7 @@ ./oauth2-proxy.nix ./promtail.nix ./provided-domains.nix + ./restic.nix ./secrets.nix ./telegraf.nix ./wireguard-proxy.nix diff --git a/modules/restic.nix b/modules/restic.nix new file mode 100644 index 0000000..ab1697d --- /dev/null +++ b/modules/restic.nix @@ -0,0 +1,57 @@ +{lib, ...}: let + inherit + (lib) + mkEnableOption + mkIf + mkOption + types + ; +in { + options.services.restic.backups = { + type = types.attrsOf (types.submodule ({config}: { + options.hetznerStorageBox = { + enable = mkEnableOption "Automatically configure this backup to use the given hetzner storage box. Will use SFTP via SSH."; + + mainUser = mkOption { + type = types.str; + description = '' + The main user. While not technically required for restic, we still use it to + derive the subuser name and it is required for the automatic setup script + that creates the users. + ''; + }; + + subUid = mkOption { + type = types.int; + description = "The id of the subuser that was allocated on the hetzner server for this backup."; + }; + + path = mkOption { + type = types.str; + description = '' + The remote path to backup into. While not technically required for restic + (since the subuser is chrooted on the remote), we'll still use it to set + a sane repository and it is required for the automatic setup script that + creates the users. + ''; + }; + + sshPrivateKeyFile = { + type = types.path; + description = "The path to the ssh private key to use for uploading backups. Don't use a path from the nix store!"; + }; + }; + + config = let + subUser = "${config.hetznerStorageBox.mainUser}-sub${toString config.hetznerStorageBox.subUid}"; + url = "${subUser}@${subUser}.your-storagebox.de"; + in + mkIf config.hetznerStorageBox.enable { + repository = "sftp://${url}:23${config.hetznerStorageBox.path}"; + extraOptions = [ + "sftp.command='ssh -s sftp -p 23 -i ${config.hetznerStorageBox.sshPrivateKeyFile} ${url}'" + ]; + }; + })); + }; +} diff --git a/nix/setup-hetzner-storage-boxes.nix b/nix/setup-hetzner-storage-boxes.nix new file mode 100644 index 0000000..c39f4cd --- /dev/null +++ b/nix/setup-hetzner-storage-boxes.nix @@ -0,0 +1,12 @@ +self: system: let + pkgs = self.pkgs.${system}; +in { + type = "app"; + drv = pkgs.writeShellApplication { + name = "setup-hetzner-storage-boxes"; + text = '' + set -euo pipefail + + ''; + }; +} diff --git a/secrets/global.nix.age b/secrets/global.nix.age index 309acb0..44a862f 100644 Binary files a/secrets/global.nix.age and b/secrets/global.nix.age differ