From ba86ae504d8ea9796e43c1b061aa070761cd1323 Mon Sep 17 00:00:00 2001
From: Gregor Kleen <gkleen@yggdrasil.li>
Date: Mon, 21 Nov 2022 18:58:56 +0100
Subject: pgbackrest

---
 hosts/surtr/postgresql/default.nix    | 239 ++++++++++++++++++++++++++++++++++
 hosts/surtr/postgresql/pgbackrest.crt |  13 ++
 hosts/surtr/postgresql/pgbackrest.key |  26 ++++
 3 files changed, 278 insertions(+)
 create mode 100644 hosts/surtr/postgresql/default.nix
 create mode 100644 hosts/surtr/postgresql/pgbackrest.crt
 create mode 100644 hosts/surtr/postgresql/pgbackrest.key

(limited to 'hosts/surtr/postgresql')

diff --git a/hosts/surtr/postgresql/default.nix b/hosts/surtr/postgresql/default.nix
new file mode 100644
index 00000000..9cf494ae
--- /dev/null
+++ b/hosts/surtr/postgresql/default.nix
@@ -0,0 +1,239 @@
+{ pkgs, sources, config, flake, ... }:
+let
+  versioning = sources.psql-versioning.src;
+in {
+  config = {
+    services.postgresql = {
+      enable = true;
+      package = pkgs.postgresql_14;
+    };
+
+    services.pgbackrest = {
+      enable = true;
+      settings = {
+        "surtr" = {
+          pg1-path = config.services.postgresql.dataDir;
+
+          repo1-path = "/var/lib/pgbackrest";
+          repo1-retention-full-type = "time";
+          repo1-retention-full = 7;
+          repo1-retention-archive = 2;
+
+          repo2-host-type = "tls";
+          repo2-host = "pgbackrest.vidhar.yggdrasil";
+          repo2-host-ca-file = toString ../../vidhar/pgbackrest/ca/ca.crt;
+          repo2-host-cert-file = toString ./pgbackrest.crt;
+          repo2-host-key-file = config.sops.secrets."pgbackrest.key".path;
+          repo2-retention-full-type = "time";
+          repo2-retention-full = 14;
+          repo2-retention-archive = 7;
+        };
+
+        "global" = {
+          compress-type = "zst";
+          compress-level = 9;
+
+          archive-async = true;
+          spool-path = "/var/spool/pgbackrest";
+        };
+
+        "global:server" = {
+          tls-server-address = "2a03:4000:52:ada:1::";
+          tls-server-ca-file = toString ../../vidhar/pgbackrest/ca/ca.crt;
+          tls-server-cert-file = toString ./pgbackrest.crt;
+          tls-server-key-file = config.sops.secrets."pgbackrest.key".path;
+          tls-server-auth = ["vidhar.yggdrasil=surtr"];
+        };
+
+        "global:archive-push" = {
+          process-max = 2;
+        };
+        "global:archive-get" = {
+          process-max = 2;
+        };
+      };
+
+      tlsServer.enable = true;
+
+      backups."surtr-daily" = {
+        stanza = "surtr";
+        repo = "1";
+        timerConfig.OnCalendar = "daily";
+      };
+    };
+
+    sops.secrets."pgbackrest.key" = {
+      format = "binary";
+      sopsFile = ./pgbackrest.key;
+      owner = "postgres";
+      group = "postgres";
+      mode = "0400";
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /var/lib/pgbackrest 0750 postgres postgres - -"
+      "d /var/spool/pgbackrest 0750 postgres postgres - -"
+    ];
+
+    systemd.services.migrate-postgresql = {
+      after = [ "postgresql.service" ];
+      bindsTo = [ "postgresql.service" ];
+      wantedBy = [ "postgresql.service" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        inherit (config.systemd.services.postgresql.serviceConfig) User Group;
+        RemainAfterExit = true;
+      };
+
+      path = [ config.services.postgresql.package ];
+      script = ''
+        psql postgres postgres -eXf ${pkgs.writeText "schema.sql" ''
+          CREATE DATABASE "matrix-synapse" WITH TEMPLATE "template0" ENCODING "UTF8" LOCALE "C";
+          CREATE DATABASE "email" WITH TEMPLATE "template0" ENCODING "UTF8" LOCALE "C";
+          CREATE DATABASE "etebase" WITH TEMPLATE "template0" ENCODING "UTF8" LOCALE "C";
+        ''}
+
+        psql matrix-synapse postgres -eXf ${pkgs.writeText "matrix-synapse.sql" ''
+          \i ${versioning + "/install.versioning.sql"}
+
+          BEGIN;
+          SELECT _v.register_patch('000-matrix-users', null, null);
+
+          CREATE USER "matrix-synapse";
+          GRANT ALL PRIVILEGES ON DATABASE "matrix-synapse" TO "matrix-synapse";
+          GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "matrix-synapse";
+          COMMIT;
+        ''}
+
+        psql email postgres -eXf ${pkgs.writeText "email.sql" ''
+          \i ${versioning + "/install.versioning.sql"}
+
+          BEGIN;
+          SELECT _v.register_patch('000-users', null, null);
+
+          CREATE USER "postfix";
+          GRANT CONNECT ON DATABASE "email" TO "postfix";
+          ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "postfix";
+          CREATE USER "dovecot2";
+          GRANT CONNECT ON DATABASE "email" TO "dovecot2";
+          ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "dovecot2";
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('001-spm', null, null);
+
+          CREATE USER "spm";
+          GRANT CONNECT ON DATABASE "email" TO "spm";
+          ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES to "spm";
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('000-base', null, null);
+
+          CREATE TABLE mailbox (
+            id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
+            mailbox text NOT NULL CONSTRAINT mailbox_non_empty CHECK (mailbox <> '''),
+            quota_bytes bigint CONSTRAINT quota_bytes_positive CHECK (CASE WHEN quota_bytes IS NOT NULL THEN quota_bytes > 0 ELSE true END),
+            CONSTRAINT mailbox_unique UNIQUE (mailbox)
+          );
+          CREATE TABLE mailbox_mapping (
+            id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
+            local text CONSTRAINT local_non_empty CHECK (local IS DISTINCT FROM '''),
+            domain text NOT NULL CONSTRAINT domain_non_empty CHECK (domain <> '''),
+            mailbox uuid REFERENCES mailbox(id),
+            CONSTRAINT local_domain_unique UNIQUE (local, domain)
+          );
+          CREATE UNIQUE INDEX domain_unique ON mailbox_mapping (domain) WHERE local IS NULL;
+
+          CREATE VIEW virtual_mailbox_domain (domain) AS SELECT DISTINCT domain FROM mailbox_mapping;
+          CREATE VIEW virtual_mailbox_mapping (mailbox, lookup) AS SELECT mailbox.mailbox as mailbox, (CASE WHEN local IS NULL THEN ''' ELSE local END) || '@' || domain AS lookup FROM mailbox_mapping INNER JOIN mailbox on mailbox.id = mailbox_mapping.mailbox;
+
+          CREATE VIEW imap_user ("user", quota_rule) AS SELECT mailbox AS "user", (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes END) AS quota_rule FROM mailbox;
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('001-lmtp-mapping', ARRAY['000-base'], null);
+
+          CREATE VIEW lmtp_mapping ("user", quota_rule, local, domain) AS SELECT mailbox.mailbox AS "user", (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes END) AS quota_rule, local, domain FROM mailbox INNER JOIN mailbox_mapping ON mailbox.id = mailbox_mapping.mailbox;
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('002-citext', ARRAY['000-base'], null);
+
+          DROP VIEW virtual_mailbox_domain;
+          DROP VIEW virtual_mailbox_mapping;
+          DROP VIEW imap_user;
+          DROP VIEW lmtp_mapping;
+
+          CREATE EXTENSION citext;
+
+          ALTER TABLE mailbox ALTER mailbox TYPE citext;
+          ALTER TABLE mailbox_mapping ALTER local TYPE citext;
+          ALTER TABLE mailbox_mapping ALTER domain TYPE citext;
+
+          CREATE VIEW mailbox_quota_rule (id, mailbox, quota_rule) AS SELECT id, mailbox, (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes END) AS quota_rule FROM mailbox;
+
+          CREATE VIEW virtual_mailbox_domain (domain) AS SELECT DISTINCT domain FROM mailbox_mapping;
+          CREATE VIEW virtual_mailbox_mapping (lookup) AS SELECT (CASE WHEN local IS NULL THEN ''' ELSE local END) || '@' || domain AS lookup FROM mailbox_mapping;
+          CREATE VIEW imap_user ("user", quota_rule) AS SELECT mailbox AS "user", quota_rule FROM mailbox_quota_rule;
+          CREATE VIEW lmtp_mapping ("user", quota_rule, local, domain) AS SELECT mailbox_quota_rule.mailbox AS "user", quota_rule, local, domain FROM mailbox_quota_rule INNER JOIN mailbox_mapping ON mailbox_quota_rule.id = mailbox_mapping.mailbox;
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('003-extensions', ARRAY['000-base', '002-citext'], null);
+
+          ALTER TABLE mailbox_mapping ADD COLUMN extension citext CHECK (CASE WHEN extension IS NOT NULL THEN extension NOT LIKE '%+%' ELSE true END);
+
+          DROP VIEW virtual_mailbox_mapping;
+          DROP VIEW lmtp_mapping;
+
+          CREATE VIEW virtual_mailbox_mapping (lookup) AS SELECT (CASE WHEN local IS NULL THEN ''' ELSE local END) || (CASE WHEN extension IS NULL THEN ''' ELSE '+' || extension END) || '@' || domain AS lookup FROM mailbox_mapping WHERE mailbox IS NOT NULL;
+          CREATE VIEW virtual_mailbox_access (lookup, action) AS SELECT (CASE WHEN local IS NULL THEN ''' ELSE local END) || (CASE WHEN extension IS NULL THEN ''' ELSE '+' || extension END) || '@' || domain AS lookup, CASE WHEN mailbox IS NULL THEN 'REJECT' ELSE 'DUNNO' END AS action FROM mailbox_mapping;
+          CREATE VIEW lmtp_mapping ("user", quota_rule, local, extension, domain) AS SELECT mailbox_quota_rule.mailbox AS "user", quota_rule, local, extension, domain FROM mailbox_quota_rule INNER JOIN mailbox_mapping ON mailbox_quota_rule.id = mailbox_mapping.mailbox;
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('004-cascade', ARRAY['000-base'], null);
+
+          ALTER TABLE mailbox_mapping DROP CONSTRAINT mailbox_mapping_mailbox_fkey;
+          ALTER TABLE mailbox_mapping ADD CONSTRAINT mailbox_mapping_mailbox_fkey FOREIGN KEY (mailbox) REFERENCES mailbox(id) ON DELETE CASCADE ON UPDATE RESTRICT;
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('005-spm', ARRAY['000-base', '002-citext', '003-extensions'], null);
+
+          GRANT INSERT ON "mailbox_mapping" TO "spm";
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('006-spm-mailbox', ARRAY['000-base'], null);
+
+          GRANT SELECT ON ALL TABLES IN SCHEMA public TO "spm";
+          COMMIT;
+
+          BEGIN;
+          SELECT _v.register_patch('007-ccert-sender-policy', ARRAY['000-base'], null);
+
+          CREATE USER "postfix-ccert-sender-policy";
+          GRANT CONNECT ON DATABASE "email" TO "postfix-ccert-sender-policy";
+          ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "postfix-ccert-sender-policy";
+          GRANT SELECT ON ALL TABLES IN SCHEMA public TO "postfix-ccert-sender-policy";
+          COMMIT;
+        ''}
+
+        psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" ''
+          \i ${versioning + "/install.versioning.sql"}
+
+          BEGIN;
+          SELECT _v.register_patch('000-user', null, null);
+
+          CREATE USER "etebase";
+          GRANT ALL PRIVILEGES ON DATABASE "etebase" TO "etebase";
+          GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "etebase";
+          COMMIT;
+        ''}
+      '';
+    };
+  };
+}
diff --git a/hosts/surtr/postgresql/pgbackrest.crt b/hosts/surtr/postgresql/pgbackrest.crt
new file mode 100644
index 00000000..b4dc4d97
--- /dev/null
+++ b/hosts/surtr/postgresql/pgbackrest.crt
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB7zCCAW+gAwIBAgIPQAAAAGN7p/Q5SZ7JU43JMAUGAytlcTAfMR0wGwYDVQQD
+DBRwZ2JhY2tyZXN0LnlnZ2RyYXNpbDAeFw0yMjExMjExNjI2MTFaFw0zMjExMjEx
+NjMxMTFaMBoxGDAWBgNVBAMMD3N1cnRyLnlnZ2RyYXNpbDAqMAUGAytlcAMhABIl
+okEGkov33jgsrF0QA4CKQILbIWkZ2tn+UUhXxxyDo4HGMIHDMB8GA1UdIwQYMBaA
+FO+/yfEkwcLr+vNPIsyCW86UwJ3aMB0GA1UdDgQWBBQnVeShLYsqF35OmmzLJEV5
+dfenhjAOBgNVHQ8BAf8EBAMCBeAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggr
+BgEFBQcDAgYIKwYBBQUHAwEwRAYDVR0RBD0wO4IdcGdiYWNrcmVzdC5zdXJ0ci55
+Z2dkcmFzaWwubGmCGnBnYmFja3Jlc3Quc3VydHIueWdnZHJhc2lsMAUGAytlcQNz
+AJqqMDWN1Ym5XANRKWcCh09j0Rej3V64XZlOOP7qFF9Gh4QJXeCvDMjX4LOeRUmi
+lB8iosdRN9MSANI4kfwYBnzgn3BNMrvMI4faEOuVnd6X2ulsJdNbJNQzB3hRVsNf
+b+QNBV+PpTUgR4k9e1XWX+wwAA==
+-----END CERTIFICATE-----
diff --git a/hosts/surtr/postgresql/pgbackrest.key b/hosts/surtr/postgresql/pgbackrest.key
new file mode 100644
index 00000000..bc2af12d
--- /dev/null
+++ b/hosts/surtr/postgresql/pgbackrest.key
@@ -0,0 +1,26 @@
+{
+	"data": "ENC[AES256_GCM,data:Bg4fIAqIGLF1P1P583vQnHhjzrD8fdnS5tA/7SuSdBRJjVaRzB0bieEv+2i9WxgaStG9TTUSmClCVUsbR5gy7MoV6Br4AL17Y++R6wPpJbQJvtMMDJB2xg+THU/Ex61dendcWqPYh73Wn4U9uBE/wC1eVrShXRM=,iv:YG/foZwVcrzi6hdk7Vk0sYZ92LMbmiKg1SbAgPaeUNM=,tag:lAcoxUfQXB4vvc6XnIcA/g==,type:str]",
+	"sops": {
+		"kms": null,
+		"gcp_kms": null,
+		"azure_kv": null,
+		"hc_vault": null,
+		"age": null,
+		"lastmodified": "2022-11-21T14:30:27Z",
+		"mac": "ENC[AES256_GCM,data:Dsfc1XrGl4abSnDqRl/IwC11bVy+kHz1RaI0V/nkkaJ3fM/qTXPVc5mMoWCiPn1nz5BTABQRSnrf79qHc0wpZ1WUpn07yOf7JejJ/T/bUC7D8BuoVdWRh1og+NzWCEIwaGXg0Eo04yli+GXisdM3YVM9g3BrxYrSInjnNZFyB+Q=,iv:T5QprwIhB8ZWwmmfWVtxkXqbMB1onW+wX7GPIFMn+z0=,tag:zMi77nMepajhg2Djgz8rBA==,type:str]",
+		"pgp": [
+			{
+				"created_at": "2022-11-21T14:30:27Z",
+				"enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdAi3pfg9DA+1v5r5sEijbkdwmOopWh05IuhRJxuy1btyAw\nuo0iV7VpngK8tFcBHnmhx3QsxIJo/gU+xrOwczW3RoSGrWo9tV2FantQPRp6f1aS\n0lwBEJSxmTApD/YDu3M6WhxN49/ZVEXG+KQ/mOdoBo0ITGKa6No0btMolzJ0bCJU\n+/avVdlDdZzfXo9XP0iJUoqh+1yMn+XdnD5deGac8a/QGvXZkxsYQ8KpK9sONA==\n=QyKr\n-----END PGP MESSAGE-----\n",
+				"fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51"
+			},
+			{
+				"created_at": "2022-11-21T14:30:27Z",
+				"enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DyFKFNkTVG5oSAQdAYU2U/anEJ8JSiG7NBppmsFeogXN3ynOEdq2tHXf+mUww\nIS7kW1pqcGMjnf7RQNuL91Wek5GEk4T498IFadiYDImAfIdS5jeX2w7UvxWLX5OZ\n0lwBlnxOwkYRWZzAhB6jHthmk2zEc+0JKuFolXhrwXqsFwFGoLTO9fctJrV7ry0u\naM9DqXru+/cEUZJDSq5GYDQaxTjyaFMVwLVdfxrtFwc8YMlqU8vVoWTqLaUVYA==\n=Tg80\n-----END PGP MESSAGE-----\n",
+				"fp": "7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8"
+			}
+		],
+		"unencrypted_suffix": "_unencrypted",
+		"version": "3.7.3"
+	}
+}
\ No newline at end of file
-- 
cgit v1.2.3