feat: add postgres

We've made a postgres module which can create databases, users, etc. in
an ergonomic way

Change-Id: Ib574c96b5639797fac373a6384c450e98b25ea46
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/727
Reviewed-by: Samuel Shuert <coded@clicks.codes>
Tested-by: Samuel Shuert <coded@clicks.codes>
diff --git a/lib/constants/default.nix b/lib/constants/default.nix
new file mode 100644
index 0000000..35e6034
--- /dev/null
+++ b/lib/constants/default.nix
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, inputs, ... }:
+{
+  constants = {
+    hosts = {
+      standard = "127.0.0.1";
+      clicks = "127.0.0.2";
+      caramels = "127.0.0.3";
+      coded = "127.0.0.4";
+      minion = "127.0.0.5";
+      pinea = "127.0.0.6";
+      generic = "127.0.0.255";
+    };
+  };
+}
diff --git a/modules/nixos/clicks/services/postgres/README.md b/modules/nixos/clicks/services/postgres/README.md
new file mode 100644
index 0000000..3efd637
--- /dev/null
+++ b/modules/nixos/clicks/services/postgres/README.md
@@ -0,0 +1,36 @@
+<!--
+SPDX-FileCopyrightText: 2024 Clicks Codes
+
+SPDX-License-Identifier: GPL-3.0-only
+-->
+
+# Clicks Postgres
+
+You can create a database, user and credentials by using `clicks.services.postgres.databases.<name>`. You should set this to a file containing the password for your database user.
+
+We recommend using our secrets module to create this password file.
+
+```nix
+clicks.services.postgres = {
+  enable = true;
+  databases.headscale = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.json}".paths.database_password;
+};
+```
+
+---
+
+If your password file needs a specific group to read, you can use `clicks.services.postgres.secretRequiredGroups` to allow postgres to read the file.
+
+```nix
+clicks.services.postgres.secretRequiredGroups = [ "headscale" ];
+```
+
+---
+
+If you're using a systemd service to start your application, and you're runinng on the same machine as postgres, you may additionally want to ensure that postgres is always
+started and stopped in sequence with your service.
+
+```nix
+systemd.services.headscale.requires = [ "postgresql.service" ];
+systemd.services.headscale.after = [ "postgresql.service" ];
+```
diff --git a/modules/nixos/clicks/services/postgres/default.nix b/modules/nixos/clicks/services/postgres/default.nix
new file mode 100644
index 0000000..0f6b71f
--- /dev/null
+++ b/modules/nixos/clicks/services/postgres/default.nix
@@ -0,0 +1,71 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, config, ... }:
+let
+  cfg = config.clicks.services.postgres;
+in
+{
+  options.clicks.services.postgres = {
+    enable = lib.mkEnableOption "Postgresql DB";
+
+    databases = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      description = "An attrset of databases to password files, these databases and users will be automatically created";
+      default = { };
+      example = {
+        headscale = "/run/secrets/headscale_db_password";
+      };
+    };
+
+    secretRequiredGroups = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      description = "A list of groups that the postgres superuser should be given to read the password file secrets";
+      default = [ ];
+      example = [ "headscale" ];
+    };
+  };
+
+  config =
+    let
+      databasesAsList = lib.attrsets.attrsToList cfg.databases;
+    in
+    lib.mkIf cfg.enable {
+      services.postgresql = {
+        enable = true;
+
+        settings = {
+          listen_addresses = lib.mkForce lib.clicks.constants.hosts.standard;
+          log_connections = true;
+          logging_collector = true;
+          log_disconnections = true;
+          log_destination = lib.mkForce "syslog";
+        };
+
+        ensureDatabases = lib.lists.forEach databasesAsList (database: database.name);
+        ensureUsers = lib.lists.forEach databasesAsList (database: {
+          name = database.name;
+          ensureDBOwnership = true;
+        });
+
+        #                 method database user address auth-method
+        authentication = "host   all      all  samenet scram-sha-256";
+      };
+
+      systemd.services.postgresql.restartTriggers = [ config.systemd.services.postgresql.postStart ];
+
+      systemd.services.postgresql.postStart = (
+        lib.pipe databasesAsList [
+          (map (database: ''
+            $PSQL -tAc "ALTER USER ${database.name} PASSWORD '$(cat ${database.value})';"
+          ''))
+          (lib.concatStringsSep "\n")
+        ]
+      );
+
+      users.users.${config.services.postgresql.superUser}.extraGroups = cfg.secretRequiredGroups;
+
+      clicks.storage.impermanence.persist.directories = [ "/var/lib/postgresql" ];
+    };
+}