{ povSelf, hostConfig, config, lib, pkgs, ... }: let inherit (lib) types; cfg = lib.getAttrFromPath povSelf config; in { options = { enable = { type = types.bool; default = false; }; domain = { type = types.str; default = "zaphyra.eu"; }; subdomain = { type = types.str; default = "git"; }; title = { type = types.str; default = "zaphyra's git"; }; mail = { type = types.str; default = "git@zaphyra.eu"; }; categories = { type = with types; listOf str; default = [ "nix" "etc" "javascript" "nimlang" "nimlang libraries" "archive" ]; }; adminPubkey = { type = types.str; default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDIHFm/bePR+HT5MAuMslUHt68nTrEhlqcKIS+9Rfi9FzRKAia/DdLbwpfC1iXuM+iQd8fIMp4Ir+kBMqoZaVzyCtqKH6QHbhwBiWIdMA7FndMbfDcO9BzUcqCAVt8HxcGd1Z4bE9ZgZZuIsJiPbqJ+QbK1rkY8uJLDVR2MXI5jpU/m+9RJzrwVZ6JxwjdY4cNaIYwoOW6ZxL+ukLRwy+spBWmWdcHeq6zeLsl/OjUV6WIh2pM9O0o9nsiDekhOBf2MJLlM+e8rWICwYsfLqGAeRAuDe03BFBXsbDg/lqTYB5G8XSaT2R8ty2RyeEBySS32pUyErdKVXnyHNBEvxC6+cJiZtL8rhkpU1qRg/MIUjprMVUWisMlYnai2K0VpNpc5w09YXQl7aXSge8L/5+IzugPj17+FK4FVwRptXxynnYeKiwWEsOiiFe3IVaQ6vyRN66fbMjx/d0JSfadbwV7L++aT85bsb05zhNDpaqK1I5sGs3uV3CglkmhxBmky67Eq/qkMlJZMVtgE7i88H8+XzTiofJaKYTeyq+XQnK6a6OVGyca2dEorBFmBTEtz70nQnSrhPqQrS4zgr4OTSFtUtdFVDzgHaxRC+y4/SP5zCA8Xfwp0q1M0jVE9XpVpGydXtGGV08uXOsDPv5E4euxq6qgv8d2azDeBHXp+kEHm4w== cardno:6445184"; }; }; config = lib.mkIf cfg.enable ( let stagitFunctions = pkgs.writeShellScript "stagitFunctions" '' is_public_and_listed() { if [ ! -f "$1/git-daemon-export-ok" ]; then return 1 fi return 0 } is_forced_update() { test "$oldrev" = "0000000000000000000000000000000000000000" && return 1 test "$newrev" = "0000000000000000000000000000000000000000" && return 1 hasrevs="$(${pkgs.git}/bin/git rev-list "$oldrev" "^$newrev" | ${pkgs.gnused}/bin/sed 1q)" if test -n "$hasrevs"; then return 0 fi return 1 } build_stagit_repo() { reponame="$(basename "$1" ".git")" printf "[%s] Generate stagit HTML pages... " "$reponame" mkdir -p "${config.modules.services.gitolite.dataDir}/stagit-cache" mkdir -p "/var/lib/stagit/$reponame" cd "/var/lib/stagit/$reponame" || return 1 # build repo pages ${pkgs.stagit}/bin/stagit -c "${config.modules.services.gitolite.dataDir}/stagit-cache/$reponame" -n "${cfg.title}" -h 'https://git.${cfg.domain}/' -s 'git@${config.networking.fqdn}:' "$1" # set correct permissions chown git:git -R /var/lib/stagit/$reponame; chmod 755 -R /var/lib/stagit/$reponame; echo "done" } build_stagit_index() { printf "Generating stagit index... " # set assets if not already there ln -sf "${pkgs.stagit}/share/doc/stagit/highlight.min.js" "/var/lib/stagit/highlight.min.js" 2> /dev/null ln -sf "${pkgs.stagit}/share/doc/stagit/style.css" "/var/lib/stagit/style.css" 2> /dev/null # generate index arguments args="-n \"${cfg.title}\" -e '${cfg.mail}'" for category in ${lib.escapeShellArgs cfg.categories}; do args="$args -c '$category'" for repo in "$HOME/repositories/"*.git/; do repo="''${repo%/}" is_public_and_listed "$repo" || continue [ "$(${pkgs.gawk}/bin/awk -F '=' '/category/ {print $2}' $repo/config | ${pkgs.gnused}/bin/sed -e 's/^[[:space:]]*//')" = "$category" ] && args="$args $repo" done done # build index echo "$args" | xargs ${pkgs.stagit}/bin/stagit-index > /var/lib/stagit/index.html # set correct permissions chown git:git /var/lib/stagit/index.html; chmod 755 /var/lib/stagit/index.html; echo "done" } update_stagit_repo() { repo="$(pwd)" reponame="$(basename "$repo" ".git")" cd "$repo" || return 1 is_public_and_listed "$repo" || return 0 # if forced update, remove directory and cache file is_forced_update && printf "[%s] Forced update, trigger complete regeneration of stagit-pages... \n" "$reponame" && rm -rf "/var/lib/stagit/$reponame" "/var/lib/gitolite/stagit-cache/$reponame" build_stagit_repo "$repo" build_stagit_index } ''; rebuildWebdir = '' source ${stagitFunctions} # clear webdir rm -rf /var/lib/stagit/* # clear cache rm -rf ${config.modules.services.gitolite.dataDir}/stagit-cache/* # generate pages per repo for repo in "$HOME/repositories/"*.git/; do repo="''${repo%/}" is_public_and_listed "$repo" || continue build_stagit_repo "$repo" done # generate index page build_stagit_index ''; in { dns.zones = { "${cfg.domain}".subdomains."${cfg.subdomain}".CNAME = [ "${config.networking.fqdn}." ]; "katja.wtf".subdomains."${cfg.subdomain}".CNAME = [ "${config.networking.fqdn}." ]; "ctu.cx".subdomains."${cfg.subdomain}".CNAME = [ "${config.networking.fqdn}." ]; "ctu.cx".subdomains."cgit".CNAME = [ "${config.networking.fqdn}." ]; }; modules.filesystem.impermanence.system.dirs = [ { directory = "/var/lib/stagit"; mode = "0755"; user = "git"; group = "git"; } ]; sops.secrets."resticPasswords/gitolite" = { owner = "git"; }; systemd.tmpfiles.settings.stagit = { "/var/lib/stagit".d = { group = "git"; user = "git"; mode = "775"; age = "-"; }; }; modules.services = { resticBackup.paths = { gitolite = { enable = true; user = "git"; passwordFile = config.sops.secrets."resticPasswords/gitolite".path; paths = [ config.modules.services.gitolite.dataDir ]; }; }; gitolite = { enable = true; user = "git"; group = "git"; adminPubkey = cfg.adminPubkey; extraGitoliteRc = '' $RC{GIT_CONFIG_KEYS} = ".*"; $RC{UMASK} = 0027; push(@{$RC{ENABLE}}, 'cgit'); push(@{$RC{ENABLE}}, 'symbolic-ref'); push(@{$RC{ENABLE}}, 'rebuild-webdir'); push(@{$RC{ENABLE}}, 'rebuild-webdir'); $RC{NON_CORE} = "rebuild-webdir-trigger POST_COMPILE rebuild-stagit"; ''; triggers.rebuild-webdir = rebuildWebdir; commands.rebuild-webdir = rebuildWebdir; commonHooks.post-receive = '' # update stagit pages source ${stagitFunctions} update_stagit_repo "$1" ''; }; }; services = { fcgiwrap = { instances.git = { process.user = "git"; process.group = "git"; socket.user = "nginx"; socket.group = "nginx"; }; }; nginx = { enable = true; virtualHosts = { "cgit.ctu.cx" = { useACMEHost = "${config.networking.fqdn}"; forceSSL = true; kTLS = true; locations = { "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".return = "307 https://${cfg.subdomain}.${cfg.domain}$request_uri"; "~ '^/([a-zA-Z0-9_.]+)/*$'".return = "307 https://${cfg.subdomain}.${cfg.domain}/$1"; "~ '^/([a-zA-Z0-9_.]+)/tree/([a-zA-Z0-9_./-]+[a-zA-Z0-9_-])/*$'".return = "307 https://${cfg.subdomain}.${cfg.domain}/$1/tree/$2.html"; "~ '^/([a-zA-Z0-9_.]+)/tree/*$'".return = "307 https://${cfg.subdomain}.${cfg.domain}/$1/tree.html"; "~ '^/([a-zA-Z0-9_.]+)/log/*$'".return = "307 https://${cfg.subdomain}.${cfg.domain}/$1/log.html"; "~ '^/([a-zA-Z0-9_.]+)/commit/*$'".extraConfig = '' if ($arg_id) { return 307 https://${cfg.subdomain}.${cfg.domain}/$1/commit/$arg_id.html; } return 307 https://${cfg.subdomain}.${cfg.domain}/$1/log.html; ''; }; }; "git.ctu.cx" = { serverAliases = [ "git.katja.wtf" ]; useACMEHost = "${config.networking.fqdn}"; forceSSL = true; kTLS = true; locations."/".return = "307 https://${cfg.subdomain}.${cfg.domain}$request_uri"; }; "${cfg.subdomain}.${cfg.domain}" = { useACMEHost = "${config.networking.fqdn}"; forceSSL = true; kTLS = true; root = "/var/lib/stagit"; locations = { "@redir".return = "307 ../log.html"; "~ '^/([a-zA-Z0-9_.]+)/commit/.*$'".extraConfig = "error_page 404 = @redir;"; "~* \.html$".extraConfig = '' add_header Last-Modified $date_gmt; add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; if_modified_since off; expires off; etag off; ''; "~ '^/[a-zA-Z0-9._-]+/raw'".extraConfig = '' types { application/json json; application/wasm wasm; font/woff woff; font/woff2 woff2; application/pdf pdf; image/gif gif; image/jpeg jpeg jpg; image/png png; image/svg+xml svg svgz; image/webp webp; image/x-icon ico; } default_type text/plain; try_files $uri =404; ''; "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".extraConfig = '' if ($query_string = service=git-receive-pack) { return 403; } include "${pkgs.nginx}/conf/fastcgi_params"; fastcgi_param SCRIPT_FILENAME "${pkgs.git}/libexec/git-core/git-http-backend"; fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; fastcgi_param PATH_INFO $uri; fastcgi_pass unix:${config.services.fcgiwrap.instances.git.socket.address}; ''; }; }; }; }; }; } ); }