fix: fix build

gg582 committed May 4, 2026 at 23:34 UTC 2a604767921fcbea44914b640718af91b8ef9757
4 files changed +109 -32
go.mod
+12 -2
@@ -1,6 +1,6 @@
1 module github.com/gosuda/portal-tunnel/v2
2
3 -go 1.26.2
3 +go 1.25.8
4
5 require (
6 cloud.google.com/go/compute/metadata v0.9.0
@@ -18,6 +18,10 @@ require (
18 github.com/go-rod/rod v0.116.2
19 github.com/gosuda/keyless_tls v0.0.1-0.20260304212324-7733f8366abc
20 github.com/hashicorp/yamux v0.1.2
21 + github.com/knadh/koanf/parsers/toml/v2 v2.2.0
22 + github.com/knadh/koanf/providers/file v1.2.1
23 + github.com/knadh/koanf/v2 v2.3.4
24 + github.com/montanaflynn/stats v0.9.0
25 github.com/prometheus/client_golang v1.23.2
26 github.com/prometheus/client_model v0.6.2
27 github.com/quic-go/quic-go v0.59.0
@@ -48,6 +52,7 @@ require (
52 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
53 github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
54 github.com/aws/smithy-go v1.24.2 // indirect
55 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
56 github.com/beorn7/perks v1.0.1 // indirect
57 github.com/cenkalti/backoff/v5 v5.0.3 // indirect
58 github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -79,8 +84,13 @@ require (
84 github.com/mattn/go-localereader v0.0.1 // indirect
85 github.com/mattn/go-runewidth v0.0.19 // indirect
86 github.com/miekg/dns v1.1.72 // indirect
82 - github.com/montanaflynn/stats v0.9.0 // indirect
87 + github.com/mitchellh/copystructure v1.2.0 // indirect
88 + github.com/mitchellh/reflectwalk v1.0.2 // indirect
89 + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
90 + github.com/muesli/cancelreader v0.2.2 // indirect
91 + github.com/muesli/termenv v0.16.0 // indirect
92 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
93 + github.com/pelletier/go-toml/v2 v2.2.4 // indirect
94 github.com/prometheus/common v0.66.1 // indirect
95 github.com/prometheus/procfs v0.16.1 // indirect
96 github.com/relvacode/iso8601 v1.1.1-0.20210511065120-b30b151cc433 // indirect
go.sum
+30
@@ -40,6 +40,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBU
40 github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
41 github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
42 github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
43 +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
44 +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
45 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
46 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
47 github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
@@ -117,10 +119,24 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
119 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
120 github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
121 github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
122 +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
123 +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
124 +github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
125 +github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
126 +github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A=
127 +github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI=
128 +github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM=
129 +github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
130 +github.com/knadh/koanf/v2 v2.3.4 h1:fnynNSDlujWE+v83hAp8wKr/cdoxHLO0629SN+U8Urc=
131 +github.com/knadh/koanf/v2 v2.3.4/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
132 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
133 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
134 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
135 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
136 +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
137 +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
138 +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
139 +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
140 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
141 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
142 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -133,10 +149,22 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF
149 github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
150 github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
151 github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
152 +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
153 +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
154 +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
155 +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
156 github.com/montanaflynn/stats v0.9.0 h1:tsBJ0RXwph9BmAuFoCmqGv6e8xa0MENQ8m0ptKq29mQ=
157 github.com/montanaflynn/stats v0.9.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
158 +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
159 +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
160 +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
161 +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
162 +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
163 +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
164 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
165 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
166 +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
167 +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
168 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
169 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
170 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -152,6 +180,8 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
180 github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
181 github.com/relvacode/iso8601 v1.1.1-0.20210511065120-b30b151cc433 h1:mLbKGKe5gDGHE8uJLYMmA/fkp/htaXEMl2Hj0k4xfYE=
182 github.com/relvacode/iso8601 v1.1.1-0.20210511065120-b30b151cc433/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
183 +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
184 +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
185 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
186 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
187 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
portal/api_server.go
+2 -1
@@ -46,7 +46,8 @@ var (
46 )
47
48 func writeAPIErrorResponse(w http.ResponseWriter, err error) {
49 - if ae, ok := errors.AsType[*apiError](err); ok {
49 + var ae *apiError
50 + if errors.As(err, &ae) {
51 utils.WriteAPIError(w, ae.status, ae.code, ae.msg)
52 return
53 }
portal/discovery/mols.go
+65 -29
@@ -263,43 +263,79 @@ func (p MOLSRelayPolicy) rankRelayPool(autoPool []RelayState, localAddress strin
263 return molsScore(int(ingressIdx)%order, idx, int(m1), int(m2), order)
264 }
265
266 - var candidates [molsCandidateDepth]molsCandidate
267 - candidateCount := 0
268 - for i, state := range autoPool {
269 - state.EvaluateSaturation()
270 - candidate := molsCandidate{
271 - state: state,
272 - score: scoreFor(state),
273 - seq: i,
266 + // Partition by fallback status and promote to meet minimum active nodes.
267 + activeStates := make([]RelayState, 0, len(autoPool))
268 + fallbackStates := make([]RelayState, 0)
269 + for _, s := range autoPool {
270 + if isRelayFallback(s) {
271 + fallbackStates = append(fallbackStates, s)
272 + } else {
273 + activeStates = append(activeStates, s)
274 }
275 - insertAt := candidateCount
276 - for insertAt > 0 && betterMOLSCandidate(candidate, candidates[insertAt-1]) {
277 - if insertAt < molsCandidateDepth {
278 - candidates[insertAt] = candidates[insertAt-1]
275 + }
276 +
277 + if len(activeStates) < molsMinActiveNodes && len(fallbackStates) > 0 {
278 + slices.SortFunc(fallbackStates, func(a, b RelayState) int {
279 + if a.DiscoveryRTT < b.DiscoveryRTT {
280 + return -1
281 }
280 - insertAt--
281 - }
282 - if insertAt >= molsCandidateDepth {
283 - continue
282 + if a.DiscoveryRTT > b.DiscoveryRTT {
283 + return 1
284 + }
285 + return 0
286 + })
287 + promote := min(molsMinActiveNodes-len(activeStates), len(fallbackStates))
288 + activeStates = append(activeStates, fallbackStates[:promote]...)
289 + fallbackStates = fallbackStates[promote:]
290 + }
291 +
292 + rankTier := func(states []RelayState) []string {
293 + if len(states) == 0 {
294 + return nil
295 }
285 - candidates[insertAt] = candidate
286 - if candidateCount < molsCandidateDepth {
287 - candidateCount++
296 + var candidates [molsCandidateDepth]molsCandidate
297 + count := 0
298 + for i, state := range states {
299 + state.EvaluateSaturation()
300 + candidate := molsCandidate{
301 + state: state,
302 + score: scoreFor(state),
303 + seq: i,
304 + }
305 + insertAt := count
306 + for insertAt > 0 && betterMOLSCandidate(candidate, candidates[insertAt-1]) {
307 + if insertAt < molsCandidateDepth {
308 + candidates[insertAt] = candidates[insertAt-1]
309 + }
310 + insertAt--
311 + }
312 + if insertAt >= molsCandidateDepth {
313 + continue
314 + }
315 + candidates[insertAt] = candidate
316 + if count < molsCandidateDepth {
317 + count++
318 + }
319 }
289 - }
320
291 - out := make([]string, 0, candidateCount)
292 - for i := 0; i < candidateCount; i++ {
293 - if !candidates[i].state.IsSaturated {
294 - out = append(out, candidates[i].state.Descriptor.APIHTTPSAddr)
321 + tierOut := make([]string, 0, count)
322 + for i := 0; i < count; i++ {
323 + if !candidates[i].state.IsSaturated {
324 + tierOut = append(tierOut, candidates[i].state.Descriptor.APIHTTPSAddr)
325 + }
326 }
296 - }
297 - for i := 0; i < candidateCount; i++ {
298 - if candidates[i].state.IsSaturated {
299 - out = append(out, candidates[i].state.Descriptor.APIHTTPSAddr)
327 + for i := 0; i < count; i++ {
328 + if candidates[i].state.IsSaturated {
329 + tierOut = append(tierOut, candidates[i].state.Descriptor.APIHTTPSAddr)
330 + }
331 }
332 + return tierOut
333 }
302 - return out
334 +
335 + activeURLs := rankTier(activeStates)
336 + fallbackURLs := rankTier(fallbackStates)
337 +
338 + return append(activeURLs, fallbackURLs...)
339 }
340
341 // SelectPriorityWithTrace is the telemetry-instrumented sibling of