Update to NixOS 23.11

Most of the release notes we have the luxury of not caring about, however
for some we needed to make changes

- postgresql ensurePermissions was deprecated. We have replaced it with a
  combination of the new ensureDBOwner, and manual permissions grants
  where that is not applicable
- fetchPypi should now be used at top-level. We used it once to install
  jishaku for ClicksForms. We have replaced the usage. There should be a
  broader conversation about the future of ClicksForms, but while
  upgrading to 23.11 is probably not the time for that
- fail2ban configs for things we no longer run have been removed

Additionally, the following things were looked at in-detail and deemed
non-important
- passwordFile changes (we do not use passwords, at all)
- matrix changes (we believe this will need no module changes for our
  use-case)
- nextcloud phpOptions changes (this may reintroduce some default PHP
  options. We didn't realize we were clobbering them and consider this to
  be a good thing)
- vaultwarden default host change (we already specify a host)
- RAID changes (neither a1d1 or a1d2 currently use software RAID)
- nixpkgs.config with external packages warnings (we don't use any
  nixpkgs.config options, but if we did we could remove them and) keep
  the same behavior
- nextcloud upgrade to 27 (we already use nextcloud 27)
- matrix workers and redis configuration (redis doesn't provide a benefit
  outside of workers, our deployment is too small to need workers)
- several services have improved requirements. In particular, lots of
  dependencies around postgres have been improved

We haven't yet updated mongodb, it may be a good idea to migrate to
ferretdb but this is better placed in a later change as migration will
require migrating all the data which will likely get messy

Change-Id: I8db3cc5bfa68bc591ef5e467e8c7de0cae30b300
Reviewed-on: https://git.clicks.codes/c/Clicks/NixFiles/+/122
Tested-by: Samuel Shuert <coded@clicks.codes>
Reviewed-by: Samuel Shuert <coded@clicks.codes>
diff --git a/flake.lock b/flake.lock
index 926437a..800bfac 100644
--- a/flake.lock
+++ b/flake.lock
@@ -79,16 +79,16 @@
         ]
       },
       "locked": {
-        "lastModified": 1695108154,
-        "narHash": "sha256-gSg7UTVtls2yO9lKtP0yb66XBHT1Fx5qZSZbGMpSn2c=",
+        "lastModified": 1700814205,
+        "narHash": "sha256-lWqDPKHRbQfi+zNIivf031BUeyciVOtwCwTjyrhDB5g=",
         "owner": "nix-community",
         "repo": "home-manager",
-        "rev": "07682fff75d41f18327a871088d20af2710d4744",
+        "rev": "aeb2232d7a32530d3448318790534d196bf9427a",
         "type": "github"
       },
       "original": {
         "owner": "nix-community",
-        "ref": "release-23.05",
+        "ref": "release-23.11",
         "repo": "home-manager",
         "type": "github"
       }
