From 83476d0ad1cf52e9cef20a04c0dcb39be98e3e6f Mon Sep 17 00:00:00 2001 From: Catherine Date: Sat, 17 Oct 2026 20:30:06 +0000 Subject: [PATCH] [breaking-change] Rename `caddy` listener to `proxy` and expose domains. The listener is no longer only used for Caddy integration; it is now a general purpose reverse proxy endpoint that can be used with e.g. Traefik terminating TLS. Fixes #260. --- conf/config.default.toml | 2 +- conf/config.docker.toml | 2 +- conf/config.example.toml | 2 +- src/config.go | 2 +- src/main.go | 4 ++-- src/{caddy.go => proxy.go} | 39 +++++++++++++++++++++++++++++--------- src/util.go | 4 ++++ 7 files changed, 40 insertions(+), 15 deletions(-) rename src/{caddy.go => proxy.go} (69%) diff --git a/conf/config.default.toml b/conf/config.default.toml index 1e09f3b..1adb56a 100644 --- a/conf/config.default.toml +++ b/conf/config.default.toml @@ -5,7 +5,7 @@ log-format = 'text' [server] pages = 'tcp/localhost:3000' -caddy = 'tcp/localhost:3001' +proxy = 'tcp/localhost:3001' metrics = 'tcp/localhost:3002' [storage] diff --git a/conf/config.docker.toml b/conf/config.docker.toml index 755fe5a..746e1b4 100644 --- a/conf/config.docker.toml +++ b/conf/config.docker.toml @@ -1,4 +1,4 @@ [server] pages = "tcp/:3000" -caddy = "tcp/:3001" +proxy = "tcp/:3001" metrics = "tcp/:3002" diff --git a/conf/config.example.toml b/conf/config.example.toml index 94b5237..def1d1d 100644 --- a/conf/config.example.toml +++ b/conf/config.example.toml @@ -6,7 +6,7 @@ log-format = "text" [server] # Use "-" to disable the handler. pages = "tcp/localhost:3000" -caddy = "tcp/localhost:3001" +proxy = "tcp/localhost:3001" metrics = "tcp/localhost:3002" [[wildcard]] # non-default section diff --git a/src/config.go b/src/config.go index c446f50..d2feac8 100644 --- a/src/config.go +++ b/src/config.go @@ -74,7 +74,7 @@ type Config struct { type ServerConfig struct { Pages string `toml:"pages" default:"tcp/localhost:3000"` - Caddy string `toml:"caddy" default:"tcp/localhost:3001"` + Proxy string `toml:"proxy" default:"tcp/localhost:3001"` Metrics string `toml:"metrics" default:"tcp/localhost:3002"` } diff --git a/src/main.go b/src/main.go index ee5b3bf..9473ed6 100644 --- a/src/main.go +++ b/src/main.go @@ -886,7 +886,7 @@ func Main(versionInfo string) { // with git-pages on startup and return errors for requests that would have been served // just 0.5s later. pagesListener := listen(ctx, "pages", config.Server.Pages) - caddyListener := listen(ctx, "caddy", config.Server.Caddy) + proxyListener := listen(ctx, "caddy", config.Server.Proxy) metricsListener := listen(ctx, "metrics", config.Server.Metrics) if backend, err = CreateBackend(ctx, &config.Storage); err != nil { @@ -904,7 +904,7 @@ func Main(versionInfo string) { ObserveHTTPHandler, ) go serve(ctx, pagesListener, middleware(http.HandlerFunc(ServePages))) - go serve(ctx, caddyListener, middleware(http.HandlerFunc(ServeCaddy))) + go serve(ctx, proxyListener, middleware(http.HandlerFunc(ServeProxy))) go serve(ctx, metricsListener, promhttp.Handler()) if config.Insecure { diff --git a/src/caddy.go b/src/proxy.go similarity index 69% rename from src/caddy.go rename to src/proxy.go index de96aa9..f7e7e8f 100644 --- a/src/caddy.go +++ b/src/proxy.go @@ -3,25 +3,46 @@ package git_pages import ( "context" "crypto/tls" + "encoding/json" "fmt" + "maps" "net" "net/http" + "slices" "strings" ) -func ServeCaddy(w http.ResponseWriter, r *http.Request) { +func ServeProxy(w http.ResponseWriter, r *http.Request) { domain := r.URL.Query().Get("domain") if domain == "" { - http.Error(w, "domain parameter required", http.StatusBadRequest) - return + proxyListDomains(w, r) + } else { + proxyCheckDomain(w, r, domain) + } +} + +func proxyListDomains(w http.ResponseWriter, r *http.Request) { + domainSet := map[string]unit{} + for metadata, err := range backend.EnumerateManifests(r.Context()) { + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "%s", err) + } + domain, _, _ := strings.Cut(metadata.Name, "/") + domainSet[domain] = Unit } + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(slices.Collect(maps.Keys(domainSet))) +} + +func proxyCheckDomain(w http.ResponseWriter, r *http.Request, domain string) { // Save the backend some effort from queries that are essentially guaranteed to fail. // While TLS certificates may be provisionsed for IP addresses under special circumstances[^1], // this isn't really what git-pages is designed for, and object store accesses can cost money. // [^1]: https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate if ip := net.ParseIP(domain); ip != nil { - logc.Println(r.Context(), "caddy:", domain, 404, "(bare IP)") + logc.Println(r.Context(), "proxy:", domain, 404, "(bare IP)") w.WriteHeader(http.StatusNotFound) return } @@ -46,18 +67,18 @@ func ServeCaddy(w http.ResponseWriter, r *http.Request) { // thankfully making it unnecessary to examine X.509 certificates manually here.) found, err = tryDialWithSNI(r.Context(), domain) if err != nil { - logc.Printf(r.Context(), "caddy err: check SNI: %s\n", err) + logc.Printf(r.Context(), "proxy err: check SNI: %s\n", err) } } if found { - logc.Println(r.Context(), "caddy:", domain, 200) + logc.Println(r.Context(), "proxy:", domain, 200) w.WriteHeader(http.StatusOK) } else if err == nil { - logc.Println(r.Context(), "caddy:", domain, 404) + logc.Println(r.Context(), "proxy:", domain, 404) w.WriteHeader(http.StatusNotFound) } else { - logc.Println(r.Context(), "caddy:", domain, 500) + logc.Println(r.Context(), "proxy:", domain, 500) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintln(w, err) } @@ -78,7 +99,7 @@ func tryDialWithSNI(ctx context.Context, domain string) (bool, error) { connectPort = "443" } - logc.Printf(ctx, "caddy: check TLS %s", fallbackURL) + logc.Printf(ctx, "proxy: check TLS %s", fallbackURL) dialer := tls.Dialer{Config: &tls.Config{ServerName: domain}} connection, err := dialer.DialContext(ctx, "tcp", net.JoinHostPort(connectHost, connectPort)) if err != nil { diff --git a/src/util.go b/src/util.go index 4d40276..e89e2d2 100644 --- a/src/util.go +++ b/src/util.go @@ -6,6 +6,10 @@ import ( "strings" ) +type unit struct{} + +var Unit unit = unit{} + type tuple[A, B any] struct { A A B B