Add gerrit

Gerrit is a service which provides code review via "changes" (commits) which you
can continually amend, viewing the diffs between them in a very similar way to
<https://graphite.dev/> (which is awesome but too expensive for Clicks to
justify). We hope it'll provide some better structure than the GitHub workflow
we have been using

Still TODO (in a followup change):
- The bazel build for the oauth module is horrible and introduces tips from
  <https://zimbatm.com/notes/nix-packaging-the-heretic-way>. This is generally
  considered a bad thing. We should change this
- Gerrit cannot yet send emails

Change-Id: I1393b2ae5a1efe049ea2170de46070d8789a2e3a
diff --git a/default/configuration.nix b/default/configuration.nix
index ad95778..0a91276 100644
--- a/default/configuration.nix
+++ b/default/configuration.nix
@@ -133,7 +133,7 @@
   nix.settings.experimental-features = [ "nix-command" "flakes" ];
 
   # Open ports in the firewall.
-  networking.firewall.allowedTCPPorts = [ 80 443 25 465 587 110 995 143 993 ];
+  networking.firewall.allowedTCPPorts = [ 80 443 25 465 587 110 995 143 993 29418 ];
   # networking.firewall.allowedUDPPorts = [ ... ];
   # Or disable the firewall altogether.
   networking.firewall.enable = true;
diff --git a/flake.nix b/flake.nix
index 838808d..11b4c88 100644
--- a/flake.nix
+++ b/flake.nix
@@ -58,6 +58,7 @@
               ./modules/ecryptfs.nix
               ./modules/fail2ban.nix
               ./modules/fuck.nix
+              ./modules/gerrit.nix
               ./modules/git.nix
               ./modules/grafana.nix
               ./modules/home-manager-users.nix
@@ -82,7 +83,7 @@
                 users.mutableUsers = false;
               }
             ];
-            specialArgs = { base = null; drive_paths = import ./variables/drive_paths.nix; };
+            specialArgs = { base = null; drive_paths = import ./variables/drive_paths.nix; inherit system; };
           };
         in
         base.extendModules {
diff --git a/modules/caddy/caddyfile.nix b/modules/caddy/caddyfile.nix
index 0b2acf4..4334549 100644
--- a/modules/caddy/caddyfile.nix
+++ b/modules/caddy/caddyfile.nix
@@ -377,6 +377,10 @@
             "syncthing.thecoded.prof"
             "syncthing.hopescaramels.com"
           ] [ "localhost:8384" ])