@@ -159,11 +159,11 @@
     },
     "nixpkgs-stable": {
       "locked": {
-        "lastModified": 1699110214,
-        "narHash": "sha256-L2TU4RgtiqF69W8Gacg2jEkEYJrW+Kp0Mp4plwQh5b8=",
+        "lastModified": 1701568804,
+        "narHash": "sha256-iwr1fjOCvlirVL/xNvOTwY9kg3L/F3TC/7yh/QszaPI=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "78f3a4ae19f0e99d5323dd2e3853916b8ee4afee",
+        "rev": "dc01248a9c946953ad4d438b0a626f5c987a93e4",
         "type": "github"
       },
       "original": {
@@ -189,16 +189,16 @@
     },
     "nixpkgs_3": {
       "locked": {
-        "lastModified": 1699291058,
-        "narHash": "sha256-5ggduoaAMPHUy4riL+OrlAZE14Kh7JWX4oLEs22ZqfU=",
+        "lastModified": 1701389149,
+        "narHash": "sha256-rU1suTIEd5DGCaAXKW6yHoCfR1mnYjOXQFOaH7M23js=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "41de143fda10e33be0f47eab2bfe08a50f234267",
+        "rev": "5de0b32be6e85dc1a9404c75131316e4ffbc634c",
         "type": "github"
       },
       "original": {
         "owner": "nixos",
-        "ref": "nixos-23.05",
+        "ref": "nixos-23.11",
         "repo": "nixpkgs",
         "type": "github"
       }
@@ -248,11 +248,11 @@
         "nixpkgs-stable": "nixpkgs-stable"
       },
       "locked": {
-        "lastModified": 1699311858,
-        "narHash": "sha256-W/sQrghPAn5J9d+9kMnHqi4NPVWVpy0V/qzQeZfS/dM=",
+        "lastModified": 1701572436,
+        "narHash": "sha256-0anfOQqDend6kSuF8CmOSAZsiAS1nwOsin5VQukh6Q4=",
         "owner": "Mic92",
         "repo": "sops-nix",
-        "rev": "664187539871f63857bda2d498f452792457b998",
+        "rev": "8bca48cb9a12bbd8766f359ad00336924e91b7f7",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index a497863..482f247 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,11 +2,11 @@
   description = "A flake to deploy and configure Clicks' NixOS server";
 
   # input URLs
-  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05";
+  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
   inputs.nixpkgs-clicksforms.url = "github:nixos/nixpkgs/nixos-22.05";
   inputs.flake-utils.url = "github:numtide/flake-utils";
   inputs.deploy-rs.url = "github:serokell/deploy-rs";
-  inputs.home-manager.url = "github:nix-community/home-manager/release-23.05";
+  inputs.home-manager.url = "github:nix-community/home-manager/release-23.11";
   inputs.sops-nix.url = "github:Mic92/sops-nix";
   inputs.scalpel.url = "github:polygon/scalpel";
 
diff --git a/modules/common/collabora.nix b/modules/common/collabora.nix
index a746962..0ff7c10 100644
--- a/modules/common/collabora.nix
+++ b/modules/common/collabora.nix
@@ -12,7 +12,7 @@
       extra_params = "--o:ssl.enable=false --o:ssl.termination=true";
     };
     extraOptions = [ 
-      "--pull=newer"
+      "--pull=always"
     ];
   };
   virtualisation.oci-containers.backend = "docker";
