Antonio Mika
·
2026-10-25
1package main
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "log/slog"
8 "net"
9 "net/http"
10 "net/url"
11 "strings"
12
13 "github.com/picosh/pico/pkg/apps/pgs"
14 "github.com/picosh/pico/pkg/metrics"
15 "github.com/picosh/pico/pkg/shared"
16 "github.com/prometheus/client_golang/prometheus"
17 "github.com/prometheus/client_golang/prometheus/promhttp"
18)
19
20func main() {
21 pipeEnabled := shared.GetEnv("PICO_PIPE_ENABLED", "true")
22 withPipe := strings.ToLower(pipeEnabled) == "true"
23 logger := shared.CreateLogger("pgs-cdn", withPipe)
24 ctx := context.Background()
25 drain := pgs.CreateSubCacheDrain(ctx, logger)
26 pubsub := pgs.NewPubsubPipe(drain)
27 defer func() {
28 _ = pubsub.Close()
29 }()
30 cfg := pgs.NewConfig(logger, nil, nil, drain)
31 proxy := newProxyServe(cfg.Logger)
32 httpCache, cache := pgs.NewPgsHttpCache(cfg, proxy)
33 // The CDN can't look users up, but origin fails every site route for a
34 // site that doesn't exist, so a success proves the owner is real.
35 cacher := &cachedHttp{
36 Logger: cfg.Logger,
37 Cache: pgs.NewSiteMetrics(cfg, "pgs-cdn", true).Handler(httpCache),
38 }
39
40 go pgs.CacheMgmt(ctx, cfg, cache, nil)
41 metrics.RegisterServerInfo(prometheus.DefaultRegisterer, "pgs-cdn")
42
43 portStr := fmt.Sprintf(":%s", cfg.WebPort)
44 cfg.Logger.Info(
45 "starting server on port",
46 "port", cfg.WebPort,
47 "domain", cfg.Domain,
48 )
49 err := http.ListenAndServe(portStr, cacher)
50 cfg.Logger.Error("listen and serve", "err", err)
51}
52
53type proxyServe struct {
54 Logger *slog.Logger
55 transport *http.Transport
56}
57
58func newProxyServe(logger *slog.Logger) *proxyServe {
59 defaultTransport := http.DefaultTransport.(*http.Transport)
60 oldDial := defaultTransport.DialContext
61 transport := &http.Transport{
62 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
63 return oldDial(ctx, "tcp", "ash.pgs.sh:443")
64 },
65 }
66 return &proxyServe{Logger: logger, transport: transport}
67}
68
69// Headers that should be stripped from upstream responses because the CDN's
70// cache layer will add its own versions.
71var stripHeaders = map[string]bool{
72 "age": true,
73 "cache-status": true,
74 "date": true,
75}
76
77func (p *proxyServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
78 // Dial ash.pgs.sh but keep the original Host header so ash.pgs.sh
79 // can route the request to the correct subdomain (zmx.sh, etc.).
80 // Without the Host header, ash.pgs.sh serves its default vhost (HTML).
81 target, _ := url.Parse("https://ash.pgs.sh" + req.URL.Path)
82 if req.URL.RawQuery != "" {
83 target.RawQuery = req.URL.RawQuery
84 }
85 p.Logger.Info("proxying request to ash.pgs.sh", "url", target.String())
86
87 proxyReq := req.Clone(req.Context())
88 proxyReq.URL.Scheme = target.Scheme
89 proxyReq.URL.Host = target.Host
90 proxyReq.URL.Path = target.Path
91 proxyReq.URL.RawQuery = target.RawQuery
92 proxyReq.RequestURI = ""
93 // Prevent the upstream from returning a compressed body. The CDN cache
94 // stores a single representation per URL; Caddy handles per-client
95 // encoding on the way out. If we forward Accept-Encoding, origin may
96 // return zstd-compressed bytes that get cached and then served to
97 // clients that never requested zstd.
98 proxyReq.Header.Del("Accept-Encoding")
99 // Preserve the original Host header so ash.pgs.sh routes correctly.
100 proxyReq.Host = req.Host
101
102 resp, err := p.transport.RoundTrip(proxyReq)
103 if err != nil {
104 http.Error(w, err.Error(), http.StatusBadGateway)
105 return
106 }
107 defer func() {
108 _ = resp.Body.Close()
109 }()
110
111 // Copy headers from upstream, but strip cache-related headers that the
112 // CDN's cache layer will regenerate
113 for k, vals := range resp.Header {
114 if stripHeaders[strings.ToLower(k)] {
115 continue
116 }
117 w.Header()[k] = vals
118 }
119 w.WriteHeader(resp.StatusCode)
120 _, _ = io.Copy(w, resp.Body)
121}
122
123type cachedHttp struct {
124 Logger *slog.Logger
125 Cache http.Handler
126}
127
128func (c *cachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
129 if req.URL.Path == "/_metrics" {
130 promhttp.Handler().ServeHTTP(writer, req)
131 return
132 }
133
134 if req.URL.Path == "/check" {
135 c.Logger.Info("proxying `/check` request to ash.pgs.sh", "query", req.URL.RawQuery)
136 req, _ := http.NewRequest("GET", "https://ash.pgs.sh/check?"+req.URL.RawQuery, nil)
137 req.Host = "pgs.sh"
138 // reqDump, _ := httputil.DumpRequestOut(req, true)
139 // fmt.Printf("REQUEST:\n%s", string(reqDump))
140
141 resp, err := http.DefaultClient.Do(req)
142 if err != nil {
143 c.Logger.Error("check request", "err", err)
144 }
145 defer func() {
146 _ = resp.Body.Close()
147 }()
148 writer.WriteHeader(resp.StatusCode)
149 _, _ = io.Copy(writer, resp.Body)
150 return
151 }
152
153 c.Cache.ServeHTTP(writer, req)
154}