+          (HTTPReverseProxyRoute [
+            "git.clicks.codes"
+            "gerrit.clicks.codes"
+          ] [ "127.0.0.255:1000" ])
           (PHPRoute
             [ "paste.clicks.codes" "paste.coded.codes" ]
             "${pkgs.privatebin}/share/privatebin"
diff --git a/modules/gerrit.nix b/modules/gerrit.nix
new file mode 100644
index 0000000..962fb2b
--- /dev/null
+++ b/modules/gerrit.nix
@@ -0,0 +1,202 @@
+{ pkgs, config, lib, base, system, ... }: let
+  cfg = config.services.gerrit;
+in lib.recursiveUpdate
+{
+  sops.secrets.clicks_gerrit_db_password = {
+    mode = lib.mkForce "0440";
+    group = lib.mkForce "gerrit";
+  };
+
+  users.users.gerrit = {
+    isSystemUser = true;
+    createHome = true;
+    home = "/var/lib/gerrit";
+    group = config.users.groups.gerrit.name;
+    shell = pkgs.bashInteractive;
+  };
+  users.groups.gerrit = {};
+
+  systemd.services.gerrit.serviceConfig.User = "gerrit";
+  systemd.services.gerrit.serviceConfig.Group = "gerrit";
+  systemd.services.gerrit.serviceConfig.DynamicUser = lib.mkForce false;
+
+  services.gerrit = {
+    enable = true;
+
+    /* jvmOpts = [
+      "-Djava.class.path=${pkgs.postgresql_jdbc}/share/java"
+    ]; */
+
+    settings = {
+      # accountPatchReviewDb.url = "postgresql://localhost:${toString config.services.postgresql.port}/gerrit?user=gerrit&password=!!gerrit_database_password!!";
+      accounts = {
+        visibility = "SAME_GROUP";
+        defaultDisplayName = "USERNAME";
+      };
+      addReviewer = {
+        maxWithoutConfirmation = 3;
+        maxAllowed = 10;
+      };
+      auth = {
+        type = "OAUTH";
+        registerEmailPrivateKey = "!!gerrit_email_private_key!!";
+        userNameCaseInsensitive = true;
+        gitBasicAuthPolicy = "HTTP";
+      };
+      plugin."gerrit-oauth-provider-keycloak-oauth" = {
+        root-url = "https://login.clicks.codes";
+        realm = "clicks";
+        client-id = "git";
+        client-secret = "!!gerrit_oauth_client_secret!!";
+        use-preferred-username = true;
+      };
+      change = {
+        topicLimit = 0;
+        mergeabilityComputationBehavior = "API_REF_UPDATED_AND_CHANGE_REINDEX";
+        sendNewPatchsetEmails = false;
+        showAssigneeInChangesTable = true;
+        submitWholeTopic = true;
+        diff3ConflictView = true;
+      };
+      changeCleanup = {
+        abandonAfter = "3 weeks";
+        abandonMessage = "This change was abandoned due to 3 weeks of inactivity. If you still want it, please restore it";
+        startTime = "00:00";
+        interval = "1 day";
+      };
+      attentionSet = {
+        readdAfter = "1 week";
+        readdMessage = "I've given the owner a *ping* as nothing has happened for a week. If in two weeks time the change is still inactive, I'll abandon it for you. If you still want it, please do something before then";
+        startTime = "00:00";
+        interval = "1 day";
+      };
+      commentlink.gerrit = {
+        match = "(I[0-9a-f]{8,40})";
+        link = "/q/$1";
+      };
+      gc = {
+        aggressive = true;
+        startTime = "Sun 00:00";
+        interval = "1 week";
+      };
+      gerrit = {
+        basePath = "/var/lib/gerrit/repos";
+        defaultBranch = "refs/heads/main";
+        canonicalWebUrl = "https://git.clicks.codes/";
+        canonicalGitUrl = "ssh://ssh.clicks.codes/";
+        gitHttpUrl = "https://git.clicks.codes/";
+        reportBugUrl = "https://discord.gg/bPaNnxe"; # TODO: kinda obnoxious, better to setup bugzilla/similar
+        enablePeerIPInReflogRecord = true;
+        instanceId = "a1d1";
+        instanceName = "a1d1.clicks";
+      };
+      mimetype = lib.pipe [ "image/*" "video/*" "application/pdf" ] [
+        (map (name: { inherit name; value.safe = true; }))
+        builtins.listToAttrs
+      ];
+      receive.enableSignedPush = true;
+      sendemail.enable = false; # TODO: add credentials to git@clicks.codes
+      sshd.advertisedAddress = "ssh.clicks.codes:29418";
+      user = {
+        name = "Clicks Gerrit";
+        email = "git@clicks.codes";
+        anonymousCoward = "Anonymous";
+      };
+      httpd.listenUrl = "proxy-https://${cfg.listenAddress}";
+    };
+
+    plugins = [ (
+      derivation {
+        name = "oauth.jar"; # HACK: wrapping a derivation in a derivation to rename it seems like a bad hack... but bazel would not build if I didn't (I think because it didn't like the .jar extension...) check why though?
+        src = (
+          pkgs.buildBazelPackage {
+            __noChroot = true; # FIXME: terrible, horrible, no good, very bad
+            # name = "gerrit-oauth-provider.jar";
+            pname = "gerrit-oauth-provider.jar";
+            version = "unstable-2023-10-08";
+            src = pkgs.fetchgit {
+              url = "https://gerrit.googlesource.com/plugins/oauth";
+              rev = "1b3cc407cb2571d08601ab852e6e01f82d27160f";
+              hash = "sha256-yC/8qnkDbfIujl+Cvamr+EQSwto1DcIUWXh5cwDEZHo=";
+              deepClone = true; # FIXME: this bazel build uses some git stuff, maybe we should try replacing with fakegit?
+            };
+            bazelTargets = [ "oauth" ];
+            bazel = pkgs.bazel_4;
+            buildAttrs = {};
+            fetchAttrs.sha256 = "sha256-i5wOTn2NqqgJf4TCIqaCucpXu+5Vm5C84UPrGYFMSzc=";
+
+            postUnpack = ''
+              echo "4.2.2" > */.bazelversion  # nixpkgs only has certain bazel versions, so let's upgrade the patch of this one
+            '';
+
+            buildInputs = with pkgs; [
+              git
+              curl
+              jdk11
+            ];
+
+            postInstall = ''
+              cp bazel-bin/oauth.jar $out
+            '';
+          }
+        );
+        builder = "/bin/sh";
+        args = [ "-c" "${pkgs.coreutils}/bin/cp $src $out" ];
+        inherit system;
+      }
+    ) ];
+    builtinPlugins = [ "codemirror-editor" "commit-message-length-validator" "delete-project" "download-commands" "gitiles" "hooks" "reviewnotes" "singleusergroup" "webhooks" ];
+    serverId = "45f277d0-fce7-43b7-9eb3-2e3234e0110f";
+
+    listenAddress = "127.0.0.255:1000";
+  };
+
+  nix.settings.sandbox = "relaxed"; # FIXME: terrible, horrible, no good, very bad, here to support buildBazelPackage's use of cURL
+
+  sops.secrets = {
+    gerrit_email_private_key = {
+      mode = "0400";
+      owner = config.users.users.root.name;
+      group = config.users.users.nobody.group;
+      sopsFile = ../secrets/gerrit.json;
+      format = "json";
+    };
+    gerrit_oauth_client_secret = {
+      mode = "0400";
+      owner = config.users.users.root.name;
+      group = config.users.users.nobody.group;
+      sopsFile = ../secrets/gerrit.json;
+      format = "json";
+    };
+  };
+}
+  (
+    let
+      isDerived = base != null;
+    in
+    if isDerived
+    then
+      let
+        gerrit_cfgfile = pkgs.writeText "gerrit.conf" (
+          lib.generators.toGitINI cfg.settings
+        );
+      in
+      {
+        scalpel.trafos."gerrit.conf" = {
+          source = toString gerrit_cfgfile;
+          matchers."gerrit_email_private_key".secret =
+            config.sops.secrets.gerrit_email_private_key.path;
+          matchers."gerrit_oauth_client_secret".secret =
+            config.sops.secrets.gerrit_oauth_client_secret.path;
+          owner = config.users.users.nobody.name;
+          group = "gerrit";
+          mode = "0040";
+        };
+
+        systemd.services.gerrit.preStart = base.config.systemd.services.gerrit.preStart + ''
+        rm etc/gerrit.config
+        ln -sfv ${config.scalpel.trafos."gerrit.conf".destination} etc/gerrit.config
+        '';
+      }
+    else {}
+  )
diff --git a/modules/git.nix b/modules/git.nix
index 747f686..d086cfd 100644
--- a/modules/git.nix
+++ b/modules/git.nix
@@ -1,56 +1,3 @@
 { config, pkgs, ... }: {
-  environment.systemPackages = with pkgs; [ gh git ];
-
-  services.gitea = {
-    enable = false;
-    settings.mailer = {
-      ENABLED = true;
-      FROM = "git@clicks.codes";
-      PROTOCOL = "smtps";
-      SMTP_ADDR = "smtp.coded.codes";
-      SMTP_PORT = "465";
-      USER = "git@clicks.codes";
-      PASSWD = "ilIfASM@U5Z4XOEoH99gA8jPvGiOiEdx";
-      HELO_HOSTNAME = "git.clicks.codes";
-    };
-    settings.service = {
-      REGISTER_EMAIL_CONFIG = false;
-      ENABLE_NOTIFY_MAIL = false;
-      DISABLE_REGISTRATION = true;
-      ENABLE_CAPTCHA = false;
-      REQUIRE_SIGNIN_VIEW = false;
-      DEFAULT_KEEP_EMAIL_PRIVATE = false;
-      DEFAULT_ENABLE_TIMETRACKING = true;
-    };
-    settings.server = {
-      ROOT_URL = "https://git.clicks.codes/";
-      HTTP_PORT = 6064;
-      SSH_DOMAIN = "ssh.clicks.codes";
-      DOMAIN = "localhost";
-      DISABLE_SSH = false;
-      OFFLINE_MODE = false;
-    };
-    settings.openid.ENABLE_OPENID_SIGNIN = true;
-    settings.log = {
-      MODE = "console";
-      LEVEL = "Info";
-      ROUTER = "console";
-    };
-    settings.repository = {
-      ENABLE_PUSH_CREATE_USER = true;
-      ENABLE_PUSH_CREATE_ORG = true;
-    };
-    settings."repository.pull-request".DEFAULT_MERGE_STYLE = "merge";
-    settings."repository.signing".DEFAULT_TRUST_MODEL = "committer";
-    settings.security = {
-      INSTALL_LOCK = true;
-      PASSWORD_HASH_ALGO = "pbkdf2";
-    };
-    settings.indexer = {
-      REPO_INDEXER_ENABLED = true;
-      UPDATE_BUFFER_LEN = 20;
-      MAX_FILE_SIZE = 1048576;
-    };
-    settings.session.PROVIDER = "file";
-  };
+  environment.systemPackages = with pkgs; [ gh git git-review ];
 }
diff --git a/modules/postgres.nix b/modules/postgres.nix
index d2844c1..d1f8a31 100644
--- a/modules/postgres.nix
+++ b/modules/postgres.nix
@@ -12,8 +12,10 @@
 
     ensureDatabases = [
       "vaultwarden"
+      "gerrit"
       "privatebin"
       "keycloak"
+      "nextcloud"
     ];
 
     ensureUsers = [
@@ -37,6 +39,12 @@
         };
       }
       {
+        name = "gerrit";
+        ensurePermissions = {
+          "DATABASE gerrit" = "ALL PRIVILEGES";
+        };
+      }
+      {
         name = "vaultwarden";
         ensurePermissions = {
           "DATABASE vaultwarden" = "ALL PRIVILEGES";
@@ -48,6 +56,12 @@
           "DATABASE privatebin" = "ALL PRIVILEGES";
         };
       }
