Skip to content

Commit b032d22

Browse files
committed
chore: tidy & metrics
1 parent 97e6408 commit b032d22

File tree

4 files changed

+184
-188
lines changed

4 files changed

+184
-188
lines changed

github_proxy.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"log/slog"
7+
"net/http"
8+
"net/url"
9+
"path"
10+
"strconv"
11+
"strings"
12+
"sync"
13+
"time"
14+
15+
"github.com/prometheus/client_golang/prometheus"
16+
"github.com/prometheus/client_golang/prometheus/promauto"
17+
)
18+
19+
var (
20+
metricGithubRedirects = promauto.NewCounter(prometheus.CounterOpts{
21+
Name: "github_proxy_redirects_total",
22+
})
23+
metricGithubRedirectBytes = promauto.NewCounter(prometheus.CounterOpts{
24+
Name: "github_proxy_redirect_bytes_total",
25+
})
26+
metricGithubRedirectAssets = promauto.NewGauge(prometheus.GaugeOpts{
27+
Name: "github_proxy_redirect_assets_loaded",
28+
})
29+
)
30+
31+
type githubRedirector struct {
32+
releasesURLs []string
33+
refreshInterval time.Duration
34+
next http.Handler
35+
36+
mut sync.Mutex
37+
assets map[string]asset
38+
}
39+
40+
type asset struct {
41+
Name string `json:"name"`
42+
BrowserURL string `json:"browser_download_url"`
43+
Size int
44+
}
45+
46+
type release struct {
47+
Tag string `json:"tag_name"`
48+
Assets []asset `json:"assets"`
49+
}
50+
51+
func (r *githubRedirector) Serve(ctx context.Context) error {
52+
slog.Info("starting GitHub redirector")
53+
defer slog.Info("stopping GitHub redirector")
54+
55+
timer := time.NewTimer(0)
56+
defer timer.Stop()
57+
for {
58+
select {
59+
case <-timer.C:
60+
newAssets := make(map[string]asset)
61+
nonUnique := make(map[string]struct{})
62+
for _, url := range r.releasesURLs {
63+
assets, err := r.fetchGithubReleaseAssets(ctx, url)
64+
if err != nil {
65+
return err
66+
}
67+
for key, asset := range assets {
68+
if _, ok := nonUnique[key]; ok {
69+
continue
70+
}
71+
if _, ok := newAssets[key]; ok {
72+
nonUnique[key] = struct{}{}
73+
delete(newAssets, key)
74+
slog.Info("skipping non-unique asset", "key", key)
75+
continue
76+
}
77+
newAssets[key] = asset
78+
}
79+
}
80+
r.mut.Lock()
81+
r.assets = newAssets
82+
r.mut.Unlock()
83+
metricGithubRedirectAssets.Set(float64(len(newAssets)))
84+
timer.Reset(r.refreshInterval)
85+
case <-ctx.Done():
86+
return ctx.Err()
87+
}
88+
}
89+
}
90+
91+
func (r *githubRedirector) ServeHTTP(w http.ResponseWriter, req *http.Request) {
92+
file := path.Base(req.URL.Path)
93+
if unesc, err := url.PathUnescape(file); err == nil {
94+
file = unesc
95+
}
96+
97+
r.mut.Lock()
98+
asset, ok := r.assets[file]
99+
// Special case; tildes become dots in GitHub assets...
100+
if !ok {
101+
asset, ok = r.assets[strings.Replace(file, "~", ".", 1)]
102+
}
103+
r.mut.Unlock()
104+
105+
if !ok {
106+
r.next.ServeHTTP(w, req)
107+
return
108+
}
109+
110+
if r.buggyAPTVersion(req) {
111+
slog.Info("serving proxied for buggy APT", "file", file, "ua", req.Header.Get("User-Agent"))
112+
r.next.ServeHTTP(w, req)
113+
return
114+
}
115+
116+
slog.Info("serving redirect", "file", file, "ua", req.Header.Get("User-Agent"))
117+
http.Redirect(w, req, asset.BrowserURL, http.StatusTemporaryRedirect)
118+
metricGithubRedirects.Inc()
119+
metricGithubRedirectBytes.Add(float64(asset.Size))
120+
}
121+
122+
func (r *githubRedirector) fetchGithubReleaseAssets(ctx context.Context, url string) (map[string]asset, error) {
123+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
124+
if err != nil {
125+
return nil, err
126+
}
127+
resp, err := http.DefaultClient.Do(req)
128+
if err != nil {
129+
return nil, err
130+
}
131+
defer resp.Body.Close()
132+
var rels []release
133+
if err := json.NewDecoder(resp.Body).Decode(&rels); err != nil {
134+
return nil, err
135+
}
136+
137+
assets := make(map[string]asset)
138+
for _, rel := range rels {
139+
for _, asset := range rel.Assets {
140+
assets[asset.Name] = asset
141+
}
142+
}
143+
return assets, nil
144+
}
145+
146+
// buggyAPTVersion returns true for APT versions that can't properly handle
147+
// a redirect to a signed object storage URL.
148+
func (r *githubRedirector) buggyAPTVersion(req *http.Request) bool {
149+
// "Debian APT-CURL/1.0 (1.2.35)"
150+
// "Debian APT-HTTP/1.3 (1.6.18)"
151+
// "Debian APT-HTTP/1.3 (2.0.11) non-interactive"
152+
// "Debian APT-HTTP/1.3 (2.2.4)"
153+
// "Debian APT-HTTP/1.3 (2.4.13)"
154+
fields := strings.Fields(req.Header.Get("User-Agent"))
155+
if len(fields) < 3 {
156+
return false
157+
}
158+
if fields[0] != "Debian" || !strings.HasPrefix(fields[1], "APT-HTTP") {
159+
return false
160+
}
161+
parts := strings.Split(strings.Trim(fields[2], "()"), ".")
162+
if len(parts) < 2 {
163+
return false
164+
}
165+
166+
// Major versions lower than 2 are guaranteed buggy, higher should be
167+
// fine. Precisely equals two requires further investigation.
168+
if maj, err := strconv.ParseInt(parts[0], 10, 32); err != nil {
169+
return false
170+
} else if maj < 2 {
171+
return true
172+
} else if maj > 2 {
173+
return false
174+
}
175+
176+
// Minor versions lower than 2 are buggy.
177+
if min, err := strconv.ParseInt(parts[1], 10, 32); err != nil {
178+
return false
179+
} else if min < 2 {
180+
return true
181+
}
182+
183+
return false
184+
}

