{ config, pkgs, modulesPath, lib, system, ... }: let # Stock cgit.css from the package with upstream dark-mode overrides # taken from https://git.zx2c4.com/cgit.css). # Follows the browser's prefers-color-scheme, light or dark. cgitCss = pkgs.writeText "cgit.css" ( builtins.readFile "${pkgs.cgit}/cgit/cgit.css" + builtins.readFile ./cgit-dark.css ); # The "manage" command is the default gitShellCommands = ./git-shell-commands; in { config = { sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.defaultSopsFile = ../../secrets/git.yaml; networking.hostName = "git"; networking.firewall.allowedTCPPorts = [ 80 ]; services.cgit."git" = { enable = true; # Any bare repo dropped in /srv/git shows up in cgit automatically, # but ONLY if it contains a git-daemon-export-ok marker file # (private-by-default; see settings.strict-export and # gitHttpBackend.checkExportOkFiles) scanPath = "/srv/git"; gitHttpBackend.checkExportOkFiles = true; settings = { # repos without a git-daemon-export-ok file are neither browsable # in cgit nor cloneable over HTTP (git-http-backend checks the same # file via checkExportOkFiles above) strict-export = "git-daemon-export-ok"; root-title = "My git browser"; root-desc = "git repositories"; repository-sort = "age"; enable-index-links = true; remove-suffix = true; # read gitweb.owner/description etc. from each repo's git config enable-git-config = true; clone-url = "https://git.kumardamani.net/$CGIT_REPO_URL git@git:$CGIT_REPO_URL"; robots = "noindex, nofollow"; # render the repo README on its About page (first match wins), readme = [ ":README.md" ":readme.md" ":README.rst" ":README.txt" ":README" ]; about-filter = "${pkgs.cgit}/lib/cgit/filters/about-formatting.sh"; "mimetype.gif" = "image/gif"; "mimetype.png" = "image/png"; "mimetype.jpg" = "image/jpeg"; "mimetype.jpeg" = "image/jpeg"; "mimetype.webp" = "image/webp"; "mimetype.svg" = "image/svg+xml"; "mimetype.ico" = "image/x-icon"; "mimetype.pdf" = "application/pdf"; "mimetype.mp4" = "video/mp4"; "mimetype.webm" = "video/webm"; enable-commit-graph = true; branch-sort = "age"; local-time = true; source-filter = "${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py"; }; }; systemd.tmpfiles.rules = [ "d /srv/git 0755 git git -" "Z /srv/git 0755 git git -" # git-shell subcommands (see gitShellCommands above) "d /srv/git/git-shell-commands 0755 git git -" "L+ /srv/git/git-shell-commands/create - - - - ${gitShellCommands}/create" "L+ /srv/git/git-shell-commands/publish - - - - ${gitShellCommands}/publish" "L+ /srv/git/git-shell-commands/hide - - - - ${gitShellCommands}/hide" "L+ /srv/git/git-shell-commands/desc - - - - ${gitShellCommands}/desc" "L+ /srv/git/git-shell-commands/manage - - - - ${gitShellCommands}/manage" # bare interactive ssh (ssh -t git@...) runs the manager instead of # git-shell's "not enabled" error. git push/clone use -c, unaffected. "L+ /srv/git/git-shell-commands/no-interactive-login - - - - ${gitShellCommands}/manage" ]; users.users.git = { isNormalUser = true; group = "git"; home = "/srv/git"; # makes git@git:.git resolve to /srv/git/.git createHome = false; shell = "${pkgs.git}/bin/git-shell"; openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAYYjB+fK4JFlJSY6Ax48p38eSuDG12VQVqP+WSrWGuO" ]; }; users.groups.git = { }; # Repos are owned by the git user; the cgit user (cgit.cgi / # git-http-backend) only reads them. Exempt them from git's # "dubious ownership" refusal. The templateDir gives every new repo # the post-receive hook below. environment.etc."gitconfig".text = '' [safe] directory = /srv/git/* [init] templateDir = /etc/git-template ''; # Copy of the official hook: # https://git.zx2c4.com/cgit/tree/contrib/hooks/post-receive.agefile environment.etc."git-template/hooks/post-receive" = { mode = "0755"; text = '' #!/bin/sh # # An example hook to update the "agefile" for CGit's idle time calculation. # # This hook assumes that you are using the default agefile location of # "info/web/last-modified". If you change the value in your cgitrc then you # must also change it here. # # To install the hook, copy (or link) it to the file "hooks/post-receive" in # each of your repositories. # agefile="$(git rev-parse --git-dir)"/info/web/last-modified mkdir -p "$(dirname "$agefile")" && git for-each-ref \ --sort=-authordate --count=1 \ --format='%(authordate:iso8601)' \ >"$agefile" ''; }; services.nginx.virtualHosts."git".locations."~ /.+/(git-receive-pack)" = { return = "403"; }; # Serve the light+dark stylesheet instead of the package's light-only one services.nginx.virtualHosts."git".locations."= /cgit.css".alias = cgitCss; # Headroom for large git operations swapDevices = [ { device = "/var/lib/swapfile"; size = 2048; } ]; }; }