feat: use embedded bootstrap relays instead of registry.json fetch
Replace the runtime HTTP fetch of registry.json with the embedded bootstrap.relays list from config.toml, eliminating the GitHub round trip and making the relay seed list a build-time invariant. ResolvePortalRelayURLs moves from utils/network.go to utils/utils.go alongside the other relay URL helpers, and drops its now-unused ctx argument. The CLI flag stays --default-relays for backward compat, but the help text is updated to drop the stale "public registry" wording. registry.json and types.PortalRelayRegistryURL are removed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
oesni committed
May 6, 2026 at 00:19 UTC
e2dadc4548372e6959c98d9952dd14beeaac3800
9 files changed
+35
-57
cmd/demo-app/main.go
+2
-2
@@ -53,8 +53,8 @@ type demoConfig struct {
53
// registerConnectivityFlags registers the relay, discovery, identity, and
54
// owner flags that are shared across TCP and UDP demo commands.
55
func registerConnectivityFlags(fs *flag.FlagSet, cfg *demoConfig, defaultRelays string) {
56
- utils.StringFlagEnv(fs, &cfg.relayURLs, "relays", defaultRelays, "additional relay API URLs (comma-separated; scheme omitted defaults to https; merged with public registry relays when discovery is enabled)", "RELAYS")
57
- utils.BoolFlagEnv(fs, &cfg.discovery, "discovery", true, "include public registry relays and enable discovery", "DISCOVERY")
56
+ utils.StringFlagEnv(fs, &cfg.relayURLs, "relays", defaultRelays, "additional relay API URLs (comma-separated; scheme omitted defaults to https; merged with bootstrap relays when discovery is enabled)", "RELAYS")
57
+ utils.BoolFlagEnv(fs, &cfg.discovery, "discovery", true, "include bootstrap relays and enable discovery", "DISCOVERY")
58
utils.BoolFlagEnv(fs, &cfg.banMITM, "ban-mitm", false, "ban relay when the MITM self-probe detects TLS termination", "BAN_MITM")
59
utils.StringFlagEnv(fs, &cfg.identityPath, "identity-path", "identity.json", "identity json file path", "IDENTITY_PATH")
60
utils.StringFlagEnv(fs, &cfg.identityJSON, "identity-json", "", "identity json payload; overrides --identity-path contents and is persisted there when both are set", "IDENTITY_JSON")
cmd/portal-tunnel/main.go
+3
-3
@@ -77,7 +77,7 @@ func runExposeCommand(args []string) error {
77
78
utils.StringFlag(fs, &flags.relayCSV, "relays", "", "Additional Portal relay server API URLs (comma-separated; scheme omitted defaults to https)")
79
utils.StringFlagEnv(fs, &flags.multiHopCSV, "multi-hop", "", "Ordered multi-hop relay API URLs, comma-separated", "MULTI_HOP")
80
- utils.BoolFlag(fs, &flags.discovery, "discovery", true, "Include public registry relays and discover additional relay bootstraps")
80
+ utils.BoolFlag(fs, &flags.discovery, "discovery", true, "Include bootstrap relays and discover additional relays")
81
utils.BoolFlagEnv(fs, &flags.banMITM, "ban-mitm", true, "Ban relay when the MITM self-probe detects TLS termination", "BAN_MITM")
82
utils.StringFlagEnv(fs, &flags.identityPath, "identity-path", "identity.json", "identity json file path", "IDENTITY_PATH")
83
utils.StringFlagEnv(fs, &flags.identityJSON, "identity-json", "", "identity json payload; overrides --identity-path contents and is persisted there when both are set", "IDENTITY_JSON")
@@ -219,7 +219,7 @@ func runListCommand(args []string) error {
219
fs := utils.NewFlagSet("list", printListUsage)
220
221
utils.StringFlag(fs, &flags.relayCSV, "relays", "", "Additional Portal relay server API URLs (comma-separated; scheme omitted defaults to https)")
222
- utils.BoolFlag(fs, &flags.defaultRelays, "default-relays", true, "Include public registry relays")
222
+ utils.BoolFlag(fs, &flags.defaultRelays, "default-relays", true, "Include bootstrap relays")
223
224
if err := utils.ParseFlagSet(fs, args, printListUsage); err != nil {
225
if errors.Is(err, flag.ErrHelp) {
@@ -237,7 +237,7 @@ func runListCommand(args []string) error {
237
238
relayInputs := utils.SplitCSV(flags.relayCSV)
239
240
- relayURLs, err := utils.ResolvePortalRelayURLs(ctx, relayInputs, flags.defaultRelays)
240
+ relayURLs, err := utils.ResolvePortalRelayURLs(relayInputs, flags.defaultRelays)
241
if err != nil {
242
return fmt.Errorf("resolve relay urls: %w", err)
243
}
cmd/relay-server/main.go
+1
-1
@@ -69,7 +69,7 @@ func runServeCommand(args []string) error {
69
70
utils.StringFlagEnv(fs, &cfg.PortalURL, "portal-url", "https://localhost:4017", "portal base URL", "PORTAL_URL")
71
utils.StringFlagEnv(fs, &cfg.IdentityPath, "identity-path", "./.portal-certs", "directory path for relay identity, admin state, and keyless materials", "IDENTITY_PATH")
72
- utils.StringFlagEnv(fs, &cfg.Bootstraps, "bootstraps", "", "bootstrap relay API URLs; merged with public registry relays when discovery is enabled", "BOOTSTRAPS")
72
+ utils.StringFlagEnv(fs, &cfg.Bootstraps, "bootstraps", "", "bootstrap relay API URLs; merged with bootstrap relays when discovery is enabled", "BOOTSTRAPS")
73
utils.BoolFlagEnv(fs, &cfg.DiscoveryEnabled, "discovery", false, "serve relay discovery endpoints and poll discovery peers", "DISCOVERY")
74
utils.IntFlagEnv(fs, &cfg.WireGuardPort, "wireguard-port", overlay.DefaultListenPort, utils.ParsePortNumber, "public and listen UDP port for relay overlay", "WIREGUARD_PORT")
75
portal/server.go
+1
-1
@@ -148,7 +148,7 @@ func NewServer(cfg ServerConfig) (*Server, error) {
148
}
149
var relaySet *discovery.RelaySet
150
if cfg.DiscoveryEnabled {
151
- cfg.Bootstraps, err = utils.ResolvePortalRelayURLs(context.Background(), cfg.Bootstraps, true)
151
+ cfg.Bootstraps, err = utils.ResolvePortalRelayURLs(cfg.Bootstraps, true)
152
if err != nil {
153
return nil, fmt.Errorf("resolve discovery bootstraps: %w", err)
154
}
registry.json
deleted
-11
@@ -1,11 +0,0 @@
1
-{
2
- "relays": [
3
- "https://portal.thumbgo.kr/",
4
- "https://portal.rabbitson87.dev/",
5
- "https://s-h.day/",
6
- "https://portal.1ncursio.dev/",
7
- "https://portal.dawnfullstack.com/",
8
- "https://portal.damn.it.com/",
9
- "https://kakashit.org"
10
- ]
11
-}
sdk/expose.go
+2
-2
@@ -109,12 +109,12 @@ func Expose(ctx context.Context, cfg ExposeConfig) (*Exposure, error) {
109
listenerRelayURLs = []string{multiHop[len(multiHop)-1]}
110
relaySetURLs = append([]string(nil), multiHop...)
111
} else if cfg.MultiHopDepth > 1 {
112
- relaySetURLs, err = utils.ResolvePortalRelayURLs(ctx, explicitRelayURLs, cfg.Discovery)
112
+ relaySetURLs, err = utils.ResolvePortalRelayURLs(explicitRelayURLs, cfg.Discovery)
113
if err != nil {
114
return nil, err
115
}
116
} else {
117
- relaySetURLs, err = utils.ResolvePortalRelayURLs(ctx, explicitRelayURLs, cfg.Discovery)
117
+ relaySetURLs, err = utils.ResolvePortalRelayURLs(explicitRelayURLs, cfg.Discovery)
118
if err != nil {
119
return nil, err
120
}
types/types.go
+5
-2
@@ -9,8 +9,6 @@ import (
9
)
10
11
const (
12
- PortalRelayRegistryURL = "https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json"
13
-
12
HeaderAccessToken = "X-Portal-Access-Token"
13
MarkerKeepalive = byte(0x00)
14
MarkerRawStart = byte(0x01)
@@ -22,6 +20,7 @@ var (
20
SDKVersion string
21
DiscoveryVersion string
22
OfficialReleaseBaseURL string
23
+ BootstrapRelays []string
24
)
25
26
func init() {
@@ -34,6 +33,9 @@ func init() {
33
Tunnel string `toml:"tunnel"`
34
Discovery string `toml:"discovery"`
35
} `toml:"protocol"`
36
+ Bootstrap struct {
37
+ Relays []string `toml:"relays"`
38
+ } `toml:"bootstrap"`
39
}
40
if err := toml.Unmarshal(portaltunnel.ConfigTOML, &m); err != nil {
41
panic(fmt.Errorf("unmarshal config TOML: %w", err))
@@ -42,4 +44,5 @@ func init() {
44
OfficialReleaseBaseURL = m.Release.BaseURL
45
SDKVersion = m.Protocol.Tunnel
46
DiscoveryVersion = m.Protocol.Discovery
47
+ BootstrapRelays = m.Bootstrap.Relays
48
}
utils/network.go
-35
@@ -140,38 +140,3 @@ func FetchRelayVersion(ctx context.Context, relayURL string) string {
140
}
141
return envelope.Data.ReleaseVersion
142
}
143
-
144
-func ResolvePortalRelayURLs(ctx context.Context, explicit []string, includeDefaults bool) ([]string, error) {
145
- explicit, err := NormalizeRelayURLs(explicit...)
146
- if err != nil {
147
- return nil, err
148
- }
149
- if !includeDefaults {
150
- return explicit, nil
151
- }
152
-
153
- client := NewHTTPClient(WithHTTPTimeout(3 * time.Second))
154
- var registry struct {
155
- Relays []string `json:"relays"`
156
- }
157
- resp, err := httpDo(ctx, client, http.MethodGet, types.PortalRelayRegistryURL, nil, nil)
158
- if err != nil {
159
- return explicit, nil
160
- }
161
- defer resp.Body.Close()
162
- if resp.StatusCode != http.StatusOK {
163
- return explicit, nil
164
- }
165
- if err := json.NewDecoder(resp.Body).Decode(®istry); err != nil {
166
- return explicit, nil
167
- }
168
-
169
- defaults, err := NormalizeRelayURLs(registry.Relays...)
170
- if err != nil {
171
- return explicit, nil
172
- }
173
- if len(defaults) == 0 {
174
- return explicit, nil
175
- }
176
- return MergeRelayURLs(defaults, nil, explicit)
177
-}
utils/utils.go
+21
@@ -16,6 +16,8 @@ import (
16
"unicode"
17
18
"golang.org/x/net/idna"
19
+
20
+ "github.com/gosuda/portal-tunnel/v2/types"
21
)
22
23
func SplitCSV(raw string) []string {
@@ -360,6 +362,25 @@ func MergeRelayURLs(current, excluded, inputs []string) ([]string, error) {
362
return FilterRelayURLs(merged, excluded), nil
363
}
364
365
+func ResolvePortalRelayURLs(explicit []string, includeBootstrap bool) ([]string, error) {
366
+ explicit, err := NormalizeRelayURLs(explicit...)
367
+ if err != nil {
368
+ return nil, err
369
+ }
370
+ if !includeBootstrap {
371
+ return explicit, nil
372
+ }
373
+
374
+ defaults, err := NormalizeRelayURLs(types.BootstrapRelays...)
375
+ if err != nil {
376
+ return explicit, nil
377
+ }
378
+ if len(defaults) == 0 {
379
+ return explicit, nil
380
+ }
381
+ return MergeRelayURLs(defaults, nil, explicit)
382
+}
383
+
384
func ExcludeLocalRelayURLs(inputs ...string) ([]string, error) {
385
normalized, err := NormalizeRelayURLs(inputs...)
386
if err != nil {