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 }