Commit 9bdd6b26e335

Vincent Demeester <vincent@sbr.pm>
2026-06-08 20:33:44
feat(carthage): serve git.sbr.pm over smart HTTP
Cloning from git.sbr.pm failed because the host only served the bare repos as static files (dumb HTTP). Shallow clones were unsupported and full clones tripped the caddy-scan fail2ban jail via 404 bursts. Added a git-http-backend fcgiwrap instance and routed *.git paths to it for read-only smart-HTTP clones, kept gitmal static browsing for the rest, and whitelisted git.sbr.pm in the caddy-scan filter.
1 parent f8c7fc4
Changed files (1)
systems
carthage
systems/carthage/extra.nix
@@ -199,10 +199,12 @@ in
     '';
 
     # Ban IPs that trigger excessive 404s (scanning for vulnerabilities)
+    # Exclude git.sbr.pm: dumb-HTTP git clones probe many loose objects,
+    # generating bursts of 404s that look like scanning but are legitimate.
     "fail2ban/filter.d/caddy-scan.conf".text = ''
       [Definition]
       failregex = ^.*"remote_ip":"<HOST>".*"status":404,.*$
-      ignoreregex =
+      ignoreregex = ^.*"host":"git\.sbr\.pm".*$
       datepattern = "ts":{EPOCH}
     '';
 
@@ -716,6 +718,21 @@ in
     BindReadOnlyPaths = [ "/home/vincent/git/public" ];
   };
 
+  # fcgiwrap instance hosting git-http-backend for smart-HTTP git clones.
+  # Runs as vincent because /home/vincent/git is mode 0700 (only vincent can
+  # traverse it). The socket is owned by caddy so Caddy can reach it.
+  services.fcgiwrap.instances.git = {
+    process.user = "vincent";
+    process.group = "users";
+    socket = {
+      type = "unix";
+      address = "/run/fcgiwrap-git.sock";
+      user = "caddy";
+      group = "caddy";
+      mode = "0660";
+    };
+  };
+
   services.caddy = {
     enable = true;
     email = "vincent@sbr.pm";
@@ -1095,14 +1112,33 @@ in
         ${securityHeaders}
       '';
 
-      # Self-hosted git repositories (public only)
+      # Self-hosted git repositories (public only).
+      # Smart-HTTP clones/fetches are served by git-http-backend via fcgiwrap;
+      # everything else falls through to gitmal static HTML browsing.
       "git.sbr.pm".extraConfig = ''
         ${blockAIBotsSnippet}
         ${robotsTxtSnippet}
 
-        root * /home/vincent/git/public
-        file_server browse {
-          hide .fancyindex README.md HEADER.md
+        # Route git smart-HTTP operations to git-http-backend (read-only;
+        # pushes still go over SSH). Matches any *.git request path.
+        @git path_regexp git ^/.+\.git(/.*)?$
+        handle @git {
+          reverse_proxy unix//run/fcgiwrap-git.sock {
+            transport fastcgi {
+              env SCRIPT_FILENAME ${pkgs.git}/bin/git-http-backend
+              env GIT_PROJECT_ROOT /home/vincent/git/public
+              env GIT_HTTP_EXPORT_ALL "1"
+              env PATH_INFO {http.request.uri.path}
+            }
+          }
+        }
+
+        # Static gitmal HTML views and directory browsing
+        handle {
+          root * /home/vincent/git/public
+          file_server browse {
+            hide .fancyindex README.md HEADER.md
+          }
         }
 
         ${gitSecurityHeaders}