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