# 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 ]; config = mkIf cfg.enable { networking.firewall.allowedTCPPorts = [ cfg.port ]; age.secrets.alloy-push-password.file = ../../secrets/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 "; # 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"; }; age.secrets.gerrit-prometheus-bearer-token.file = ../../secrets/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"; }; }; }