refactor: simplify tunnel script generation and remove unused utility functions
Kim committed
Dec 11, 2025 at 16:44 UTC
d8c1771a1f4d62a040d6b3606d2cc583b2403c66
5 files changed
+19
-188
cmd/portal-tunnel/config.go
+3
-11
@@ -36,7 +36,9 @@ func LoadConfig(path string) (*TunnelConfig, error) {
36
if err := yaml.Unmarshal(data, &cfg); err != nil {
37
return nil, fmt.Errorf("parse config: %w", err)
38
}
39
- cfg.applyDefaults()
39
+ if len(cfg.Service.Protocols) == 0 {
40
+ cfg.Service.Protocols = append([]string(nil), defaultProtocols...)
41
+ }
42
43
if err := cfg.validate(); err != nil {
44
return nil, err
@@ -78,13 +80,3 @@ func (cfg *TunnelConfig) validate() error {
80
81
return nil
82
}
81
-
82
-func (cfg *TunnelConfig) applyDefaults() {
83
- applyServiceDefaults(&cfg.Service)
84
-}
85
-
86
-func applyServiceDefaults(svc *ServiceConfig) {
87
- if len(svc.Protocols) == 0 {
88
- svc.Protocols = append([]string(nil), defaultProtocols...)
89
- }
90
-}
cmd/portal-tunnel/main.go
+15
-60
@@ -70,8 +70,7 @@ func runExposeWithConfig() error {
70
return fmt.Errorf("load config: %w", err)
71
}
72
73
- relayURLs := normalizeRelayURLs(cfg.Relays)
74
- if len(relayURLs) == 0 {
73
+ if len(cfg.Relays) == 0 {
74
return fmt.Errorf("config: relays must include at least one URL")
75
}
76
@@ -88,7 +87,7 @@ func runExposeWithConfig() error {
87
cancel()
88
}()
89
91
- if err := runServiceTunnel(ctx, relayURLs, &cfg.Service, fmt.Sprintf("config=%s", flagConfigPath)); err != nil {
90
+ if err := runServiceTunnel(ctx, cfg.Relays, &cfg.Service, fmt.Sprintf("config=%s", flagConfigPath)); err != nil {
91
return err
92
}
93
@@ -102,39 +101,17 @@ func runExposeWithFlags() error {
101
return fmt.Errorf("--relay must include at least one non-empty URL when --config is not provided")
102
}
103
105
- var metadata sdk.Metadata
106
- if strings.TrimSpace(flagDesc) != "" {
107
- metadata.Description = flagDesc
108
- }
109
- if strings.TrimSpace(flagTags) != "" {
110
- tags := strings.Split(flagTags, ",")
111
- for i := range tags {
112
- tags[i] = strings.TrimSpace(tags[i])
113
- }
114
- filtered := tags[:0]
115
- for _, t := range tags {
116
- if t != "" {
117
- filtered = append(filtered, t)
118
- }
119
- }
120
- metadata.Tags = filtered
121
- }
122
- if strings.TrimSpace(flagThumbnail) != "" {
123
- metadata.Thumbnail = flagThumbnail
124
- }
125
- if strings.TrimSpace(flagOwner) != "" {
126
- metadata.Owner = flagOwner
127
- }
128
- if flagHide {
129
- metadata.Hide = flagHide
130
- }
131
-
104
service := &ServiceConfig{
133
- Name: strings.TrimSpace(flagName),
134
- Target: flagHost,
135
- Metadata: metadata,
105
+ Name: flagName,
106
+ Target: flagHost,
107
+ Metadata: sdk.Metadata{
108
+ Description: flagDesc,
109
+ Tags: strings.Split(flagTags, ","),
110
+ Thumbnail: flagThumbnail,
111
+ Owner: flagOwner,
112
+ Hide: flagHide,
113
+ },
114
}
137
- applyServiceDefaults(service)
115
116
ctx, cancel := context.WithCancel(context.Background())
117
defer cancel()
@@ -200,11 +177,7 @@ func proxyConnection(ctx context.Context, localAddr string, relayConn net.Conn)
177
178
func runServiceTunnel(ctx context.Context, relayURLs []string, service *ServiceConfig, origin string) error {
179
localAddr := service.Target
203
- serviceName := strings.TrimSpace(service.Name)
204
- if len(relayURLs) == 0 {
205
- return fmt.Errorf("no relay URLs provided")
206
- }
207
- bootstrapServers := relayURLs
180
+ serviceName := service.Name
181
182
cred := sdk.NewCredential()
183
leaseID := cred.ID()
@@ -215,11 +188,11 @@ func runServiceTunnel(ctx context.Context, relayURLs []string, service *ServiceC
188
log.Info().Str("service", serviceName).Msgf("Local service is reachable at %s", localAddr)
189
log.Info().Str("service", serviceName).Msgf("Starting Portal Tunnel (%s)...", origin)
190
log.Info().Str("service", serviceName).Msgf(" Local: %s", localAddr)
218
- log.Info().Str("service", serviceName).Msgf(" Relays: %s", strings.Join(bootstrapServers, ", "))
191
+ log.Info().Str("service", serviceName).Msgf(" Relays: %s", strings.Join(relayURLs, ", "))
192
log.Info().Str("service", serviceName).Msgf(" Lease ID: %s", leaseID)
193
194
client, err := sdk.NewClient(func(c *sdk.ClientConfig) {
222
- c.BootstrapServers = bootstrapServers
195
+ c.BootstrapServers = relayURLs
196
})
197
if err != nil {
198
return fmt.Errorf("service %s: failed to connect to relay: %w", serviceName, err)
@@ -247,7 +220,7 @@ func runServiceTunnel(ctx context.Context, relayURLs []string, service *ServiceC
220
log.Info().Str("service", serviceName).Msg("Access via:")
221
log.Info().Str("service", serviceName).Msgf("- Name: /peer/%s", serviceName)
222
log.Info().Str("service", serviceName).Msgf("- Lease ID: /peer/%s", leaseID)
250
- log.Info().Str("service", serviceName).Msgf("- Example: http://%s/peer/%s", bootstrapServers[0], serviceName)
223
+ log.Info().Str("service", serviceName).Msgf("- Example: %s/peer/%s", relayURLs[0], serviceName)
224
225
log.Info().Str("service", serviceName).Msg("")
226
@@ -285,21 +258,3 @@ func runServiceTunnel(ctx context.Context, relayURLs []string, service *ServiceC
258
}(relayConn)
259
}
260
}
288
-
289
-// normalizeRelayURLs trims, de-duplicates, and filters empty relay URLs.
290
-func normalizeRelayURLs(urls []string) []string {
291
- seen := map[string]struct{}{}
292
- var out []string
293
- for _, u := range urls {
294
- u = strings.TrimSpace(u)
295
- if u == "" {
296
- continue
297
- }
298
- if _, ok := seen[u]; ok {
299
- continue
300
- }
301
- seen[u] = struct{}{}
302
- out = append(out, u)
303
- }
304
- return out
305
-}
cmd/relay-server/tunnel.go
+1
-2
@@ -69,8 +69,7 @@ func serveTunnelScript(w http.ResponseWriter, r *http.Request) {
69
return
70
}
71
72
- baseURL := utils.DetectBaseURL(r, flagPortalURL)
73
- script := fmt.Sprintf(tunnelScriptTemplate, baseURL)
72
+ script := fmt.Sprintf(tunnelScriptTemplate, flagPortalURL)
73
74
w.Header().Set("Content-Type", "text/x-shellscript")
75
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
utils/utils.go
-88
@@ -4,7 +4,6 @@ import (
4
"context"
5
"fmt"
6
"io"
7
- "mime"
7
"net/http"
8
"net/url"
9
"regexp"
@@ -154,15 +153,6 @@ func GetContentType(ext string) string {
153
}
154
}
155
157
-// MatchesWildcardPattern checks if a host matches a wildcard pattern (e.g., *.localhost:4017)
158
-func MatchesWildcardPattern(host, pattern string) bool {
159
- if strings.HasPrefix(pattern, "*.") {
160
- suffix := strings.TrimPrefix(pattern, "*")
161
- return strings.HasSuffix(host, suffix)
162
- }
163
- return host == pattern
164
-}
165
-
156
// IsHexString reports whether s contains only hexadecimal characters
157
func IsHexString(s string) bool {
158
for _, c := range s {
@@ -173,19 +163,6 @@ func IsHexString(s string) bool {
163
return true
164
}
165
176
-// IsHTMLContentType checks if the Content-Type header indicates HTML content
177
-// It properly handles media type parsing with parameters like charset
178
-func IsHTMLContentType(contentType string) bool {
179
- if contentType == "" {
180
- return false
181
- }
182
- mediaType, _, err := mime.ParseMediaType(contentType)
183
- if err != nil {
184
- return strings.HasPrefix(strings.ToLower(contentType), "text/html")
185
- }
186
- return mediaType == "text/html"
187
-}
188
-
166
// SetCORSHeaders sets permissive CORS headers for GET/OPTIONS and common headers
167
func SetCORSHeaders(w http.ResponseWriter) {
168
w.Header().Set("Access-Control-Allow-Origin", "*")
@@ -193,71 +170,6 @@ func SetCORSHeaders(w http.ResponseWriter) {
170
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Accept-Encoding")
171
}
172
196
-// ForwardedHost returns the host from X-Forwarded-Host (first value) or falls back to r.Host.
197
-func ForwardedHost(r *http.Request) string {
198
- if r == nil {
199
- return ""
200
- }
201
- if h := r.Header.Get("X-Forwarded-Host"); h != "" {
202
- parts := strings.Split(h, ",")
203
- return strings.TrimSpace(parts[0])
204
- }
205
- return r.Host
206
-}
207
-
208
-// IsHTTPS reports whether the request is HTTPS, checking TLS or X-Forwarded-Proto.
209
-func IsHTTPS(r *http.Request) bool {
210
- if r == nil {
211
- return false
212
- }
213
- if r.TLS != nil {
214
- return true
215
- }
216
- proto := r.Header.Get("X-Forwarded-Proto")
217
- return strings.EqualFold(proto, "https")
218
-}
219
-
220
-// RequestScheme returns "https" when the request is HTTPS and "http" otherwise.
221
-func RequestScheme(r *http.Request) string {
222
- if IsHTTPS(r) {
223
- return "https"
224
- }
225
- return "http"
226
-}
227
-
228
-// DetectBaseURL builds a base URL (scheme://host) using request headers with a fallback portal URL.
229
-func DetectBaseURL(r *http.Request, fallbackPortalURL string) string {
230
- scheme := RequestScheme(r)
231
- host := ForwardedHost(r)
232
- if host == "" && fallbackPortalURL != "" {
233
- if u, err := url.Parse(fallbackPortalURL); err == nil {
234
- host = u.Host
235
- if u.Scheme != "" {
236
- scheme = u.Scheme
237
- }
238
- }
239
- }
240
- return fmt.Sprintf("%s://%s", scheme, host)
241
-}
242
-
243
-// DetectRelayURL builds a relay WebSocket URL (ws[s]://host/relay) using request headers with a fallback portal URL.
244
-func DetectRelayURL(r *http.Request, fallbackPortalURL string) string {
245
- wsScheme := "ws"
246
- if IsHTTPS(r) {
247
- wsScheme = "wss"
248
- }
249
- host := ForwardedHost(r)
250
- if host == "" && fallbackPortalURL != "" {
251
- if u, err := url.Parse(fallbackPortalURL); err == nil {
252
- host = u.Host
253
- if u.Scheme == "https" {
254
- wsScheme = "wss"
255
- }
256
- }
257
- }
258
- return fmt.Sprintf("%s://%s/relay", wsScheme, host)
259
-}
260
-
173
// IsSubdomain reports whether host matches the given domain pattern.
174
// Supports patterns like:
175
// - "*.example.com" (wildcard for any subdomain of example.com)
utils/utils_test.go
-27
@@ -177,16 +177,6 @@ func TestParseURLs(t *testing.T) {
177
}
178
}
179
180
-func TestIsHTMLContentType(t *testing.T) {
181
- assert.True(t, IsHTMLContentType("text/html"))
182
- assert.True(t, IsHTMLContentType("text/html; charset=utf-8"))
183
- assert.True(t, IsHTMLContentType("TEXT/HTML; CHARSET=UTF-8"))
184
- // Fallback path (parse error) with html prefix
185
- assert.True(t, IsHTMLContentType("text/html; bad==value"))
186
- assert.False(t, IsHTMLContentType("application/json"))
187
- assert.False(t, IsHTMLContentType(""))
188
-}
189
-
180
func TestGetContentType(t *testing.T) {
181
cases := map[string]string{
182
".html": "text/html; charset=utf-8",
@@ -207,23 +197,6 @@ func TestGetContentType(t *testing.T) {
197
}
198
}
199
210
-func TestMatchesWildcardPattern(t *testing.T) {
211
- // Wildcard pattern
212
- assert.True(t, MatchesWildcardPattern("app.localhost:4017", "*.localhost:4017"))
213
- assert.True(t, MatchesWildcardPattern("x.y.localhost:4017", "*.localhost:4017"))
214
- assert.False(t, MatchesWildcardPattern("localhost:4017", "*.localhost:4017"))
215
- assert.True(t, MatchesWildcardPattern("exact.host", "exact.host"))
216
- assert.False(t, MatchesWildcardPattern("sub.exact.host", "exact.host"))
217
-}
218
-
219
-func TestIsHexString(t *testing.T) {
220
- assert.True(t, IsHexString("0123456789abcdef"))
221
- assert.True(t, IsHexString("ABCDEF"))
222
- assert.True(t, IsHexString(""), "empty string is considered hex")
223
- assert.False(t, IsHexString("g"))
224
- assert.False(t, IsHexString("xyz"))
225
-}
226
-
200
func TestIsSubdomain(t *testing.T) {
201
tests := []struct {
202
name string