diff --git a/modules/common/fail2ban.nix b/modules/common/fail2ban.nix
index 5368094..ef06f0a 100644
--- a/modules/common/fail2ban.nix
+++ b/modules/common/fail2ban.nix
@@ -1,74 +1,12 @@
 { config, ... }: {
   services.fail2ban = {
     enable = true;
-    jails = {
-      mailu-auth-fail = ''
-        enabled = true
-        backend = systemd
-        filter = mailu-auth-fail
-        bantime = 604800
-        findtime = 600
-        maxretry = 5
-      '';
-      mailu-auth-limit = ''
-        enabled = true
-        backend = systemd
-        filter = mailu-auth-limit
-        bantime = 604800
-        findtime = 900
-        maxretry = 15
-      '';
-      samba = ''
-        filter=samba-filter
-        enabled=true
-        logpath=/var/log/messages
-        maxretry=1
-        findtime=600
-        bantime=2592000
-      '';
-    };
     banaction-allports = "iptables-allports";
     banaction = config.services.fail2ban.banaction-allports;
-    bantime = "24h";
     bantime-increment = {
       enable = true;
-      rndtime = "1h";
+      rndtime = "5m";
       overalljails = true;
-      factor = "24";
     };
   };
-  environment.etc = {
-    "fail2ban/filter.d/mailu-auth-fail.conf".text = ''
-      [Definition]
-      failregex = ^\s?\S+ mailu\-front\[\d+\]: \S+ \S+ \[info\] \d+#\d+: \*\d+ client login failed: \"AUTH not supported\" while in http auth state, client: <HOST>, server:
-      ignoreregex =
-      journalmatch = CONTAINER_TAG=mailu-front
-    '';
-
-    "fail2ban/filter.d/mailu-auth-limit.conf".text = ''
-      [Definition]
-      failregex = : Authentication attempt from <HOST> has been rate-limited\.$
-      ignoreregex =
-      journalmatch = CONTAINER_TAG=mailu-admin
-    '';
-
-    "fail2ban/filter.d/samba-filter.conf".text = ''
-      [Definition]
-      # Honeypot file regex. The files in the honeypot folder MUST match this regex
-      __honeypot_files_re=(-sync-decrypted\.)
-
-      # Known ransomware extensions regex
-      __known_ransom_extensions_re=(\.k$|\.encoderpass$|\.key$|\.ecc$|\.ezz$|\.exx$|\.zzz$|\.xyz$|\.aaa$|\.abc$|\.ccc$|\.vvv$|\.xxx$|\.ttt$|\.micro$|\.encrypted$|\.locked$|\.crypto$|_crypt$|\.crinf$|\.r5a$|\.xrtn$|\.XTBL$|\.crypt$|\.R16M01D05$|\.pzdc$|\.good$|\.LOL\!$|\.OMG\!$|\.RDM$|\.RRK$|\.encryptedRSA$|\.crjoker$|\.EnCiPhErEd$|\.LeChiffre$|\.keybtc@inbox_com$|\.0x0$|\.bleep$|\.1999$|\.vault$|\.HA3$|\.toxcrypt$|\.magic$|\.SUPERCRYPT$|\.CTBL$|\.CTB2$|\.locky$|\.wnry$|\.wcry$|\.wncry$|\.wncryt$|\.uiwix$)
-      # Known ransomware files regex
-      __known_ransom_files_re=(HELPDECRYPT\.TXT$|HELP_YOUR_FILES\.TXT$|HELP_TO_DECRYPT_YOUR_FILES\.txt$|RECOVERY_KEY\.txt$|HELP_RESTORE_FILES\.txt$|HELP_RECOVER_FILES\.txt$|HELP_TO_SAVE_FILES\.txt$|DecryptAllFiles\.txt$|DECRYPT_INSTRUCTIONS\.TXT$|INSTRUCCIONES_DESCIFRADO\.TXT$|How_To_Recover_Files\.txt$|YOUR_FILES\.HTML$|YOUR_FILES\.url$|Help_Decrypt\.txt$|DECRYPT_INSTRUCTION\.TXT$|HOW_TO_DECRYPT_FILES\.TXT$|ReadDecryptFilesHere\.txt$|Coin\.Locker\.txt$|_secret_code\.txt$|About_Files\.txt$|Read\.txt$|ReadMe\.txt$|DECRYPT_ReadMe\.TXT$|DecryptAllFiles\.txt$|FILESAREGONE\.TXT$|IAMREADYTOPAY\.TXT$|HELLOTHERE\.TXT$|READTHISNOW\!\!\!\.TXT$|SECRETIDHERE\.KEY$|IHAVEYOURSECRET\.KEY$|SECRET\.KEY$|HELPDECYPRT_YOUR_FILES\.HTML$|help_decrypt_your_files\.html$|HELP_TO_SAVE_FILES\.txt$|RECOVERY_FILES\.txt$|RECOVERY_FILE\.TXT$|RECOVERY_FILE.*\.txt$|HowtoRESTORE_FILES\.txt$|HowtoRestore_FILES\.txt$|howto_recover_file\.txt$|restorefiles\.txt$|howrecover\+.*\.txt$|_how_recover\.txt$|recoveryfile.*\.txt$|recoverfile.*\.txt$|recoveryfile.*\.txt$|Howto_Restore_FILES\.TXT$|help_recover_instructions\+.*\.txt$|_Locky_recover_instructions\.txt$)
-
-      # Match on known ransomware regex or generic honeypot
-      failregex = smbd.*:\ IP=<HOST>\ .*%(__honeypot_files_re)s
-            smbd.*:\ IP=<HOST>\ .*%(__known_ransom_extensions_re)s
-            smbd.*:\ IP=<HOST>\ .*%(__known_ransom_files_re)s
-
-      # Filter generously provided by https://github.com/CanaryTek/ransomware-samba-tools
-      # Provided under GPL3
-    '';
-  };
 }
diff --git a/modules/common/filesystems.nix b/modules/common/filesystems.nix
new file mode 100644
index 0000000..a8ac3df
--- /dev/null
+++ b/modules/common/filesystems.nix
@@ -0,0 +1,4 @@
+{ lib, ... }: {
+  fileSystems."/".device = lib.mkDefault "none";
+  # We cannot set up a system without a device on /, but we need to for overriding later
+}
diff --git a/modules/common/grafana.nix b/modules/common/grafana.nix
index f6ca62a..05fe726 100644
--- a/modules/common/grafana.nix
+++ b/modules/common/grafana.nix
@@ -49,6 +49,8 @@
     }];
   };
 
