diff --git a/machines/akhaten/stalwart.nix b/machines/akhaten/stalwart.nix index 8a5b288..62b7cf9 100644 --- a/machines/akhaten/stalwart.nix +++ b/machines/akhaten/stalwart.nix @@ -57,6 +57,8 @@ }; }; + services.backup.includes = [ "/var/lib/stalwart-mail/db" ]; + age.secrets.stalwart-admin-hash = { file = ../../secrets/stalwart-admin.age; path = "/var/lib/stalwart-mail/admin-hash"; diff --git a/machines/gustave/borg.nix b/machines/gustave/borg.nix new file mode 100644 index 0000000..97ac2c8 --- /dev/null +++ b/machines/gustave/borg.nix @@ -0,0 +1,16 @@ +{ pkgs, ... }: +{ + users.users.borg = { + home = "/home/borg"; + group = "borg"; + isNormalUser = true; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAziNbLEO9D69xUGPGEq3eXYauFuOlvhqQTwpLNKjFqs julien@tower" + ]; + + }; + users.groups.borg = { }; + + environment.systemPackages = with pkgs; [ borgbackup ]; + +} diff --git a/machines/gustave/default.nix b/machines/gustave/default.nix index 7de9cdb..78e2e73 100644 --- a/machines/gustave/default.nix +++ b/machines/gustave/default.nix @@ -9,6 +9,7 @@ ./hardware.nix ./home-julien.nix ./nsd.nix + ./borg.nix ]; machine.meta = { diff --git a/modules/backup/default.nix b/modules/backup/default.nix new file mode 100644 index 0000000..8b2a6a5 --- /dev/null +++ b/modules/backup/default.nix @@ -0,0 +1,135 @@ +{ + lib, + config, + ... +}: + +let + cfg = config.services.backup; + # We backup on gustave + host = "gustave.luj"; + port = "45"; + hostPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDJrHUzjPX0v2FX5gJALCjEJaUJ4sbfkv8CBWc6zm0Oe"; + sshKey = config.age.secrets."borg-ssh-key".path; + secretPath = config.age.secrets."borg-encryption-secret".path; + +in +{ + options.services.backup = + with lib; + with types; + { + quota = mkOption { + type = nullOr str; + default = null; + example = "90G"; + description = '' + Quota for the borg repository. Useful to prevent the target disk from running full and ensuring borg keeps some space to work with. + ''; + }; + + includes = mkOption { + type = listOf path; + default = [ ]; + description = '' + Paths to include in the backup. + ''; + }; + + excludes = mkOption { + type = listOf path; + default = [ ]; + description = '' + Paths to exclude in the backup. + ''; + }; + + preHook = mkOption { + type = lines; + default = ""; + description = '' + Shell commands to run before the backup. + ''; + }; + + postHook = mkOption { + type = lines; + default = ""; + description = '' + Shell commands to run after the backup. + ''; + }; + + wantedUnits = mkOption { + type = listOf str; + default = [ ]; + description = '' + List of units to require before starting the backup. + ''; + }; + }; + + config = lib.mkIf (cfg.includes != [ ]) { + + age.secrets."borg-ssh-key" = { + file = ../../secrets/borg-ssh-priv.age; + owner = "root"; + mode = "0600"; + }; + + age.secrets."borg-encryption-secret".file = ../../secrets/borg-encryption-secret.age; + + programs.ssh.knownHosts."${if port != 22 then "[${host}]:${port}" else host}" = { + publicKey = "${hostPublicKey}"; + }; + + systemd.services.borgbackup-job-state = { + wants = cfg.wantedUnits; + after = cfg.wantedUnits; + }; + + systemd.timers.borgbackup-job-state.timerConfig = { + # Spread all backups over the day + RandomizedDelaySec = "30m"; + FixedRandomDelay = true; + }; + + services.borgbackup.jobs.state = { + inherit (cfg) preHook postHook; + + # Create the repo + doInit = true; + + # Create daily backups, but prune to a reasonable amount + startAt = [ "hourly" ]; + prune.keep = { + daily = 7; + weekly = 4; + monthly = 3; + }; + + # What to backup + paths = cfg.includes; + exclude = cfg.excludes; + + # Where to backup it to + repo = "borg@gustave.luj:${config.networking.hostName}"; + environment.BORG_RSH = "ssh -p ${port} -i ${sshKey}"; + + # Ensure we don't fill up the destination disk + extraInitArgs = lib.optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}"; + + # Authenticated & encrypted, key resides in the repository + encryption = { + mode = "repokey-blake2"; + passCommand = "cat ${secretPath}"; + }; + + # Reduce the backup size + compression = "auto,zstd"; + + # Show summary detailing data usage once completed + extraCreateArgs = "--stats"; + }; + }; +} diff --git a/secrets/borg-encryption-secret.age b/secrets/borg-encryption-secret.age new file mode 100644 index 0000000..0bde513 --- /dev/null +++ b/secrets/borg-encryption-secret.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 81O5Zw WCIdHHk7nehDc0n0u4Df17oVi2OIg+WT2FxM9uUMBTM +ZufMHxTCazJycMSqHYKiGoRQUR+mPwXi/xsZQxIkjUM +-> ssh-ed25519 AqX2tg kYJ4UZixeb7WjkXzdtS6Bgm0JQBLNUdONJbjsTtkaQM +2H4qDAAlSJ3TNOF/UFOBlNnyAy1BclMvTv58fMJctuo +--- s3weoMhZdAdfJ4rTU5E3x56PhlM6N3JfbdJOtQFjACM +FC"a^C2b=bg$M A~T4` '.rPeC \ No newline at end of file diff --git a/secrets/borg-ssh-priv.age b/secrets/borg-ssh-priv.age new file mode 100644 index 0000000..42dacac Binary files /dev/null and b/secrets/borg-ssh-priv.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 25ee09c..f4e363a 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -81,4 +81,12 @@ in tower ]; "arkheon-token.age".publicKeys = servers; + "borg-ssh-priv.age".publicKeys = [ + akhaten + tower + ]; + "borg-encryption-secret.age".publicKeys = [ + akhaten + tower + ]; }