config

NixOS config
Log | Files | Refs | README | LICENSE

default.nix (5834B) - Raw


      1 {
      2   config,
      3   lib,
      4   pkgs,
      5   myData,
      6   ...
      7 }:
      8 let
      9   cfg = config.mj.services.git;
     10   cacheDir = "${cfg.wwwDir}/.cache";
     11   dirtyDir = "${cfg.wwwDir}/.dirty";
     12 
     13   stagit = pkgs.stagit.overrideAttrs {
     14     src = builtins.fetchGit {
     15       url = "https://git.jakstys.lt/motiejus/stagit.git";
     16       ref = "master";
     17       rev = "54d688765453529364644ed90f37543eb71a65f4";
     18     };
     19   };
     20   stagitAssets = "${pkgs.stagit.src}";
     21 
     22   postReceiveHook = pkgs.writeShellApplication {
     23     name = "post-receive";
     24     runtimeInputs = [
     25       pkgs.coreutils
     26       pkgs.git
     27     ];
     28     text = ''
     29       repo="$(pwd)"
     30       reponame="$(realpath --relative-to="${cfg.repoDir}" "$repo")"
     31       reponame="''${reponame%.git}"
     32 
     33       git update-server-info
     34 
     35       printf '%s\n' "$reponame" >> "${dirtyDir}/queue"
     36     '';
     37   };
     38 
     39   regenScript = pkgs.writeShellApplication {
     40     name = "stagit-regen";
     41     runtimeInputs = [
     42       pkgs.coreutils
     43       pkgs.findutils
     44       pkgs.git
     45       stagit
     46     ];
     47     text = ''
     48       if [ -f "${dirtyDir}/queue.work" ]; then
     49         cat "${dirtyDir}/queue.work" >> "${dirtyDir}/queue" 2>/dev/null || true
     50       fi
     51       mv "${dirtyDir}/queue" "${dirtyDir}/queue.work" 2>/dev/null || exit 0
     52 
     53       declare -A repos
     54       declare -A orgs
     55 
     56       while IFS= read -r reponame; do
     57         repos["$reponame"]=1
     58         orgs["''${reponame%%/*}"]=1
     59       done < "${dirtyDir}/queue.work"
     60 
     61       for reponame in "''${!repos[@]}"; do
     62         repo="${cfg.repoDir}/''${reponame}.git"
     63         [ -d "$repo" ] || continue
     64 
     65         outdir="${cfg.wwwDir}/$reponame"
     66         mkdir -p "$outdir"
     67         cachefile="${cacheDir}/$reponame"
     68         mkdir -p "$(dirname "$cachefile")"
     69 
     70         (cd "$outdir" && stagit -c "$cachefile" -T${toString cfg.threads} "$repo") || continue
     71 
     72         if [ ! -f "$outdir/index.html" ]; then
     73           ln -sf log.html "$outdir/index.html"
     74         fi
     75 
     76         for f in style.css favicon.png logo.png; do
     77           cp -f "${stagitAssets}/$f" "$outdir/$f"
     78         done
     79       done
     80 
     81       for orgname in "''${!orgs[@]}"; do
     82         mkdir -p "${cfg.wwwDir}/$orgname"
     83         tmpidx=$(mktemp "${cfg.wwwDir}/''${orgname}/index.html.XXXXXX")
     84         for r in "${cfg.repoDir}/''${orgname}"/*.git; do
     85           [ -d "$r" ] || continue
     86           printf '%s %s\n' "$(git -C "$r" log -1 --format=%ct 2>/dev/null || echo 0)" "$r"
     87         done | sort -rn | cut -d' ' -f2- | xargs -r stagit-index > "$tmpidx"
     88         mv "$tmpidx" "${cfg.wwwDir}/''${orgname}/index.html"
     89 
     90         for f in style.css favicon.png logo.png; do
     91           cp -f "${stagitAssets}/$f" "${cfg.wwwDir}/''${orgname}/$f"
     92         done
     93       done
     94 
     95       rm -f "${dirtyDir}/queue.work"
     96     '';
     97   };
     98 
     99   newRepo = pkgs.writeShellApplication {
    100     name = "git-new-repo";
    101     runtimeInputs = with pkgs; [
    102       coreutils
    103       git
    104     ];
    105     text = ''
    106       if [ $# -lt 1 ] || [ $# -gt 2 ]; then
    107         echo "Usage: git-new-repo <org/name> [description]" >&2
    108         exit 1
    109       fi
    110 
    111       repopath="${cfg.repoDir}/$1.git"
    112 
    113       if [ ! -d "$repopath" ]; then
    114         mkdir -p "$(dirname "$repopath")"
    115         git init --bare "$repopath"
    116         echo "Created $repopath"
    117       fi
    118 
    119       git config -f "$repopath/config" core.sharedRepository 0644
    120 
    121       if [ -n "''${2:-}" ]; then
    122         printf '%s\n' "$2" > "$repopath/description"
    123       fi
    124 
    125       ln -sf "${cfg.repoDir}/.post-receive-hook" "$repopath/hooks/post-receive"
    126     '';
    127   };
    128 in
    129 {
    130   options.mj.services.git = with lib.types; {
    131     enable = lib.mkEnableOption "git web hosting with stagit";
    132     repoDir = lib.mkOption { type = str; };
    133     wwwDir = lib.mkOption { type = str; };
    134     threads = lib.mkOption {
    135       type = int;
    136       default = 0;
    137       description = "Number of threads for stagit blob/tree generation (0 = auto-detect).";
    138     };
    139     sshKeys = lib.mkOption {
    140       type = listOf str;
    141       default = [ ];
    142     };
    143   };
    144 
    145   config = lib.mkIf cfg.enable {
    146     assertions = [
    147       {
    148         assertion = !config.mj.services.gitea.enable;
    149         message = "git and gitea cannot be enabled simultaneously (both define the git user)";
    150       }
    151     ];
    152 
    153     users.users.git = {
    154       description = "Git";
    155       home = cfg.repoDir;
    156       shell = "${pkgs.git}/bin/git-shell";
    157       group = "git";
    158       isSystemUser = true;
    159       createHome = true;
    160       homeMode = "755";
    161       uid = myData.uidgid.gitea;
    162       openssh.authorizedKeys.keys = cfg.sshKeys;
    163     };
    164 
    165     users.groups.git.gid = myData.uidgid.gitea;
    166 
    167     services.openssh.extraConfig = ''
    168       AcceptEnv GIT_PROTOCOL
    169     '';
    170 
    171     systemd.tmpfiles.rules = [
    172       "d ${cfg.wwwDir} 0755 git git -"
    173       "d ${dirtyDir} 0755 git git -"
    174       "L+ ${cfg.repoDir}/.post-receive-hook - - - - ${postReceiveHook}/bin/post-receive"
    175     ];
    176 
    177     systemd.services.stagit-regen = {
    178       description = "Regenerate stagit HTML pages";
    179       serviceConfig = {
    180         Type = "oneshot";
    181         User = "git";
    182         Group = "git";
    183         ExecStart = "${regenScript}/bin/stagit-regen";
    184       };
    185     };
    186 
    187     systemd.paths.stagit-regen = {
    188       description = "Watch for stagit regeneration triggers";
    189       pathConfig.PathExists = "${dirtyDir}/queue";
    190       wantedBy = [ "multi-user.target" ];
    191     };
    192 
    193     environment.systemPackages = [ newRepo ];
    194 
    195     services.caddy.virtualHosts."git.jakstys.lt".extraConfig = ''
    196       header {
    197         Strict-Transport-Security "max-age=15768000"
    198         Content-Security-Policy "default-src 'none'; style-src 'self'; img-src 'self'"
    199         X-Content-Type-Options "nosniff"
    200         X-Frame-Options "DENY"
    201         Alt-Svc "h3=\":443\"; ma=86400"
    202       }
    203 
    204       route {
    205         redir / /motiejus/ 302
    206 
    207         @git_clone path_regexp \.git/
    208         handle @git_clone {
    209           root * ${cfg.repoDir}
    210           file_server
    211         }
    212         handle {
    213           root * ${cfg.wwwDir}
    214           file_server
    215         }
    216       }
    217     '';
    218   };
    219 }