chore: remove bootstrap and fix tunnel script

Kim committed Mar 13, 2026 at 11:28 UTC 232f1c7bbca7236494bde16806042dd32b96b930
10 files changed +40 -34
.env.example
-1
@@ -1,6 +1,5 @@
1 # Public routing
2 PORTAL_URL=https://localhost:4017
3 -BOOTSTRAP_URIS=https://localhost:4017
3
4 # Listener ports
5 API_PORT=4017
Dockerfile
-1
@@ -39,7 +39,6 @@ FROM gcr.io/distroless/static-debian12:nonroot
39 COPY --from=go-builder /src/bin/relay-server /usr/bin/relay-server
40
41 ENV PORTAL_URL=https://localhost:4017
42 -ENV BOOTSTRAP_URIS=https://localhost:4017
42 ENV ADMIN_SECRET_KEY=
43 ENV SNI_PORT=:443
44 ENV KEYLESS_DIR=
cmd/relay-server/main.go
-14
@@ -23,7 +23,6 @@ const (
23
24 type relayServerConfig struct {
25 PortalURL string
26 - Bootstraps []string
26 APIPort int
27 SNIPort int
28 AdminSecretKey string
@@ -49,10 +48,6 @@ func main() {
48 if portalURL == "" {
49 portalURL = defaultPortalURL
50 }
52 - bootstrapsCSV := trimmedEnv("BOOTSTRAP_URIS")
53 - if bootstrapsCSV == "" {
54 - bootstrapsCSV = portalURL
55 - }
51 apiPort := parsePortNumber(os.Getenv("API_PORT"), defaultAPIPort)
52 sniPort := parsePortNumber(os.Getenv("SNI_PORT"), defaultSNIPort)
53 adminSecretKey := trimmedEnv("ADMIN_SECRET_KEY")
@@ -77,7 +72,6 @@ func main() {
72 awsHostedZoneID := trimmedEnv("AWS_HOSTED_ZONE_ID")
73
74 flag.StringVar(&cfg.PortalURL, "portal-url", portalURL, "portal base URL (env: PORTAL_URL)")
80 - flag.StringVar(&bootstrapsCSV, "bootstraps", bootstrapsCSV, "bootstrap URIs, comma-separated (env: BOOTSTRAP_URIS)")
75 flag.IntVar(&cfg.APIPort, "api-port", apiPort, "Admin/API server port (env: API_PORT)")
76 flag.IntVar(&cfg.SNIPort, "sni-port", sniPort, "SNI router port number (env: SNI_PORT)")
77
@@ -95,14 +89,6 @@ func main() {
89 flag.StringVar(&cfg.AWSHostedZoneID, "aws-hosted-zone-id", awsHostedZoneID, "explicit Route53 hosted zone ID override (env: AWS_HOSTED_ZONE_ID)")
90 flag.Parse()
91
98 - cfg.Bootstraps = utils.SplitCSV(bootstrapsCSV)
99 - if len(cfg.Bootstraps) == 0 {
100 - cfg.Bootstraps = []string{cfg.PortalURL}
101 - }
102 - if cfg.PortalURL == "" {
103 - cfg.PortalURL = cfg.Bootstraps[0]
104 - }
105 -
92 logger.Info().
93 Str("release_version", types.ReleaseVersion).
94 Str("portal_url", cfg.PortalURL).
cmd/relay-server/serve.go
-3
@@ -24,9 +24,6 @@ func runServer(cfg relayServerConfig) error {
24 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
25 defer stop()
26
27 - if len(cfg.Bootstraps) > 0 && cfg.PortalURL == "" {
28 - cfg.PortalURL = cfg.Bootstraps[0]
29 - }
27 rootHost := utils.PortalRootHost(cfg.PortalURL)
28 apiListenAddr := fmt.Sprintf(":%d", cfg.APIPort)
29 sniListenAddr := fmt.Sprintf(":%d", cfg.SNIPort)
cmd/relay-server/tunnel.go
+39 -8
@@ -11,7 +11,7 @@ import (
11 "github.com/gosuda/portal/v2/types"
12 )
13
14 -const installShellScriptTemplate = `#!/usr/bin/env sh
14 +const installShellScriptTemplatePrefix = `#!/usr/bin/env sh
15 set -eu
16
17 OS="$(uname -s)"
@@ -34,7 +34,10 @@ case "$ARCH" in
34 ;;
35 esac
36
37 -BASE_URL="${BASE_URL:-%s}"
37 +BASE_URL="${BASE_URL:-}"
38 +if [ -z "$BASE_URL" ]; then
39 + BASE_URL=%s
40 +fi
41 BIN_URL="${BIN_URL:-$BASE_URL/install/bin/$PORTAL_OS-$PORTAL_ARCH}"
42 CHECKSUM_URL="${BIN_URL}.sha256"
43 CURL_INSECURE_FLAG=""
@@ -111,7 +114,9 @@ write_config() {
114 CONFIG_PATH="$CONFIG_DIR/config.json"
115 mkdir -p "$CONFIG_DIR"
116 cat > "$CONFIG_PATH" <<'EOF'
114 -%s
117 +`
118 +
119 +const installShellScriptTemplateSuffix = `
120 EOF
121 printf '%%s\n' "$CONFIG_PATH"
122 }
@@ -140,8 +145,8 @@ echo "Next step:" >&2
145 echo " portal expose 3000" >&2
146 `
147
143 -const installPowerShellTemplate = `$ErrorActionPreference = "Stop"
144 -$BaseUrl = if ($env:BASE_URL) { $env:BASE_URL } else { "%s" }
148 +const installPowerShellTemplatePrefix = `$ErrorActionPreference = "Stop"
149 +$BaseUrl = if ($env:BASE_URL) { $env:BASE_URL } else { %s }
150 $OriginalSecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol
151 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
152 $WorkDir = $null
@@ -191,7 +196,9 @@ try {
196 New-Item -ItemType Directory -Force -Path $ConfigDir | Out-Null
197 $ConfigPath = Join-Path $ConfigDir "config.json"
198 $ConfigPayload = @'
194 -%s
199 +`
200 +
201 +const installPowerShellTemplateSuffix = `
202 '@
203 $Utf8NoBom = New-Object System.Text.UTF8Encoding $false
204 [System.IO.File]::WriteAllText($ConfigPath, $ConfigPayload, $Utf8NoBom)
@@ -293,11 +300,11 @@ func serveInstallScript(w http.ResponseWriter, r *http.Request, portalURL string
300 return
301 }
302
296 - script := fmt.Sprintf(installShellScriptTemplate, portalURL, string(configPayload))
303 + script := buildInstallShellScript(portalURL, configPayload)
304 contentType := "text/x-shellscript"
305 filename := "install.sh"
306 if isWindows {
300 - script = fmt.Sprintf(installPowerShellTemplate, portalURL, string(configPayload))
307 + script = buildInstallPowerShellScript(portalURL, configPayload)
308 contentType = "text/plain; charset=utf-8"
309 filename = "install.ps1"
310 }
@@ -308,3 +315,27 @@ func serveInstallScript(w http.ResponseWriter, r *http.Request, portalURL string
315 _, _ = w.Write([]byte(script))
316 }
317 }
318 +
319 +func buildInstallShellScript(portalURL string, configPayload []byte) string {
320 + var script strings.Builder
321 + fmt.Fprintf(&script, installShellScriptTemplatePrefix, shellSingleQuoted(portalURL))
322 + script.Write(configPayload)
323 + script.WriteString(installShellScriptTemplateSuffix)
324 + return script.String()
325 +}
326 +
327 +func buildInstallPowerShellScript(portalURL string, configPayload []byte) string {
328 + var script strings.Builder
329 + fmt.Fprintf(&script, installPowerShellTemplatePrefix, powerShellSingleQuoted(portalURL))
330 + script.Write(configPayload)
331 + script.WriteString(installPowerShellTemplateSuffix)
332 + return script.String()
333 +}
334 +
335 +func shellSingleQuoted(value string) string {
336 + return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
337 +}
338 +
339 +func powerShellSingleQuoted(value string) string {
340 + return "'" + strings.ReplaceAll(value, "'", "''") + "'"
341 +}
docker-compose.yml
-1
@@ -7,7 +7,6 @@ services:
7 environment:
8 # Public routing
9 PORTAL_URL: ${PORTAL_URL:-https://localhost:${API_PORT:-4017}}
10 - BOOTSTRAP_URIS: ${BOOTSTRAP_URIS:-https://localhost:${API_PORT:-4017}}
10
11 # Listener ports
12 API_PORT: ${API_PORT:-4017}
docs/deployment.md
+1 -3
@@ -26,7 +26,6 @@ You need:
26
27 ```bash
28 PORTAL_URL=https://example.com
29 -BOOTSTRAP_URIS=https://example.com
29 SNI_PORT=443
30 ADMIN_SECRET_KEY=your-admin-secret
31 KEYLESS_DIR=./.portal-certs
@@ -43,7 +42,6 @@ CLOUDFLARE_TOKEN=cf_xxxxxxxxxxxxxxxxx
42
43 ```bash
44 PORTAL_URL=https://example.com
46 -BOOTSTRAP_URIS=https://example.com
45 SNI_PORT=443
46 ADMIN_SECRET_KEY=your-admin-secret
47 KEYLESS_DIR=./.portal-certs
@@ -96,7 +94,7 @@ sudo ufw status
94
95 ### 4.3 Non-Apex and Proxy Setups
96
99 -- For non-apex deployments, set `PORTAL_URL` and `BOOTSTRAP_URIS` to the same non-apex host value such as `https://portal.example.com:8443`.
97 +- For non-apex deployments, set `PORTAL_URL` to the non-apex host value such as `https://portal.example.com:8443`.
98 - `KEYLESS_DIR` stores the relay certificate material as `fullchain.pem` and `privatekey.pem`.
99 - If the relay sits behind a reverse proxy or ingress and you want admin/auth and lease IP tracking to use forwarded client addresses, set:
100
docs/examples/nginx-proxy-multi-service/docker-compose.yaml
-1
@@ -56,7 +56,6 @@ services:
56 container_name: portal
57 environment:
58 PORTAL_URL: ${PORTAL_URL:-https://portal.example.com}
59 - BOOTSTRAP_URIS: ${BOOTSTRAP_URIS:-https://portal.example.com}
59 API_PORT: ${API_PORT:-4017}
60 SNI_PORT: ${SNI_PORT:-443}
61 ADMIN_SECRET_KEY: ${ADMIN_SECRET_KEY:-}
docs/examples/nginx-proxy/.env.example
-1
@@ -3,7 +3,6 @@
3
4 # Public routing (no port — nginx handles :443 externally)
5 PORTAL_URL=https://portal.example.com
6 -BOOTSTRAP_URIS=https://portal.example.com
6
7 # Internal listener ports
8 API_PORT=4017
docs/examples/nginx-proxy/docker-compose.yaml
-1
@@ -49,7 +49,6 @@ services:
49 # Public-facing relay URL. nginx handles TLS on port 443 externally,
50 # so this URL should not include a port number.
51 PORTAL_URL: ${PORTAL_URL:-https://portal.example.com}
52 - BOOTSTRAP_URIS: ${BOOTSTRAP_URIS:-https://portal.example.com}
52
53 # Internal listener ports (not exposed to host).
54 API_PORT: ${API_PORT:-4017}