infra/services/gerrit/default.nix

337 lines
10 KiB
Nix

# Gerrit configuration for the Nixpkgs monorepo
# Inspired from TVL configuration.
{ pkgs, config, lib, ... }:
let
inherit (lib) mkEnableOption mkIf mkOption types head;
cfgGerrit = config.services.gerrit;
cfg = config.bagel.services.gerrit;
jdk = pkgs.openjdk21_headless;
in
{
options.bagel.services.gerrit = {
enable = mkEnableOption "Gerrit";
pyroscope.enable = mkEnableOption ''Pyroscope client,
this will send profiling of all Java processes on the current host
to our Pyroscope instance.
'';
domains = mkOption {
type = types.listOf types.str;
description = "List of domains that Gerrit will answer to";
};
canonicalDomain = mkOption {
type = types.str;
description = "Canonical domain for this Gerrit instance";
default = head cfg.domains;
};
data = mkOption {
type = types.path;
default = "/var/lib/gerrit";
description = "Root of data directory for the Gerrit";
};
port = mkOption {
type = types.port;
default = 29418;
readOnly = true;
description = "Port for the Gerrit SSH server";
};
};
imports = [
./www.nix
./one-way-sync.nix
./git-gc-preserve.nix
];
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [ cfg.port ];
age.secrets.alloy-push-password.file = ../../secrets/floral/metrics-push-password.age;
environment.systemPackages = [ jdk
pkgs.git
# https://gerrit.googlesource.com/gerrit/+/refs/heads/master/contrib/git-gc-preserve
pkgs.git-gc-preserve
];
fileSystems."/var/lib/gerrit" = mkIf (cfg.data != "/var/lib/gerrit") {
device = cfg.data;
options = [ "bind" ];
};
users.users.git = {
isSystemUser = true;
group = "git";
};
users.groups.git = {};
services.alloy = {
enable = cfg.pyroscope.enable;
extraFlags = [
# Debugging interface.
"--server.http.listen-addr=127.0.0.1:15555"
];
};
systemd.services.alloy.serviceConfig = {
User = lib.mkForce "root";
Group = lib.mkForce "root";
DynamicUser = lib.mkForce false;
};
systemd.services.alloy.serviceConfig.LoadCredential = [ "password:${config.age.secrets.alloy-push-password.path}" ];
environment.etc."alloy/config.alloy".text = ''
pyroscope.write "production" {
endpoint {
url = "https://pyroscope.forkos.org"
basic_auth {
username = "promtail"
password_file = "/run/credentials/alloy.service/password"
}
}
}
discovery.process "all" {
refresh_interval = "60s"
discover_config {
cwd = true
exe = true
commandline = true
username = true
uid = true
container_id = true
}
}
discovery.relabel "java" {
targets = discovery.process.all.targets
rule {
action = "keep"
regex = ".*/java$"
source_labels = ["__meta_process_exe"]
}
}
pyroscope.java "java" {
targets = discovery.relabel.java.output
forward_to = [pyroscope.write.production.receiver]
profiling_config {
interval = "60s"
alloc = "512k"
cpu = true
sample_rate = 100
lock = "1ms"
}
}
'';
services.gerrit = {
enable = true;
listenAddress = "[::]:4778"; # 4778 - grrt
serverId = "9e5216ad-038d-4d74-a4e8-716515834a94";
builtinPlugins = [
"gitiles"
"codemirror-editor"
"reviewnotes"
"download-commands"
"hooks"
"replication"
"webhooks"
];
plugins = with pkgs.gerritPlugins; [
oauth
metrics-reporter-prometheus
# Buildbot checks plugin (writeText because services.gerrit.plugins expects packages)
(pkgs.runCommand "checks.js" {
BASE_URI = builtins.toJSON "https://buildbot.forkos.org";
SUPPORTED_PROJECTS = builtins.toJSON [
"infra"
"nixpkgs"
"buildbot-test"
];
}
''
echo "configuring buildbot checks plugin for $BASE_URI with $SUPPORTED_PROJECTS project list"
substitute ${./checks.js} $out \
--replace-fail "@BASE_URI@" "$BASE_URI" \
--replace-fail "@SUPPORTED_PROJECTS@" "$SUPPORTED_PROJECTS"
'')
];
package = pkgs.gerrit;
jvmHeapLimit = "32g";
jvmPackage = jdk;
settings = {
# Performance settings
sshd.threads = 64;
sshd.batchThreads = 8;
gc.aggressive = true;
gc.interval = "1 day";
database.poolLimit = 250;
database.poolMaxIdle = 16;
httpd.maxThreads = 100;
receive.timeout = "4min";
# Default is 0, infinite.
# When system is under pressure and there's too many packfiles
# the search for reuse can take a stupid amount of time.
transfer.timeout = "60min";
# We may overshoot but it's OK.
core.packedGitWindowSize = "256k";
# Sum of all current packfiles is ~1.2G
# Largest packfile is 906MB.
# Average packfile is ~5-10MB.
core.packedGitLimit = "2g";
# We have plenty of memory, let's avoid file system cache → Gerrit needless copies.
core.packedGitUseStrongRefs = true;
core.packedGitOpenFiles = 4096;
# Big files in nixpkgs are usually lockfiles or machine-generated expressions
# containing a lot of hashes, they would weigh at most ~15MB.
core.streamFileThreshold = "20m";
# `mmap()` rather than `mmap()+read()` at the risk of running out of virtual address space.
core.packedGitMmap = true;
## Takes more CPU but the transfer is smaller.
pack.deltacompression = true;
pack.threads = 8;
# FIXME(raito):
# Are we supposed to have private / hidden references?
# For a public server, that seems unlikely.
# But, we should be careful with this option.
# https://gerrit-documentation.storage.googleapis.com/Documentation/3.9.5/config-gerrit.html#receive.checkReferencedObjectsAreReachable
receive.checkReferencedObjectsAreReachable = false;
# Other settings
log.jsonLogging = true;
log.textLogging = false;
sshd.advertisedAddress = "${cfg.canonicalDomain}:${toString cfg.port}";
cache.web_sessions.maxAge = "3 months";
plugins.allowRemoteAdmin = false;
change.enableAttentionSet = true;
change.enableAssignee = false;
user = {
name = "ForkOS Gerrit";
email = "gerrit@forkos.org";
anonymousCoward = "ForkOS contributor";
};
# Configures gerrit for being reverse-proxied by nginx as per
# https://gerrit-review.googlesource.com/Documentation/config-reverseproxy.html
gerrit = {
canonicalWebUrl = "https://${cfg.canonicalDomain}";
docUrl = "/Documentation";
defaultBranch = "refs/heads/main";
};
httpd.listenUrl = "proxy-https://${cfgGerrit.listenAddress}";
download.command = [
"checkout"
"cherry_pick"
"format_patch"
"pull"
];
# Auto-link other CLs
commentlink.gerrit = {
match = "cl/(\\d+)";
link = "https://${cfg.canonicalDomain}/$1";
};
# Configures integration with Keycloak, which then integrates with a
# variety of backends.
auth.type = "OAUTH";
plugin.gerrit-oauth-provider-keycloak-oauth = {
root-url = "https://identity.lix.systems";
realm = "lix-project";
client-id = "raito-gerrit-testing";
# client-secret is set in /var/lib/gerrit/etc/secure.config.
};
plugin.code-owners = {
# A Code-Review +2 vote is required from a code owner.
requiredApproval = "Code-Review+2";
# The OWNERS check can be overriden using an Owners-Override vote.
overrideApproval = "Owners-Override+1";
# People implicitly approve their own changes automatically.
enableImplicitApprovals = "TRUE";
};
# Allow users to add additional email addresses to their accounts.
oauth.allowRegisterNewEmail = true;
# Use Gerrit's built-in HTTP passwords, rather than trying to use the
# password against the backing OAuth provider.
auth.gitBasicAuthPolicy = "HTTP";
# Email sending (emails are relayed via the tazj.in domain's
# GSuite currently).
#
# Note that sendemail.smtpPass is stored in
# $site_path/etc/secure.config and is *not* controlled by Nix.
#
# Receiving email is not currently supported.
sendemail.enable = false;
#sendemail = {
# enable = false;
# html = false;
# connectTimeout = "10sec";
# from = "TVL Code Review <tvlbot@tazj.in>";
# includeDiff = true;
# smtpEncryption = "none";
# smtpServer = "localhost";
# smtpServerPort = 2525;
#};
};
# Replication of the depot repository to secondary machines, for
# serving cgit/josh.
#replicationSettings = {
# gerrit.replicateOnStartup = true;
# remote.sanduny = {
# url = "depot@sanduny.tvl.su:/var/lib/depot";
# projects = "depot";
# };
#};
};
systemd.services.gerrit = {
serviceConfig = {
# There seems to be no easy way to get `DynamicUser` to play
# well with other services (e.g. by using SupplementaryGroups,
# which seem to have no effect) so we force the DynamicUser
# setting for the Gerrit service to be disabled and reuse the
# existing 'git' user.
DynamicUser = lib.mkForce false;
User = "git";
Group = "git";
};
environment.REVWALK_USE_PRIORITY_QUEUE = "true";
};
bagel.services.git-gc-preserve = {
nixpkgs = {
enable = true;
repoPath = "/var/lib/gerrit/git/nixpkgs.git";
};
};
age.secrets.gerrit-prometheus-bearer-token.file = ../../secrets/floral/gerrit-prometheus-bearer-token.age;
bagel.monitoring.grafana-agent.exporters.gerrit = {
port = 4778; # grrt
bearerTokenFile = config.age.secrets.gerrit-prometheus-bearer-token.path;
scrapeConfig.metrics_path = "/plugins/metrics-reporter-prometheus/metrics";
};
};
}