+      {
+        name = "nextcloud";
+        ensurePermissions = {
+          "DATABASE nextcloud" = "ALL PRIVILEGES";
+        };
+      }
     ] ++ (map
       (name: (
         {
@@ -80,6 +94,7 @@
     (lib.mkAfter (lib.pipe [
       { user = "clicks_grafana"; passwordFile = config.sops.secrets.clicks_grafana_db_password.path; }
       { user = "keycloak"; passwordFile = config.sops.secrets.clicks_keycloak_db_password.path; }
+      { user = "gerrit"; passwordFile = config.sops.secrets.clicks_gerrit_db_password.path; }
       { user = "vaultwarden"; passwordFile = config.sops.secrets.clicks_bitwarden_db_password.path; }
       { user = "privatebin"; passwordFile = config.sops.secrets.clicks_privatebin_db_password.path; }
     ] [
@@ -93,6 +108,7 @@
   sops.secrets = lib.pipe [
     "clicks_grafana_db_password"
     "clicks_keycloak_db_password"
+    "clicks_gerrit_db_password"
     "clicks_bitwarden_db_password"
     "clicks_privatebin_db_password"
   ] [
diff --git a/secrets/gerrit.json b/secrets/gerrit.json
new file mode 100644
index 0000000..5af3821
--- /dev/null
+++ b/secrets/gerrit.json
@@ -0,0 +1,29 @@
+{
+	"gerrit_email_private_key": "ENC[AES256_GCM,data:SmQwCqV9yfMLeXz9GJKSUEhJXyk8xmfvBcjNusnXQexm15OavlKq1bE9K8xnt0XZ5RWEP/NKk7RKfEC77myqUaQeuoIP3Yb1pEj8jcHokWoc/f9spP2g/tlG8yf51RDC1YafGNBzoASnEnMaI0F7i/CtrdTDO8lBQ566dHOBa5U=,iv:TxzBnbdzMKuK0SC770/sq3O2JYbaik7RPuo9uWII5XM=,tag:H3Ni6Xk26x/hR42V4LYEkQ==,type:str]",
+	"gerrit_oauth_client_secret": "ENC[AES256_GCM,data:F48NTbAAP9naO/xg0QZA9BQQhOTBP4GiPoQMCW7HADo=,iv:tUw4P6lYSjNBC2lDPOlXsJKM45bkA8QTZQy6fY88GOU=,tag:6sCOI9T8cJe5h+TGZ5l5uQ==,type:str]",
+	"sops": {
+		"kms": null,
+		"gcp_kms": null,
+		"azure_kv": null,
+		"hc_vault": null,
+		"age": [
+			{
+				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArWW9DWWgwL1lPYXpQUUts\ndUsrVlprT1p0NEZwdjdtRVRUeDVlWjJ4Y1RRCm91MXJEUjJuQ3RSS3NUa0JlZnRl\nYVVwQ0I1NytYU1JhTXBBek1zSVRhbHcKLS0tIHZrTGx3MWNxWmVxN1gvcDNCOWN2\naEhxS04rTXZNK3VkT2ovaVVhQ0Z4VzQKOc8Jptj+QHcSAoI1oVZzytbMEm8rmRRx\nr/TxROAYfD2iN+ppFNctXNIw0DrESW3fOaK3kzLr40F9TacHBEIRig==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrMGk2b0Myb3BDN3ZZeHNC\ncXprZG5vbjlnc3lmZ0FORzM4bkpZS2tnUWx3ClcvcmFHaDlJaE83V0ZIM0R2OTJ6\nbllUbjNESS9KQlMyaFZWQk1KY0VCeW8KLS0tIFF4bm5GenlNNUpYbytQaG1ndUxD\na1pnbzIvL1BLYWdieWk1Y2RnSlpPWWsKOXs73Z3Qg1D0yic2w57zZUdcYyLPfwCM\nbBSOEEYl3XgHfCNUBP9MjjekcgWZ6/aOfr+vs8ywt8/qPFvdc3bNEw==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBMHUxMXVkMTJKaXMrYkFO\nS0hSSFptRFFHUDMxaTNwdldXVkhaT1FiVXdvCkt2YmtlbzBxaFNERnN1MkVRd3k0\nTzVObEpSM3JwcHpaWHdBZkNFb1ZMN2MKLS0tIGpleWI4ZDdOUXVXUnhLNUtianRF\nNXdIZjVXZ1BOdm9nNjRNTHJXRUJGWDAK0LFyd/uQWlExs0xnh/9EQimY9GX+BrFa\nHqQw9MEf2sXquLN+JOUQJFB3apIHP1V330j3dAGHuK4CVtfAd7UwNQ==\n-----END AGE ENCRYPTED FILE-----\n"
+			}
+		],
+		"lastmodified": "2023-10-08T20:27:38Z",
+		"mac": "ENC[AES256_GCM,data:757DUB58m953xGaNUJSezST1iXn/mOdh4RW/anjikBesJqgjToLwMD6AZHDQq+MgJMVdjOFhRAyQ+6UjuiWJYWCRiNUxy8dI9j6V4T7AnGDDkO9Pp3KfAVkIarT28BRqgO4MSr2YoB0yrQYPJScVPVjulaKOHEJeq3TSvgefaxo=,iv:ss8f3v6Z/9hiI1Ju/KTLSW91NSaoYNZ8opuKLDQnqvg=,tag:LmPKGcI8v9tq9exfZQxwXA==,type:str]",
+		"pgp": null,
+		"unencrypted_suffix": "_unencrypted",
+		"version": "3.7.3"
+	}
+}
\ No newline at end of file