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/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 {}
+ )