+  systemd.services.grafana.requires = [ "postgresql.service" ];
+
   sops.secrets.clicks_grafana_client_secret = {
     mode = "0600";
     owner = config.users.users.root.name;
diff --git a/modules/common/keycloak.nix b/modules/common/keycloak.nix
index e158825..587744d 100644
--- a/modules/common/keycloak.nix
+++ b/modules/common/keycloak.nix
@@ -16,6 +16,8 @@
   users.groups.keycloak = {};
   systemd.services.keycloak.serviceConfig.DynamicUser = lib.mkForce false;
 
+  systemd.services.keycloak.requires = [ "postgresql.service" ];
+
   services.keycloak = {
     enable = true;
     settings = {
diff --git a/modules/common/loginctl-linger.nix b/modules/common/loginctl-linger.nix
deleted file mode 100644
index adc5c84..0000000
--- a/modules/common/loginctl-linger.nix
+++ /dev/null
@@ -1,49 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-# A temporary hack to `loginctl enable-linger $somebody` (for
-# multiplexer sessions to last), until this one is unresolved:
-# https://github.com/NixOS/nixpkgs/issues/3702
-#
-# Usage: `users.extraUsers.somebody.linger = true` or slt.
-# Originally from
-# https://gist.githubusercontent.com/graham33/fdbdcc18317a621d9dd54beb36be6683/raw/776ed252749313470f1c9a286a0419ba9746d133/loginctl-linger.nix,
-# modified by Minion3665
-
-with lib;
-
-let
-
-  dataDir = "/var/lib/systemd/linger";
-
-  lingeringUsers = map (u: u.name)
-    (attrValues (flip filterAttrs config.users.users (n: u: u.linger)));
-
-  lingeringUsersFile = builtins.toFile "lingering-users" (concatStrings (map
-    (s: ''
-      ${s}
-    '') (sort (a: b: a < b)
-      lingeringUsers))); # this sorting is important for `comm` to work correctly
-
-  updateLingering = ''
-    if [ -e ${dataDir} ] ; then
-      ${pkgs.gawk}/bin/awk -F':' '{ print $1}' /etc/passwd | sort > /tmp/users-that-actually-exist
-      ls ${dataDir} | sort | comm -3 -1 ${lingeringUsersFile} - | comm -3 -2 /tmp/users-that-actually-exist - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
-      ls ${dataDir} | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl  enable-linger
-      ls ${dataDir} | sort | comm -3 -1 /tmp/users-that-actually-exist - | ${pkgs.gawk}/bin/awk '{print "${dataDir}/"$1}' | xargs -r rm
-      rm -f /tmp/users-that-actually-exist
-    fi
-  '';
-
-  userOptions = { options.linger = mkEnableOption "Lingering for the user"; };
-
-in {
-  options = {
-    users.users =
-      mkOption { type = with types; attrsOf (submodule userOptions); };
-  };
-
-  config = {
-    system.activationScripts.update-lingering =
-      stringAfter [ "users" ] updateLingering;
-  };
-}
diff --git a/modules/common/matrix.nix b/modules/common/matrix.nix
index b06ee64..26b2c26 100644
--- a/modules/common/matrix.nix
+++ b/modules/common/matrix.nix
@@ -116,7 +116,7 @@
       };
     };
 
-    homeserverUrl = "http://localhost:4527";
+    homeserverUrl = "http://generic:1030";
 
     managementRoom = "#moderation-commands:clicks.codes";
   };
@@ -160,7 +160,7 @@
     };
   };
 
-  systemd.services.matrix-synapse.requires = [ "postgresql.service" ];
+  systemd.services.matrix-synapse.requires = [ "postgresql.service" "nginx.service" "keycloak.service" ];
 
 } (let isDerived = base != null;
 in if isDerived
diff --git a/modules/common/nextcloud.nix b/modules/common/nextcloud.nix
index e98c5f4..29fc69a 100644
--- a/modules/common/nextcloud.nix
+++ b/modules/common/nextcloud.nix
@@ -51,61 +51,75 @@
     "overwrite.cli.url" = "https://nextcloud.clicks.codes";
   };
 
