SEANK.H.LIAO

personal use gerrit, part 1

a code review server as a git host.

gerrit as a personal git host

I don't really like how everything and everyone is on Github, so I want to do something about it. It srarts small with hosting your own git stuff elsewhere.

For a while, I had been using charmbracelet/soft-serve as my git host. But it's just a git host, you can host a repo, and do some ci with webhooks, but there' no review integration, and it's very much ssh first.

I thought I'd try out gerrit again, this one is review-first rather than host first. Now, I don't really review my own code, after all I just wrote it..., but I could maybe make it work.

gerrit in kubernetes

Right, to spin up a gerrit server. My server runs kubernetes, so gerrit will run inside kubernetes too.

Gerrit have published prebuilt containers at index.docker.io/gerritcodereview/gerrit, which is great, I don't want to figure out how to build java services.

Annoyingly, almost all gerrit data and config is mixed together in /var/gerrit, which is also its $HOME directory. You'll have to mount the individual cache, data, db, etc, git, index, logs, plugins directories as volumes. I'm not quite sure if it's gerrit or its init process, but it does write to the config files as well, you'll need some way of managing that. This is also the reason I don't use the embedded entrypoint script, TBD if I should skip the gerrit run script too.

The first user to log in become admin, you can later promote other users if you want.

auth = HTTP

As a single user instance, and not having an openid instance idp around, I opted for using HTTP Basic Auth. Gerrit has 2 parts that need authentication, and they don't really speak to each other. The web ui does basic auth by offloading it to whatever reverse proxy you have, while the git http daemon requires credentials generated from the web ui... In effect, this means you should:

Side note, running in K8s, I'm using the new Gateway API, specifically Envoy Gateway. I kept getting 500 internal server errors until I read the instructions more carefully, and realized it only supports SHA as the password hash method for .htpasswd.

http passing encoded slashes

Everything seemed to work, until I clicked on a diff and it came back with a cryptic:

An error occurred
You might have not enough privileges.

Error 404: Not found: envoy-gateway

Endpoint: /changes/*~*/revisions/*/files/*/reviewed

Poking around a bit, for a diff affecting a path in a directory like deploy/envoy-gateway/kubernetes.yaml, the gerrit web ui sent a request for https://gerrit.liao.dev/c/mono/+/21/2/deploy%2Fenvoy-gateway%2Fkubernetes.yaml, note the slashes encoded as %2F. My reverse proxy was canonicalizing requests to https://gerrit.liao.dev/c/mono/+/21/2/deploy/envoy-gateway/kubernetes.yaml, which then gerrit failed to match. Gerrit has this documented as AllowEncodedSlashes On and nocanon for apache httpd. For envoy gateway, this meant modifying my ClientTrafficPolicy with path.escapedSlashesAction: KeepUnchanged

 1kind: ClientTrafficPolicy
 2spec:
 3  http3: {}
 4  path:
 5    escapedSlashesAction: KeepUnchanged
 6    disableMergeSlashes: true
 7  targetRefs:
 8    - group: gateway.networking.k8s.io
 9      kind: Gateway
10      name: http-gateway
11metadata:
12  name: http-gateway
13  namespace: envoy-gateway-system
14  labels:
15    app.kubernetes.io/part-of: envoy-gateway
16    app.kubernetes.io/managed-by: kpt
17    app.kubernetes.io/name: envoy-gateway
18  annotations:
19    config.kubernetes.io/origin: "\tmono/deploy/envoy-gateway/*.cue"
20apiVersion: gateway.envoyproxy.io/v1alpha1

gerrit config file

gerrit config options are documented online but lets go over some of the settings. The config file format is the same as git config, # are comments.

# This is a plugin for auto submit when all requriements are met
[automerge]
    botEmail = gerrit@liao.dev

# basic auth handled by gateway
[auth]
    type = HTTP
    gitBasicAuthPolicy = HTTP
    # gerrit wants emails for users,
    # this maps the username to a fixed domain part
    emailFormat = {0}@liao.dev

[cache]
    directory = cache

[gerrit]
    # directory within /var/gerrit to store bare git repos
    basePath = git
    defaultBranch = refs/heads/main
    canonicalWebUrl = https://gerrit.liao.dev/
    serverId = e536212b-9667-4845-8848-736bb2f4a5f0

[httpd]
    listenUrl = proxy-http://*:8080

[index]
    type = lucene

# allow managing plugins from web interface,
# seems it may sometime still be necessary to install them by hand though...
[plugins]
    allowRemoteAdmin = true

[receive]
    enableSignedPush = false

# gerrit really wants to send email,
# but I don't have a SMTP server to give it yet
[sendemail]
    enable = false

[sshd]
    listenAddress = *:29418

gitiles config

Gerrit runs a built in gitiles server for the web git view, I couldn't quite figure out how to make it run on its own hostname.

gerrit deployment manifests

