Initial commit
diff --git a/dockerimg/githttp.go b/dockerimg/githttp.go
new file mode 100644
index 0000000..d618ab6
--- /dev/null
+++ b/dockerimg/githttp.go
@@ -0,0 +1,53 @@
+package dockerimg
+
+import (
+	"fmt"
+	"log/slog"
+	"net/http"
+	"net/http/cgi"
+	"os/exec"
+	"strings"
+)
+
+type gitHTTP struct {
+	gitRepoRoot string
+}
+
+func (g *gitHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	defer func() {
+		if err := recover(); err != nil {
+			slog.ErrorContext(r.Context(), "gitHTTP.ServeHTTP panic", slog.Any("recovered_err", err))
+
+			// Return an error response to the client
+			http.Error(w, fmt.Sprintf("panic: %v\n", err), http.StatusInternalServerError)
+		}
+	}()
+	if !strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
+		slog.InfoContext(r.Context(), "githttp: denied", "remote addr", r.RemoteAddr)
+		http.Error(w, "no", http.StatusUnauthorized)
+		return
+	}
+	gitBin, err := exec.LookPath("git")
+	if err != nil {
+		http.Error(w, "no git: "+err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Cache-Control", "no-cache")
+	h := &cgi.Handler{
+		Path: gitBin,
+		Args: []string{"http-backend"},
+		Dir:  g.gitRepoRoot,
+		Env: []string{
+			"GIT_PROJECT_ROOT=" + g.gitRepoRoot,
+			"PATH_INFO=" + r.URL.Path,
+			"QUERY_STRING=" + r.URL.RawQuery,
+			"REQUEST_METHOD=" + r.Method,
+			"GIT_HTTP_EXPORT_ALL=true",
+			"GIT_HTTP_ALLOW_REPACK=true",
+			"GIT_HTTP_ALLOW_PUSH=true",
+			"GIT_HTTP_VERBOSE=1",
+		},
+	}
+	h.ServeHTTP(w, r)
+}