+  services.nextcloud.notify_push.enable = false;
+  services.nextcloud.configureRedis = true;
+
   services.nextcloud.extraApps = {
     sociallogin = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/zorn-v/nextcloud-social-login/releases/download/v5.5.3/release.tar.gz";
       sha256 = "sha256-96/wtK7t23fXVRcntDONjgb5bYtZuaNZzbvQCa5Gsj4=";
+      license = "agpl3Only";
     };
     richdocuments = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud-releases/richdocuments/releases/download/v8.2.0/richdocuments-v8.2.0.tar.gz";
       sha256 = "sha256-PKw7FXSWvden2+6XjnUDOvbTF71slgeTF/ktS/l2+Dk=";
+      license = "agpl3Only";
     };
     calendar = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud-releases/calendar/releases/download/v4.5.2/calendar-v4.5.2.tar.gz";
       sha256 = "sha256-n7GjgAyw2SLoZTEfakmI3IllWUk6o1MF89Zt3WGhR6A=";
+      license = "agpl3Only";
     };
     contacts = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud-releases/contacts/releases/download/v5.4.2/contacts-v5.4.2.tar.gz";
       sha256 = "sha256-IkKHJ3MY/UPZqa4H86WGOEOypffMIHyJ9WvMqkq/4t8=";
+      license = "agpl3Only";
     };
     tasks = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud/tasks/releases/download/v0.15.0/tasks.tar.gz";
       sha256 = "sha256-zMMqtEWiXmhB1C2IeWk8hgP7eacaXLkT7Tgi4NK6PCg=";
+      license = "agpl3Only";
     };
     appointments = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/SergeyMosin/Appointments/raw/v1.15.4/build/artifacts/appstore/appointments.tar.gz";
       sha256 = "sha256-2Oo7MJBPiBUBf4kti4or5nX+QiXT1Tkw3KowUGCj67E=";
+      license = "agpl3Only";
     };
     mail = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud-releases/mail/releases/download/v3.4.4/mail-v3.4.4.tar.gz";
       sha256 = "sha256-2+EUVjeFW0mrnR23aU5UHZtGjqpDE11qHXu6PWhUTCs=";
+      license = "agpl3Only";
     };
     spreed = pkgs.fetchNextcloudApp {  # nextcloud talk
       url =
         "https://github.com/nextcloud-releases/spreed/releases/download/v17.1.2/spreed-v17.1.2.tar.gz";
       sha256 = "sha256-OvZD/k1t4MAJ/BXbHzli6+V/bsgzE6iZQGrC9cG3b8E=";
+      license = "agpl3Only";
     };
     notes = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud-releases/notes/releases/download/v4.8.1/notes.tar.gz";
       sha256 = "sha256-7GkTGyGTvtDbZsq/zOdbBE7xh6DZO183W6I5XX1ekbw=";
+      license = "agpl3Only";
     };
     files_3dmodelviewer = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/WARP-LAB/files_3dmodelviewer/releases/download/v0.0.12/files_3dmodelviewer.tar.gz";
       sha256 = "sha256-JKlHDB6VFUXv7V+TzWSgJeuvR2Z+oXGKFZgZtX2A9pA=";
+      license = "agpl3Only";
     };
     external = pkgs.fetchNextcloudApp {
       url =
         "https://github.com/nextcloud-releases/external/releases/download/v5.2.1/external-v5.2.1.tar.gz";
       sha256 = "sha256-X7eC8T8wSZGVwCQp6U/WxjMC7aIj39osgHotaUoRNSQ=";
+      license = "agpl3Only";
     };
   };
 
@@ -117,5 +131,7 @@
     format = "json";
   };
 
+  systemd.services.nextcloud-setup.requires = [ "postgresql.service" ];
   systemd.services.nextcloud-cron.requires = [ "postgresql.service" ];
+  systemd.services.nextcloud-notify_push.requires = [ "postgresql.service" ];
 }