go.mod

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,18 @@ go 1.23.4
55
require (
66
calmh.dev/proxy v0.0.0-20250605162626-0ab1901de77d
77
github.com/prometheus/client_golang v1.22.0
8-
github.com/syncthing/syncthing v1.29.7
98
github.com/thejerf/suture/v4 v4.0.6
109
)
1110

1211
require (
1312
github.com/beorn7/perks v1.0.1 // indirect
1413
github.com/cespare/xxhash/v2 v2.3.0 // indirect
15-
github.com/ebitengine/purego v0.8.3 // indirect
16-
github.com/go-ole/go-ole v1.3.0 // indirect
1714
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
18-
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
1915
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
20-
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
2116
github.com/prometheus/client_model v0.6.2 // indirect
2217
github.com/prometheus/common v0.64.0 // indirect
2318
github.com/prometheus/procfs v0.16.1 // indirect
24-
github.com/shirou/gopsutil/v4 v4.25.4 // indirect
25-
github.com/tklauser/go-sysconf v0.3.14 // indirect
26-
github.com/tklauser/numcpus v0.9.0 // indirect
27-
github.com/yusufpapurcu/wmi v1.2.4 // indirect
28-
golang.org/x/net v0.40.0 // indirect
2919
golang.org/x/sync v0.15.0 // indirect
3020
golang.org/x/sys v0.33.0 // indirect
31-
golang.org/x/text v0.25.0 // indirect
3221
google.golang.org/protobuf v1.36.6 // indirect
3322
)

go.sum

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,18 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
66
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
77
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
88
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9-
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
10-
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
11-
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
12-
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
13-
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
149
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
1510
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
16-
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1711
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1812
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1913
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
2014
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
2115
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
2216
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
23-
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
24-
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
2517
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
2618
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
2719
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2820
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
29-
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
30-
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
3121
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
3222
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
3323
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -36,31 +26,14 @@ github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQP
3626
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
3727
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
3828
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
39-
github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw=
40-
github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
4129
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
4230
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
43-
github.com/syncthing/syncthing v1.29.7 h1:Jq+gEDSuNRAwkm+dvH68rwBMLiXJKWPB4Erh8EM2SE4=
44-
github.com/syncthing/syncthing v1.29.7/go.mod h1:fdJbdWqmQsJa7bWrqZJQOAuednqtxCNgNpK8tNQS9fw=
4531
github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8=
4632
github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
47-
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
48-
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
49-
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
50-
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
51-
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
52-
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
53-
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
54-
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
5533
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
5634
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
57-
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
58-
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
59-
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6035
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
6136
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
62-
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
63-
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
6437
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
6538
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
6639
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

0 commit comments

Comments
 (0)