From 3994f21100586bfeb272d84d28448bd54ce76764 Mon Sep 17 00:00:00 2001 From: oddlama Date: Sun, 4 Aug 2024 20:39:06 +0200 Subject: [PATCH] feat: enable storage box backups for mail --- hosts/envoy/idmail.nix | 1 + hosts/envoy/stalwart-mail.nix | 82 +++++++++++------- nix/storage-box.nix | 8 +- .../envoy/restic-encryption-password.age | 9 ++ .../generated/envoy/restic-ssh-privkey.age | Bin 0 -> 797 bytes secrets/global.nix.age | Bin 3234 -> 3289 bytes ...64c4dd995fa-restic-encryption-password.age | 10 +++ ...0c4d58a856518f5774f-restic-ssh-privkey.age | Bin 0 -> 776 bytes 8 files changed, 71 insertions(+), 39 deletions(-) create mode 100644 secrets/generated/envoy/restic-encryption-password.age create mode 100644 secrets/generated/envoy/restic-ssh-privkey.age create mode 100644 secrets/rekeyed/envoy/9dc0c843d69f02a6b0c3b64c4dd995fa-restic-encryption-password.age create mode 100644 secrets/rekeyed/envoy/db9c11543e8500c4d58a856518f5774f-restic-ssh-privkey.age diff --git a/hosts/envoy/idmail.nix b/hosts/envoy/idmail.nix index ff8d5ce..616f1e7 100644 --- a/hosts/envoy/idmail.nix +++ b/hosts/envoy/idmail.nix @@ -66,6 +66,7 @@ in { owner = "admin"; } ); + # XXX: create mailboxes for git@ vaultwarden@ and simultaneously alias them to the catch all for a send only mail. }; }; systemd.services.idmail.serviceConfig.RestartSec = "60"; # Retry every minute diff --git a/hosts/envoy/stalwart-mail.nix b/hosts/envoy/stalwart-mail.nix index 0003911..8c3a032 100644 --- a/hosts/envoy/stalwart-mail.nix +++ b/hosts/envoy/stalwart-mail.nix @@ -8,6 +8,7 @@ primaryDomain = globals.mail.primary; stalwartDomain = "mail.${primaryDomain}"; dataDir = "/var/lib/stalwart-mail"; + mailBackupDir = "/var/cache/mail-backup"; in { environment.persistence."/persist".directories = [ { @@ -193,7 +194,7 @@ in { -- Finally, select any catch_all address that would catch this. -- Again make sure everything is active. UNION - SELECT d.catch_all, 3 AS rowOrder AS name + SELECT d.catch_all AS name, 3 AS rowOrder FROM domains AS d JOIN mailboxes AS m ON d.catch_all = m.address JOIN users AS u ON m.owner = u.username @@ -567,36 +568,51 @@ in { }; }; - # systemd.services.stalwart-backup = { - # description = "Stalwart and idmail backup"; - # serviceConfig = { - # ExecStart = "${config.services.paperless.package}/bin/paperless-ngx document_exporter -na -nt -f -d ${stalwartBackupDir}"; - # ReadWritePaths = [ - # dataDir - # config.services.idmail.dataDir - # stalwartBackupDir - # ]; - # Restart = "no"; - # Type = "oneshot"; - # }; - # inherit (cfg) environment; - # requiredBy = ["restic-backups-storage-box-dusk.service"]; - # before = ["restic-backups-storage-box-dusk.service"]; - # }; - # - # # Needed so we don't run out of tmpfs space for large backups. - # # Technically this could be cleared each boot but whatever. - # environment.persistence."/state".directories = [ - # { - # directory = stalwartBackupDir; - # user = "stalwart-mail"; - # group = "stalwart-mail"; - # mode = "0700"; - # } - # ]; - # - # backups.storageBoxes.dusk = { - # subuser = "stalwart"; - # paths = [stalwartBackupDir]; - # }; + systemd.services.backup-mail = { + description = "Mail backup"; + environment = { + STALWART_DATA = dataDir; + IDMAIL_DATA = config.services.idmail.dataDir; + BACKUP_DIR = mailBackupDir; + }; + serviceConfig = { + SyslogIdentifier = "backup-mail"; + Type = "oneshot"; + User = "stalwart-mail"; + Group = "stalwart-mail"; + ExecStart = lib.getExe (pkgs.writeShellApplication { + name = "backup-mail"; + runtimeInputs = [pkgs.sqlite]; + text = '' + sqlite3 "$STALWART_DATA/database.sqlite3" ".backup '$BACKUP_DIR/database.sqlite3'" + sqlite3 "$IDMAIL_DATA/database.sqlite3" ".backup '$BACKUP_DIR/idmail.db'" + cp -r "$STALWART_DATA/dkim" "$BACKUP_DIR/" + ''; + }); + ReadWritePaths = [ + dataDir + config.services.idmail.dataDir + mailBackupDir + ]; + Restart = "no"; + }; + requiredBy = ["restic-backups-storage-box-dusk.service"]; + before = ["restic-backups-storage-box-dusk.service"]; + }; + + # Needed so we don't run out of tmpfs space for large backups. + # Technically this could be cleared each boot but whatever. + environment.persistence."/state".directories = [ + { + directory = mailBackupDir; + user = "stalwart-mail"; + group = "stalwart-mail"; + mode = "0700"; + } + ]; + + backups.storageBoxes.dusk = { + subuser = "stalwart"; + paths = [mailBackupDir]; + }; } diff --git a/nix/storage-box.nix b/nix/storage-box.nix index c195684..e1be2fd 100644 --- a/nix/storage-box.nix +++ b/nix/storage-box.nix @@ -1,13 +1,9 @@ {inputs, ...}: { - perSystem = { - config, - pkgs, - ... - }: { + perSystem = {pkgs, ...}: { apps.setupHetznerStorageBoxes = import (inputs.nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix") { inherit pkgs; nixosConfigurations = inputs.self.nodes; - decryptIdentity = builtins.head config.secretsConfig.masterIdentities; + decryptIdentity = builtins.head inputs.self.secretsConfig.masterIdentities; }; }; } diff --git a/secrets/generated/envoy/restic-encryption-password.age b/secrets/generated/envoy/restic-encryption-password.age new file mode 100644 index 0000000..68f8d68 --- /dev/null +++ b/secrets/generated/envoy/restic-encryption-password.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> X25519 OH3vohVD+wOgaXnBXkPmISX/rKOHw9vAkHhvbKNc5SI +7iJoL/5LbcmBL+9F8OP/H4DLz3OsQO2/Jo7/SGIMPeQ +-> piv-p256 xqSe8Q AtlmXfAqQRosyLlV5EHnGsDkvqA9ng1IxD1F4uMldAkt +I53dck5u2DJ3WpHZEqbO5tJRLhXCzTpjx+YOGunZU1M +-> 7@H=O~_X-grease Dn +Gs1wrPIA9dG0rm00/+DBzC0 +--- CipW/4FDMF+bWtWTNLatWXqYLV8MThrR26cWHDrKT0k +jHq>/R:etԋ3F!{ĺ7@ϯVٙܫX>չ!)˜,ފ"(ScO_>' \ No newline at end of file diff --git a/secrets/generated/envoy/restic-ssh-privkey.age b/secrets/generated/envoy/restic-ssh-privkey.age new file mode 100644 index 0000000000000000000000000000000000000000..f7807e59a4882dacbd8ea968581ecff6337d7dd0 GIT binary patch literal 797 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 X?B$u9eUa7{bM-pc^0sL!&V`#X@aiT7 literal 3234 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 U3sNPg4Ox-NQe`%9m3vRO)hK`GX#fBK diff --git a/secrets/rekeyed/envoy/9dc0c843d69f02a6b0c3b64c4dd995fa-restic-encryption-password.age b/secrets/rekeyed/envoy/9dc0c843d69f02a6b0c3b64c4dd995fa-restic-encryption-password.age new file mode 100644 index 0000000..cb43cc7 --- /dev/null +++ b/secrets/rekeyed/envoy/9dc0c843d69f02a6b0c3b64c4dd995fa-restic-encryption-password.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 rz300w 2nOYeoxR+f268mg+ZdrWkOnbZ4Kt8wo2LqKDxn/yHl4 +et62QuU5pCxgSBGnr3y4QqSbZ2JtIPa8VSNkWRE/OFs +-> 9tGrxZ)-grease ucb=Pw 6CW&' YK?& ^ ++Ge5Vl7GgagHYuLisS63vR3TQtzneq+AppcSadnCRs9ZwzpyRH2/m9xn3eftbliv +6kWgXJS4iOxUDJS5FpZWsC1krmwWgmNdLtYoGEaJK9nmAS2CcrCQ38CLeQ +--- bwtbv70YcG3bkzeV8ldrdzjbC6YOVvl9Flqs2J8YElo +x:Hq` +sAZy1r;ZZqvB]YM G=M"H7dޱJ*mT +:WxZ \ No newline at end of file diff --git a/secrets/rekeyed/envoy/db9c11543e8500c4d58a856518f5774f-restic-ssh-privkey.age b/secrets/rekeyed/envoy/db9c11543e8500c4d58a856518f5774f-restic-ssh-privkey.age new file mode 100644 index 0000000000000000000000000000000000000000..1d3fb5e6a63d50c6d8425d7b0f6eb9cf83096022 GIT binary patch literal 776 zcmV+j1NZ!4XJsvAZewzJaCB*JZZ2bICy9;Raj?Na8YhePE%HHD>zteNi#J~aA<9CG%;msO;cz=Q)3EsRCaPpVn}Rp zY)DWtIW=fPF*8pqYIS5yT3AL}d02H#Qgl>VYfV~OO?L_{J|Hc8I#4ZVa%Ew2Wgu-w zY#?qcAS^&oKSLlWS3^5kFLh`NGEsOpXk>C%D{n<{Q$a&&KIb#!twYjag~Rbpi~ za5!OiWNL6jF>qFMT4;J%PBMCRZ(%S{L^o9xS5+%o zZ%%ktdPYQLM{H6sbT>6=D|c^WFj#jAEiEk|cVTQ{ax!#Ic4lHLFH}TTOfO6?WJ6g@)9TZ!7EHt8b6ADz6>8-p1@J>^a6?qTdIv@m8#UIw% zu5z54!_+h|f`meTGXu+b1SJzs+q=d;dc!AfD+15~>4NT`QU zkgoe*z(sxhe2pXzXX1o%(@yMMqQxMhX!FAi0*Op}?2uz3VYVWkFxMXNmLAB4`CAR( zo&iH>a3a<{uM0~jHJFyR-}tb^P)My zUg;CrR617gilV;X3oZ&geKhg)!IIJqcydwxqqU9!nG)* z04Q|j;%Dyy&Hh_(tO`kC^J8@VA9Fm}^fBHi?qes~KUmn%9z(6+v}&9R5o+$1LY74p G48T37T1XiH literal 0 HcmV?d00001