diff --git a/modules/common/nginx.nix b/modules/common/nginx.nix
index 6bd95e3..c34be24 100644
--- a/modules/common/nginx.nix
+++ b/modules/common/nginx.nix
@@ -175,7 +175,7 @@
 
     security.acme.defaults = {
       email = "admin@clicks.codes";
-      credentialsFile = config.sops.secrets.cloudflare_cert__api_token.path;
+      environmentFile = config.sops.secrets.cloudflare_cert__api_token.path;
     };
     security.acme.acceptTerms = true;
 
diff --git a/modules/common/postgres.nix b/modules/common/postgres.nix
index 30103e8..a4e107e 100644
--- a/modules/common/postgres.nix
+++ b/modules/common/postgres.nix
@@ -1,4 +1,8 @@
 { lib, config, pkgs, ... }: {
+  systemd.services.postgresql.after = [
+    "docker-network-taiga.service" # Needed to listen in 172.20.0.1
+  ];
+
   services.postgresql = {
     enable = true;
 
@@ -17,38 +21,32 @@
     ensureUsers = [
       {
         name = "clicks_grafana";
-        ensurePermissions = {
-          "ALL TABLES IN SCHEMA public" = "SELECT";
-          "SCHEMA public" = "USAGE";
-        };
       }
       {
         name = "matrix-synapse";
-        ensurePermissions = { "DATABASE synapse" = "ALL PRIVILEGES"; };
       }
       {
         name = "keycloak";
-        ensurePermissions = { "DATABASE keycloak" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }
       {
         name = "vaultwarden";
-        ensurePermissions = { "DATABASE vaultwarden" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }
       {
         name = "privatebin";
-        ensurePermissions = { "DATABASE privatebin" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }
       {
         name = "nextcloud";
-        ensurePermissions = { "DATABASE nextcloud" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }
       {
         name = "taiga";
-        ensurePermissions = { "DATABASE taiga" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }
     ] ++ (map (name: ({
       inherit name;
-      ensurePermissions = { "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; };
     })) [ "minion" "coded" "pineafan" ]);
 
     #                 method database user address auth-method
@@ -101,6 +99,19 @@
       ''))
       (lib.concatStringsSep "\n")
     ]))
+    ''
+      $PSQL -tAc 'ALTER DATABASE synapse OWNER TO "matrix-synapse";'
+      # matrix-synapse is done manually, because the database does not have the same name as the user
+
+      $PSQL -tAc 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "clicks_grafana"'
+      $PSQL -tAc 'GRANT USAGE ON SCHEMA public TO "clicks_grafana"'
+      # grafana is done manually, because it needs read permission in lots of places
+
+      $PSQL -tAc 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "coded"'
+      $PSQL -tAc 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "minion"'
+      $PSQL -tAc 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "pineafan"'
+      # leadership is done manually, because we need owner-level permissions in lots of places but cannot specify ourselves as the database owners (as there may only be 1)
+    ''
   ];
 
   sops.secrets = lib.pipe [
diff --git a/modules/common/privatebin.nix b/modules/common/privatebin.nix
index eece255..da6510b 100644
--- a/modules/common/privatebin.nix
+++ b/modules/common/privatebin.nix
@@ -5,6 +5,7 @@
     enableACME = lib.mkForce true;
     forceSSL = lib.mkForce true;
   };
+  systemd.services.privatebin.requires = [ "postgresql.service" ];
   services.privatebin = {
     enable = true;
     settings = {
diff --git a/modules/common/vaultwarden.nix b/modules/common/vaultwarden.nix
index 7ccc3a1..ced326c 100644
--- a/modules/common/vaultwarden.nix
+++ b/modules/common/vaultwarden.nix
@@ -4,6 +4,7 @@
 
   services.vaultwarden.enable = true;
   services.vaultwarden.dbBackend = "postgresql";
+  systemd.services.vaultwarden.requires = [ "postgresql.service" ];
 
   sops.secrets = lib.pipe [
     "ADMIN_TOKEN"
diff --git a/services/clicksforms/default.nix b/services/clicksforms/default.nix
index f0960c1..9b71960 100644
--- a/services/clicksforms/default.nix
+++ b/services/clicksforms/default.nix
@@ -14,7 +14,7 @@
         aiosqlite
         uvicorn
         validators
-        (pyPkgs.fetchPypi {
+        (fetchPypi {
           pname = "jishaku";
           version = "2.5.1";
           hash = lib.fakeSha256;