diff options
| author | Kumar Damani <me@kumardamani.net> | 2026-08-02 01:43:25 +0000 |
|---|---|---|
| committer | Kumar Damani <me@kumardamani.net> | 2026-08-02 01:43:25 +0000 |
| commit | 43930cb8c22bc28ed64362845c5f4ebf54faabf2 (patch) | |
| tree | 7351357bd38139b0267f1a4cdeff4b5143f5e89a /nix/per-host/git/configuration.nix | |
| parent | 3e139a0e702513f6e1145158b2695b43c8251b92 (diff) | |
added git with cgit
Diffstat (limited to 'nix/per-host/git/configuration.nix')
| -rw-r--r-- | nix/per-host/git/configuration.nix | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/nix/per-host/git/configuration.nix b/nix/per-host/git/configuration.nix new file mode 100644 index 0000000..2a2fa34 --- /dev/null +++ b/nix/per-host/git/configuration.nix @@ -0,0 +1,141 @@ +{ 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:<repo>.git resolve to /srv/git/<repo>.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; } ]; + + }; +} |