A snapshot of what I currently use to deploy gerrit

  1kind: Namespace
  2metadata:
  3  name: gerrit
  4  labels:
  5    app.kubernetes.io/part-of: gerrit
  6    app.kubernetes.io/managed-by: kpt
  7    app.kubernetes.io/name: gerrit
  8  annotations:
  9    config.kubernetes.io/origin: "\tmono/deploy/gerrit/*.cue"
 10apiVersion: v1
 11---
 12spec:
 13  type: ClusterIP
 14  ports:
 15    - name: http
 16      port: 80
 17      protocol: TCP
 18      appProtocol: http
 19      targetPort: http
 20  selector:
 21    app.kubernetes.io/name: gerrit
 22kind: Service
 23apiVersion: v1
 24metadata:
 25  name: gerrit
 26  namespace: gerrit
 27  annotations:
 28    config.kubernetes.io/origin: "\tmono/deploy/gerrit/*.cue"
 29  labels:
 30    app.kubernetes.io/part-of: gerrit
 31    app.kubernetes.io/managed-by: kpt
 32    app.kubernetes.io/name: gerrit
 33---
 34kind: Deployment
 35metadata:
 36  name: gerrit
 37  namespace: gerrit
 38  labels:
 39    app.kubernetes.io/part-of: gerrit
 40    app.kubernetes.io/managed-by: kpt
 41    app.kubernetes.io/name: gerrit
 42  annotations:
 43    config.kubernetes.io/origin: "\tmono/deploy/gerrit/*.cue"
 44spec:
 45  selector:
 46    matchLabels:
 47      app.kubernetes.io/name: gerrit
 48  template:
 49    metadata:
 50      labels:
 51        app.kubernetes.io/name: gerrit
 52    spec:
 53      initContainers:
 54        - image: index.docker.io/gerritcodereview/gerrit:3.11.0-rc3-ubuntu24
 55          name: gerrit-init
 56          command:
 57            - sh
 58            - -c
 59            - |-
 60              if [ ! -d /var/gerrit/git/All-Projects.git ]
 61              then
 62                echo "Initializing Gerrit site ..."
 63                java $JAVA_OPTS -jar /var/gerrit/bin/gerrit.war init --batch --install-all-plugins -d /var/gerrit
 64                java $JAVA_OPTS -jar /var/gerrit/bin/gerrit.war reindex -d /var/gerrit
 65                git config -f /var/gerrit/etc/gerrit.config --add container.javaOptions "-Djava.security.egd=file:/dev/./urandom"
 66                git config -f /var/gerrit/etc/gerrit.config --add container.javaOptions "--add-opens java.base/java.net=ALL-UNNAMED"
 67                git config -f /var/gerrit/etc/gerrit.config --add container.javaOptions "--add-opens java.base/java.lang.invoke=ALL-UNNAMED"
 68              fi              
 69          env:
 70            - name: JAVA_OPTS
 71              value: --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED
 72          volumeMounts:
 73            - mountPath: /var/gerrit/cache
 74              subPath: cache
 75              name: data
 76            - mountPath: /var/gerrit/data
 77              subPath: data
 78              name: data
 79            - mountPath: /var/gerrit/db
 80              subPath: db
 81              name: data
 82            - mountPath: /var/gerrit/etc
 83              subPath: etc
 84              name: data
 85            - mountPath: /var/gerrit/git
 86              subPath: git
 87              name: data
 88            - mountPath: /var/gerrit/index
 89              subPath: index
 90              name: data
 91            - mountPath: /var/gerrit/logs
 92              subPath: logs
 93              name: data
 94      containers:
 95        - image: index.docker.io/gerritcodereview/gerrit:3.11.0-rc3-ubuntu24
 96          name: gerrit
 97          command:
 98            - /var/gerrit/bin/gerrit.sh
 99            - run
100          env:
101            - name: JAVA_OPTS
102              value: --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED
103          ports:
104            - containerPort: 29418
105              name: git-ssh
106            - containerPort: 8080
107              name: http
108          volumeMounts:
109            - mountPath: /var/gerrit/cache
110              subPath: cache
111              name: data
112            - mountPath: /var/gerrit/data
113              subPath: data
114              name: data
115            - mountPath: /var/gerrit/db
116              subPath: db
117              name: data
118            - mountPath: /var/gerrit/etc
119              subPath: etc
120              name: data
121            - mountPath: /var/gerrit/git
122              subPath: git
123              name: data
124            - mountPath: /var/gerrit/index
125              subPath: index
126              name: data
127            - mountPath: /var/gerrit/logs
128              subPath: logs
129              name: data
130            - mountPath: /var/gerrit/plugins
131              subPath: plugins
132              name: data
133      volumes:
134        - hostPath:
135            path: /opt/volumes/gerrit
136          name: data
137      enableServiceLinks: false
138  strategy:
139    type: Recreate
140  revisionHistoryLimit: 1
141apiVersion: apps/v1
142---
143kind: SecurityPolicy
144spec:
145  targetRefs:
146    - group: gateway.networking.k8s.io
147      kind: HTTPRoute
148      name: gerrit
149  basicAuth:
150    users:
151      name: basic-auth
152metadata:
153  name: gerrit
154  namespace: gerrit
155  labels:
156    app.kubernetes.io/part-of: gerrit
157    app.kubernetes.io/managed-by: kpt
158    app.kubernetes.io/name: gerrit
159  annotations:
160    config.kubernetes.io/origin: "\tmono/deploy/gerrit/*.cue"
161apiVersion: gateway.envoyproxy.io/v1alpha1
162---
163kind: HTTPRoute
164spec:
165  hostnames:
166    - gerrit.liao.dev
167  parentRefs:
168    - name: http-gateway
169      namespace: envoy-gateway-system
170  rules:
171    - backendRefs:
172        - name: gerrit
173          port: 80
174metadata:
175  name: gerrit
176  namespace: gerrit
177  labels:
178    app.kubernetes.io/part-of: gerrit
179    app.kubernetes.io/managed-by: kpt
180    app.kubernetes.io/name: gerrit
181  annotations:
182    config.kubernetes.io/origin: "\tmono/deploy/gerrit/*.cue"
183apiVersion: gateway.networking.k8s.io/v1