[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.
This commit is contained in:
Catherine
2026-10-17 22:41:19 +02:00
parent 600c8a7848
commit 83476d0ad1
7 changed files with 40 additions and 15 deletions
+1 -1
View File
@@ -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]
+1 -1
View File
@@ -1,4 +1,4 @@
[server]
pages = "tcp/:3000"
caddy = "tcp/:3001"
proxy = "tcp/:3001"
metrics = "tcp/:3002"
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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"`
}
+2 -2
View File
@@ -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 {
+30 -9
View File
@@ -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 {
+4
View File
@@ -6,6 +6,10 @@ import (
"strings"
)
type unit struct{}
var Unit unit = unit{}
type tuple[A, B any] struct {
A A
B B