feat: update documentation

Kim committed Apr 15, 2026 at 14:07 UTC 7daa812098ab92d4a55953e1b68770b780d155fe
20 files changed +171 -197
.env.example
+4 -4
@@ -7,6 +7,7 @@ IDENTITY_PATH=/portal-certs
7 # Listener ports
8 API_PORT=4017
9 SNI_PORT=443
10 +WIREGUARD_PORT=51820
11 # Set when enabling public UDP or raw TCP lease ports.
12 MIN_PORT=0
13 MAX_PORT=0
@@ -15,7 +16,7 @@ TCP_ENABLED=false
16
17
18 # Supported managed values: cloudflare, gcloud, route53
18 -ACME_DNS_PROVIDER=cloudflare
19 +ACME_DNS_PROVIDER=
20
21 # Cloudflare API token (required when ACME_DNS_PROVIDER=cloudflare)
22 CLOUDFLARE_TOKEN=
@@ -39,8 +40,7 @@ AWS_DNSSEC_KMS_KEY_ARN=
40 # for DNSSEC and ENS TXT automation, even when certificate files are managed manually.
41 ENS_GASLESS_ENABLED=false
42
42 -# Admin/auth configuration
43 -ADMIN_SECRET_KEY=
43 +# Admin/auth configuration. The admin secret is generated and stored in IDENTITY_PATH/identity.json.
44 LANDING_PAGE_ENABLED=false
45 # Enable when the relay is behind nginx/ingress/load balancers and should trust forwarded client IP headers.
46 # Optionally restrict which proxy source ranges may supply those headers; leave empty for default private/loopback proxy ranges.
@@ -49,5 +49,5 @@ TRUSTED_PROXY_CIDRS=
49
50 # Optional: auto-generated thumbnail screenshots for tunnel apps without a thumbnail.
51 # Requires the headless-shell sidecar (chromedp/headless-shell) in docker-compose.
52 -# Leave empty or remove to disable. See docs/deployment.md section 8.
52 +# Leave empty or remove to disable. See docs/src/routes/deployment/+page.md.
53 # HEADLESS_SHELL_URL=ws://headless-shell:9222
README.md
+4 -5
@@ -58,7 +58,7 @@ cd portal-tunnel && cp .env.example .env
58 docker compose up
59 ```
60
61 -For deployment to a public domain, see [docs/deployment.md](docs/deployment.md).
61 +For deployment to a public domain, see [Deployment](docs/src/routes/deployment/+page.md).
62
63 ### Run native app (Advanced)
64
@@ -66,15 +66,14 @@ See [portal-toys](https://github.com/gosuda/portal-toys) for more examples.
66
67 ## Architecture
68
69 -See [docs/architecture.md](docs/architecture.md).
70 -For architecture decisions, see [docs/adr/README.md](docs/adr/README.md).
69 +See [Architecture](docs/src/routes/architecture/+page.md).
70
71 ## Examples
72
73 | Example | Description |
74 |---------|-------------|
76 -| [nginx reverse proxy](docs/examples/nginx-proxy/) | Deploy Portal behind nginx with L4 SNI routing and TLS termination |
77 -| [nginx + multi-service](docs/examples/nginx-proxy-multi-service/) | Run Portal alongside other web services behind a single nginx instance |
75 +| [nginx reverse proxy](docs/static/examples/nginx-proxy/) | Deploy Portal behind nginx with L4 SNI routing and TLS termination |
76 +| [nginx + multi-service](docs/static/examples/nginx-proxy-multi-service/) | Run Portal alongside other web services behind a single nginx instance |
77
78 ## Public Relay Registry
79
README.zh-CN.md
+28 -71
@@ -4,53 +4,38 @@
4
5 <p align="center"><img width="800" alt="Portal Demo" src="./portal.gif" /></p>
6
7 -<p align="center">将本地应用暴露到公网,无需端口转发、NAT 配置或 DNS 设置。<br />Portal 是一个无需信任的中继网络,中继无法访问你的流量。你可以连接任意中继,也可以自行部署自己的中继。</p><br />
8 -
9 -## 功能特性
10 -
11 -- **为 localhost 提供公网 HTTPS**:通过 TCP 透传实现 NAT 友好的发布方式(无需端口转发)
12 -- **端到端 TLS**:TLS 在你这一侧终止,并内置 MITM 检测,因此中继无法访问明文
13 -- **一条命令即可启动**:以最少配置启动中继和隧道
14 -- **自托管中继**:既可以连接公共中继,也可以运行你自己的中继
15 -- **中继发现与中继池**:将发现到的中继作为中继池使用,支持多中继访问与故障切换
16 -- **无需登录、无需 API Key**:使用 SIWE 验证所有权,并支持基于 ENS 的身份
17 -- **原生 TCP 与 UDP 传输**:原生 TCP 反向会话,并可选支持 UDP(不依赖 SSH 或 WebSocket)
18 -- **TCP 端口路由**:为非 TLS 服务(如 Minecraft、游戏服务器)分配专用 TCP 端口,无需基于 SNI 的路由
19 -
20 -## 对比
21 -
22 -| | Portal | ngrok | Cloudflare Tunnel | frp |
23 -|---|---|---|---|---|
24 -| 端到端加密 | **是** | 可选 | 否 | 否 |
25 -| TLS 终止位置 | 客户端 | 边缘(默认) | 边缘(始终) | 服务器端 |
26 -| MITM 检测 | **内置** | 否 | 否 | 否 |
27 -| 可自托管 | **是** | 仅企业版 | 否 | 是 |
28 -| 多中继故障切换 | **是** | 托管 | 内置多数据中心 | 否 |
29 -| 自定义域名 | **是** | 付费方案 | 是 | 是 |
30 -| 传输协议 | 原生 TCP / UDP | HTTP/S, TCP, TLS | HTTP/S, TCP, UDP | HTTP/S, TCP, UDP |
31 -| 非 TLS TCP 端口路由 | **是** | 付费方案 | 否 | 是 |
32 -| 开源 | **MIT** | 否 | 仅客户端(Apache 2.0) | Apache 2.0 |
33 -| 需要账号 | **否**(SIWE) | 是 | 是 | 否 |
7 +Portal 可以把本地服务发布到公网,不需要端口转发、NAT 配置或手动 DNS 配置。Portal 是一个可自托管的中继网络,默认让租户 TLS 在你的机器上终止,中继无法读取明文流量。
8 +
9 +## 功能
10 +
11 +- **本地服务公网 HTTPS**:通过反向连接发布 localhost 服务。
12 +- **端到端租户 TLS**:TLS 在 SDK/客户端侧终止,中继只做 SNI 路由和字节转发。
13 +- **MITM 自探测**:`portal expose` 默认在检测到疑似 TLS 终止时禁用该中继。
14 +- **中继发现和池化**:可以使用公共 registry、显式中继和运行时 discovery 结果。
15 +- **自托管中继**:可以连接公共中继,也可以运行自己的 relay server。
16 +- **Raw TCP/UDP**:支持专用 TCP 端口和 UDP/QUIC datagram backhaul。
17 +- **无需账号/API key**:注册使用 SIWE 身份签名。
18
19 ## 快速开始
20
37 -### 公开你的本地应用:
21 +### 暴露本地服务
22
23 ```bash
24 curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
25 portal expose 3000
26 ```
27
28 +Windows PowerShell:
29 +
30 ```powershell
31 $ProgressPreference = 'SilentlyContinue'
32 irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
33 portal expose 3000
34 ```
35
50 -然后你就可以通过一个公网 HTTPS URL 访问你的应用。
51 -安装细节请参见 [cmd/portal-tunnel/README.md](cmd/portal-tunnel/README.md)。
36 +安装细节见 [cmd/portal-tunnel/README.md](cmd/portal-tunnel/README.md)。
37
53 -### 运行你自己的中继
38 +### 运行自己的中继
39
40 ```bash
41 git clone https://github.com/gosuda/portal-tunnel
@@ -58,57 +43,29 @@ cd portal-tunnel && cp .env.example .env
43 docker compose up
44 ```
45
61 -部署到公网域名时,请参见 [docs/deployment.md](docs/deployment.md)。
62 -
63 -### 运行原生应用(高级)
64 -
65 -更多示例请参见 [portal-toys](https://github.com/gosuda/portal-toys)。
66 -
67 -## 架构
68 -
69 -请参见 [docs/architecture.md](docs/architecture.md)。
70 -架构决策请参见 [docs/adr/README.md](docs/adr/README.md)。
46 +公网部署请看 [Deployment](docs/src/routes/deployment/+page.md),架构说明请看 [Architecture](docs/src/routes/architecture/+page.md)。
47
48 ## 示例
49
50 | 示例 | 说明 |
75 -|---------|-------------|
76 -| [nginx reverse proxy](docs/examples/nginx-proxy/) | 在 nginx 后部署 Portal,并使用 L4 SNI 路由和 TLS 终止 |
77 -| [nginx + multi-service](docs/examples/nginx-proxy-multi-service/) | 在同一个 nginx 实例后,将 Portal 与其他 Web 服务一起运行 |
51 +|---|---|
52 +| [nginx reverse proxy](docs/static/examples/nginx-proxy/) | 在 nginx 后部署 Portal,使用 L4 SNI 路由 |
53 +| [nginx + multi-service](docs/static/examples/nginx-proxy-multi-service/) | 在同一个 nginx 实例后运行 Portal 和其他服务 |
54
79 -## 公共中继注册表
55 +## 公共中继 Registry
56
81 -Portal 官方公共中继注册表为:
57 +官方公共中继 registry:
58
59 `https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json`
60
85 -Portal 隧道客户端可以默认包含这个注册表,Relay UI 也会从同一路径读取官方中继列表。
86 -
87 -如果你正在运营公共 Portal 中继,请提交一个 Pull Request,将你的中继 URL 添加到 `registry.json`。持续维护这个注册表可以让社区更容易发现公共中继。
88 -
89 -## Portal 如何提供端到端加密
90 -
91 -Portal 的设计目标是让租户 TLS 在你这一侧终止,而不是在中继侧终止。在正常数据路径中,中继只转发加密流量,无法访问租户 TLS 明文。
92 -
93 -1. 中继接收公网连接,并且只读取基于 SNI 路由所需的 TLS ClientHello。
94 -2. 它通过反向会话将租户连接作为原始加密字节转发,而不会终止租户 TLS。
95 -3. 你这一侧的 Portal 客户端充当 TLS 服务器,并在本地完成租户握手。
96 -4. 对于由中继托管的域名,Portal 客户端通过 `/v1/sign` 获取证书签名,此时中继只作为无密钥签名预言机使用。
97 -5. 会话密钥完全在你这一侧派生。中继只提供证书签名,不会接收租户流量密钥。
98 -6. 握手完成后,中继继续转发密文,无需租户 TLS 明文即可保持流量路由。
99 -
100 -Portal 还会检查中继是否真的保持了 TLS 透传。Portal 客户端会连接到自己的公网端点,并比较由客户端控制的两端观察到的 TLS exporter 值。如果两者不一致,`portal expose` 默认会拒绝该中继。
61 +如果你运行公共 Portal 中继,可以提交 Pull Request 把中继 URL 加入 `registry.json`。
62
102 -## 贡献
63 +## 安全模型
64
104 -欢迎社区贡献!
65 +Portal 的默认 stream 路径中,relay 只读取 TLS ClientHello 中的 SNI 来选择 lease,然后转发加密字节。SDK/客户端侧通过 relay 的 `/v1/sign` keyless signer 完成租户 TLS 握手,但会在本地派生 session key。relay API TLS、租户 TLS、QUIC datagram backhaul TLS 是不同的信任边界。
66
106 -1. Fork 此仓库
107 -2. 创建功能分支(`git checkout -b feature/amazing-feature`)
108 -3. 提交你的修改(`git commit -m 'Add amazing feature'`)
109 -4. 推送到分支(`git push origin feature/amazing-feature`)
110 -5. 创建 Pull Request
67 +Raw TCP 和 UDP 端口模式不自动增加租户 TLS。如果这些模式需要保密性,请使用应用层加密。
68
112 -## 许可证
69 +## License
70
114 -MIT License,详见 [LICENSE](LICENSE)
71 +MIT License - see [LICENSE](LICENSE)
cmd/portal-tunnel/README.md
+4
@@ -75,6 +75,7 @@ portal expose --name myapp \
75 - `--discovery=false` disables the public registry seed list and the runtime relay discovery expansion loop for that run. With `--discovery=false`, only the explicit `--relays` values are used.
76 - `--ban-mitm` enables strict rejection when the TLS self-probe detects termination in the path.
77 - `--tcp` requests a dedicated TCP port on the relay for raw TCP services that do not use TLS (e.g., Minecraft, game servers).
78 +- `--udp` requests a public UDP port on the relay and forwards datagrams to `--udp-addr` or the primary target.
79
80 Flags:
81
@@ -84,6 +85,7 @@ Flags:
85 --max-active-relays Maximum number of auto-selected relays; explicit --relays are always included
86 --ban-mitm Ban relay when the MITM self-probe detects TLS termination
87 --identity-path Identity JSON file path; created automatically when missing
88 +--identity-json Identity JSON payload; overrides --identity-path contents and is persisted there when both are set
89 --name Public hostname prefix (single DNS label); auto-generated when omitted
90 --description Service description metadata
91 --tags Service tags metadata (comma-separated)
@@ -91,6 +93,8 @@ Flags:
93 --owner Service owner metadata
94 --hide Hide service from relay listing screens
95 --tcp Request a dedicated TCP port on the relay for raw TCP services (no TLS)
96 +--udp Enable public UDP relay in addition to the default stream path
97 +--udp-addr Local UDP target address; defaults to the primary target when --udp is enabled
98 --http-route HTTP route mapping in PATH=UPSTREAM form; repeat for multiple routes
99 ```
100
docker-compose.yml
+1 -1
@@ -1,6 +1,6 @@
1 services:
2 # Optional: uncomment to enable auto-generated thumbnails for tunnel apps.
3 - # See docs/deployment.md section 8 for details.
3 + # See docs/src/routes/deployment/+page.md for details.
4 # headless-shell:
5 # image: chromedp/headless-shell:stable
6 # restart: unless-stopped
docs/src/routes/api-reference/+page.md
+3 -3
@@ -9,7 +9,7 @@ import Mermaid from '$lib/components/Mermaid.svelte'
9
10 # API Reference
11
12 -This page provides a complete reference for the Portal relay server HTTP API. All endpoints are served over HTTPS with keyless TLS.
12 +This page provides a complete reference for the Portal relay server HTTP API. Control-plane endpoints are served over the relay API HTTPS listener; tenant TLS is handled separately by the SDK using the relay's keyless signing endpoint.
13
14 ## Response Envelope
15
@@ -106,7 +106,7 @@ Admin clients authenticate using a shared secret key:
106 | `GET` | `/healthz` | Health check | None |
107 | `GET` | `/discovery` | Relay discovery | None |
108 | `POST` | `/discovery/announce` | Relay discovery self-announce | Signed Descriptor |
109 -| `GET` | `/v1/sign` | Keyless TLS signing | None |
109 +| `POST` | `/v1/sign` | Keyless TLS signing | None |
110 | `GET` | `/thumbnail/{hostname}` | Cached thumbnail screenshot | None |
111 | `GET` | `/tunnel/status` | Tunnel connection status | Access Token |
112
@@ -167,7 +167,7 @@ Submits this relay's signed descriptor to a bootstrap relay so registry-external
167 | `protocol_version` | `string` | Discovery protocol version |
168 | `accepted` | `boolean` | Whether the descriptor was accepted |
169
170 -### `GET /v1/sign`
170 +### `POST /v1/sign`
171
172 Keyless TLS signing endpoint. Used by the relay's keyless TLS infrastructure. Only available when the API server is configured with a TLS private key.
173
docs/src/routes/api-reference/sdk/+page.md
+20 -10
@@ -33,10 +33,11 @@ const reverseConnectDiagram = `sequenceDiagram
33
34 Browser->>Relay: TLS ClientHello (SNI: app.relay.example.com)
35 Relay->>Relay: Match SNI to lease
36 - Relay->>SDK: Forward traffic over hijacked conn
36 + Relay->>SDK: 0x02 marker, then encrypted tenant bytes
37 + Note over SDK: Tenant TLS terminates locally via keyless signer
38 SDK->>Relay: Response traffic
39 Relay->>Browser: Forward response
39 - Note over SDK,Browser: Bidirectional bridge established`
40 + Note over SDK,Browser: Relay bridges ciphertext only`
41 </script>
42
43 # SDK API
@@ -78,8 +79,8 @@ curl https://relay.example.com/sdk/domain
79 {
80 "ok": true,
81 "data": {
81 - "protocol_version": "2.0",
82 - "release_version": "v2.1.0"
82 + "protocol_version": "5",
83 + "release_version": "v2.1.5"
84 }
85 }
86 ```
@@ -121,6 +122,10 @@ Request a SIWE (Sign-In with Ethereum) challenge message for tunnel registration
122 |------|--------|-------------|
123 | `ip_banned` | 403 | Source IP is banned |
124 | `feature_unavailable` | 503 | UDP or TCP transport not available |
125 +| `udp_disabled` | 403 | UDP transport disabled by admin policy |
126 +| `udp_capacity_exceeded` | 503 | UDP lease capacity reached |
127 +| `tcp_port_disabled` | 403 | TCP port transport disabled by admin policy |
128 +| `tcp_port_capacity_exceeded` | 503 | TCP port lease capacity reached |
129
130 **Example:**
131
@@ -176,7 +181,7 @@ Complete tunnel registration by submitting the signed SIWE challenge. Returns an
181 | `identity` | `object` | Normalized identity (name + address) |
182 | `expires_at` | `string` | ISO 8601 lease expiration |
183 | `hostname` | `string` | Assigned tunnel hostname (e.g. `my-app.relay.example.com`) |
179 -| `access_token` | `string` | JWT access token for subsequent API calls |
184 +| `access_token` | `string` | ES256K JWT access token for subsequent API calls |
185 | `sni_port` | `int` | SNI port for QUIC transport (omitted if UDP not enabled) |
186 | `udp_addr` | `string` | UDP address for QUIC transport (e.g. `relay.example.com:4443`) |
187 | `udp_enabled` | `bool` | Whether UDP transport is active |
@@ -191,6 +196,11 @@ Complete tunnel registration by submitting the signed SIWE challenge. Returns an
196 | `hostname_conflict` | 409 | Hostname already registered |
197 | `ip_banned` | 403 | Source IP is banned |
198 | `udp_port_exhausted` | 503 | No UDP ports available |
199 +| `tcp_port_exhausted` | 503 | No TCP ports available |
200 +| `udp_disabled` | 403 | UDP transport disabled by admin policy |
201 +| `udp_capacity_exceeded` | 503 | UDP lease capacity reached |
202 +| `tcp_port_disabled` | 403 | TCP port transport disabled by admin policy |
203 +| `tcp_port_capacity_exceeded` | 503 | TCP port lease capacity reached |
204 | `feature_unavailable` | 503 | Requested transport not available |
205
206 **Example:**
@@ -217,7 +227,7 @@ curl -X POST https://relay.example.com/sdk/register \
227 },
228 "expires_at": "2025-01-01T00:01:00Z",
229 "hostname": "my-app.relay.example.com",
220 - "access_token": "eyJhbGciOiJFZDI1NTE5...",
230 + "access_token": "eyJhbGciOiJFUzI1Nksi...",
231 "udp_enabled": false,
232 "tcp_enabled": false
233 }
@@ -261,7 +271,7 @@ Renew an existing lease to extend its TTL. Returns a new access token that shoul
271 curl -X POST https://relay.example.com/sdk/renew \
272 -H "Content-Type: application/json" \
273 -d '{
264 - "access_token": "eyJhbGciOiJFZDI1NTE5...",
274 + "access_token": "eyJhbGciOiJFUzI1Nksi...",
275 "ttl": 60
276 }'
277 ```
@@ -273,7 +283,7 @@ curl -X POST https://relay.example.com/sdk/renew \
283 "ok": true,
284 "data": {
285 "expires_at": "2025-01-01T00:02:00Z",
276 - "access_token": "eyJhbGciOiJFZDI1NTE5...new"
286 + "access_token": "eyJhbGciOiJFUzI1Nksi...new"
287 }
288 }
289 ```
@@ -309,7 +319,7 @@ Empty data object on success.
319 curl -X POST https://relay.example.com/sdk/unregister \
320 -H "Content-Type: application/json" \
321 -d '{
312 - "access_token": "eyJhbGciOiJFZDI1NTE5..."
322 + "access_token": "eyJhbGciOiJFUzI1Nksi..."
323 }'
324 ```
325
@@ -361,7 +371,7 @@ On success, the server responds with `HTTP/1.1 200 OK` and hijacks the underlyin
371
372 ```bash
373 curl -X GET https://relay.example.com/sdk/connect \
364 - -H "X-Portal-Access-Token: eyJhbGciOiJFZDI1NTE5..." \
374 + -H "X-Portal-Access-Token: eyJhbGciOiJFUzI1Nksi..." \
375 -H "Connection: keep-alive" \
376 --http1.1
377 ```
docs/src/routes/architecture/+page.md
+3
@@ -166,6 +166,7 @@ UDP client
166
167 - Relay terminates admin/API TLS on the root host and exposes `/v1/sign` for tenant-side keyless signing.
168 - Control-plane HTTP (`/sdk/*`), reverse-session establishment (`/sdk/connect`), and tenant TLS are separate connections with different trust boundaries.
169 +- Relay API TLS, SDK relay-client TLS, SDK tenant-server TLS, and QUIC tunnel TLS are distinct configs even when they reuse the same relay certificate material.
170 - Relay does not terminate tenant TLS. It peeks ClientHello for SNI and bridges raw encrypted bytes after routing.
171 - SDK/tunnel endpoints terminate tenant TLS locally with a keyless-backed signer that calls the relay.
172 - In keyless TLS, the relay performs certificate private-key signing through `/v1/sign`, but the SDK/tunnel endpoint still runs the TLS server handshake and derives tenant TLS session keys locally.
@@ -293,7 +294,9 @@ Result: raw public UDP exposure with an internal QUIC datagram backhaul. UDP and
294 ## WireGuard Overlay and Discovery
295
296 - Discovery bootstraps from public HTTPS relay URLs, then expands through relay-to-relay `/discovery` polling and periodic self-announces to bootstrap relays through `/discovery/announce`.
297 +- SDK exposures consume relay discovery results to choose relays, but they do not announce themselves and do not serve `/discovery`.
298 - Discovery descriptors carry secp256k1 signatures that bind relay routing metadata to the relay identity. Lease access tokens remain separate and authorize tenant lease operations only.
299 +- `/discovery/announce` accepts only signed relay descriptors. Loopback or localhost relay descriptors are rejected because they cannot join the public discovery mesh.
300 - The overlay peer API is plain HTTP on the WireGuard network, not public Internet HTTP. It serves the same discovery payload shape used by public `/discovery`.
301 - Overlay failure affects inter-relay discovery and mesh synchronization only. Tenant stream routing, keyless TLS, register/renew/connect, and public UDP ingress do not depend on the WireGuard transport path.
302
docs/src/routes/cli-reference/+page.md
+17 -12
@@ -58,8 +58,10 @@ Instead of a positional target, you can use `--http-route` for multi-service rou
58 |------|------|---------|-------------|
59 | `--relays` | string | _(registry)_ | Portal relay API URLs (comma-separated, https only) |
60 | `--discovery` | bool | `true` | Include public registry relays and discover additional bootstraps |
61 +| `--max-active-relays` | int | `3` | Maximum auto-selected relays to keep connected; explicit relays are always included |
62 | `--ban-mitm` | bool | `true` | Ban relay when the MITM self-probe detects TLS termination |
63 | `--identity-path` | string | `./identity.json` | Identity JSON file path; created automatically when missing |
64 +| `--identity-json` | string | | Identity JSON payload; overrides `--identity-path` contents and is persisted there when both are set |
65 | `--name` | string | _(auto)_ | Public hostname prefix (single DNS label); auto-generated when omitted |
66 | `--description` | string | | Service description metadata |
67 | `--tags` | string | | Service tags metadata (comma-separated) |
@@ -67,6 +69,8 @@ Instead of a positional target, you can use `--http-route` for multi-service rou
69 | `--owner` | string | | Service owner metadata |
70 | `--hide` | bool | `false` | Hide service from relay listing screens |
71 | `--tcp` | bool | `false` | Request a dedicated TCP port for raw TCP services (no TLS) |
72 +| `--udp` | bool | `false` | Enable public UDP relay in addition to the default stream path |
73 +| `--udp-addr` | string | | Local UDP target address; defaults to the primary target when `--udp` is enabled |
74 | `--http-route` | string | | HTTP route mapping in `PATH=UPSTREAM` form; repeat for multiple routes |
75
76 **Examples:**
@@ -134,18 +138,19 @@ Unlike `portal expose`, `portal list` does not run the relay discovery expansion
138
139 ## Behavior Notes
140
137 -- **Identity persistence** — `portal expose` loads or creates a signing identity at `identity.json` (or `--identity-path`). Reusing the same path keeps the same address across runs.
138 -- **Multiple relays** — Multiple relay URLs are registered independently. Each relay gets its own lease. A relay going down does not stop healthy relays from serving.
139 -- **Retry semantics** — Relay startup and reconnect failures are retried in the background. The tunnel starts as soon as relay URLs pass local validation.
140 -- **Discovery expansion** — With discovery enabled, the relay list can grow beyond the initial registry + `--relays` values through relay-to-relay synchronization.
141 -- **MITM enforcement** — Enabled by default. The TLS self-probe runs asynchronously after real connections begin, with a 30-second cooldown between probes.
142 -- **503 on unreachable local service** — When the local target is unreachable, the tunnel returns an HTTP 503 page to the client.
143 -- **HTTP route mode** — Cannot be combined with `--udp`. Routes are HTTP-only.
144 -- **TCP port requirements** — `--tcp` requires the relay to have `TCP_ENABLED=true`, a valid `MIN_PORT/MAX_PORT` range, and TCP port enabled in the admin panel.
145 -- **Legacy CLI removed** — Bare `portal [flags]` is no longer accepted; use `portal expose` explicitly. `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variables are no longer used.
141 +- **Identity persistence** - `portal expose` loads or creates a signing identity at `identity.json` (or `--identity-path`). Reusing the same path keeps the same address across runs.
142 +- **Multiple relays** - Multiple relay URLs are registered independently. Each relay gets its own lease. A relay going down does not stop healthy relays from serving.
143 +- **Retry semantics** - Relay startup and reconnect failures are retried in the background. The tunnel starts as soon as relay URLs pass local validation.
144 +- **Discovery expansion** - With discovery enabled, the tunnel consumes relay `/discovery` results and reconciles its relay pool. The SDK does not announce itself and does not serve discovery endpoints.
145 +- **MITM enforcement** - Enabled by default. The TLS self-probe runs asynchronously after real connections begin, with a 30-second cooldown between probes.
146 +- **503 on unreachable local service** - When the local target is unreachable, the tunnel returns an HTTP 503 page to the client.
147 +- **HTTP route mode** - Cannot be combined with `--udp`. Routes are HTTP-only.
148 +- **TCP port requirements** - `--tcp` requires the relay to have `TCP_ENABLED=true`, a valid `MIN_PORT/MAX_PORT` range, and TCP port enabled in the admin panel.
149 +- **UDP requirements** - `--udp` requires the relay to have `UDP_ENABLED=true`, a valid `MIN_PORT/MAX_PORT` range, UDP enabled in the admin panel, and `SNI_PORT/udp` reachable for the QUIC backhaul.
150 +- **Legacy CLI removed** - Bare `portal [flags]` is no longer accepted; use `portal expose` explicitly. `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variables are no longer used.
151
152 ## Next Steps
153
149 -- **[Getting Started](/getting-started)** — Quick tutorial for your first tunnel
150 -- **[Concepts](/concepts)** — How Portal's encryption and relay model works
151 -- **[Deployment](/deployment)** — Run your own relay server
154 +- **[Getting Started](/getting-started)** - Quick tutorial for your first tunnel
155 +- **[Concepts](/concepts)** - How Portal's encryption and relay model works
156 +- **[Deployment](/deployment)** - Run your own relay server
docs/src/routes/configuration/+page.md
+15 -18
@@ -16,8 +16,10 @@ The relay server (`relay-server`) reads configuration from environment variables
16 | Variable | Default | Type | Description |
17 |----------|---------|------|-------------|
18 | `PORTAL_URL` | `https://localhost:4017` | string | Public base URL of this relay server |
19 +| `IDENTITY_PATH` | `./.portal-certs` | string | Directory path for relay identity, admin state, and TLS materials |
20 | `API_PORT` | `4017` | int | Admin/API server listen port |
21 | `SNI_PORT` | `443` | int | TCP SNI router listen port |
22 +| `WIREGUARD_PORT` | `51820` | int | Public and listen UDP port for relay discovery overlay |
23
24 ### Transport
25
@@ -36,13 +38,6 @@ The relay server (`relay-server`) reads configuration from environment variables
38 | `DISCOVERY` | `false` | bool | Serve relay discovery endpoints and poll discovery peers |
39 | `BOOTSTRAPS` | `""` | string | Additional bootstrap relay API URLs used for discovery expansion (comma-separated) |
40
39 -### Identity
40 -
41 -| Variable | Default | Type | Description |
42 -|----------|---------|------|-------------|
43 -| `IDENTITY_PATH` | `identity.json` | string | Relay identity JSON file path |
44 -| `ADMIN_SECRET_KEY` | `""` | string | Admin authentication secret. When empty, the server auto-generates a random key at startup and logs it. |
45 -
41 ### Proxy
42
43 | Variable | Default | Type | Description |
@@ -54,15 +49,13 @@ The relay server (`relay-server`) reads configuration from environment variables
49
50 | Variable | Default | Type | Description |
51 |----------|---------|------|-------------|
57 -| `KEYLESS_DIR` | `./.portal-certs` | string | Directory path for relay keyless TLS materials |
58 -| `ACME_DNS_PROVIDER` | `""` | string | ACME DNS provider for managed DNS-01/A-record sync and ENS gasless DNSSEC/TXT automation (`cloudflare` \| `gcloud` \| `route53`); leave empty to use manual `fullchain.pem`/`privatekey.pem` from `KEYLESS_DIR` |
52 +| `ACME_DNS_PROVIDER` | `""` | string | ACME DNS provider for managed DNS-01/A-record sync and ENS gasless DNSSEC/TXT automation (`cloudflare` \| `gcloud` \| `route53`); leave empty to use manual `fullchain.pem`/`privatekey.pem` from `IDENTITY_PATH` |
53 | `ENS_GASLESS_ENABLED` | `false` | bool | Enable ENS gasless DNS import automation for the managed DNS zone and lease hostnames |
54
55 ### Admin
56
57 | Variable | Default | Type | Description |
58 |----------|---------|------|-------------|
65 -| `ADMIN_SETTINGS_PATH` | `admin_settings.json` | string | Admin settings file path for persisted admin state |
59 | `HEADLESS_SHELL_URL` | `""` | string | Headless Chrome CDP WebSocket URL for thumbnail generation (e.g. `ws://headless-shell:9222`) |
60
61 ### Cloudflare
@@ -102,6 +95,7 @@ The `portal expose` subcommand accepts the following flags. Flags that read from
95 |------|---------|------|---------|-------------|
96 | `--relays` | | string | _(registry)_ | Additional Portal relay server API URLs (comma-separated; scheme omitted defaults to https) |
97 | `--discovery` | | bool | `true` | Include public registry relays and discover additional relay bootstraps |
98 +| `--max-active-relays` | `MAX_ACTIVE_RELAYS` | int | `3` | Maximum auto-selected relays to keep connected; explicit relays are always included |
99 | `--ban-mitm` | `BAN_MITM` | bool | `true` | Ban relay when the MITM self-probe detects TLS termination |
100
101 ### Identity
@@ -132,7 +126,7 @@ The `portal expose` subcommand accepts the following flags. Flags that read from
126
127 | Flag | Env Var | Type | Default | Description |
128 |------|---------|------|---------|-------------|
135 -| `--udp` | `UDP_ENABLED` | bool | `false` | Enable public UDP relay in addition to the default TCP relay |
129 +| `--udp` | `UDP_ENABLED` | bool | `false` | Enable public UDP relay in addition to the default stream path |
130 | `--udp-addr` | `UDP_ADDR` | string | | Local UDP target address for relayed datagrams (`host:port` or port only); defaults to the target when `--udp` is enabled |
131 | `--tcp` | `TCP_ENABLED` | bool | `false` | Request a dedicated TCP port on the relay for raw TCP services (no TLS; e.g., Minecraft, game servers) |
132
@@ -149,22 +143,25 @@ The `portal list` subcommand accepts the following flags:
143
144 ### `identity.json`
145
152 -Stores the cryptographic identity used to sign tunnel sessions. Created automatically by both `relay-server` and `portal expose` on first run.
146 +Stores the secp256k1 identity used to sign tunnel sessions and relay descriptors. `portal expose` treats `--identity-path` as a direct JSON file path. `relay-server` treats `IDENTITY_PATH` as a state directory and stores this file at `IDENTITY_PATH/identity.json`.
147
148 | Field | Type | Description |
149 |-------|------|-------------|
150 | `name` | string | Human-readable label for this identity |
157 -| `address` | string | Derived public address (used as the tunnel subdomain seed) |
158 -| `public_key` | string | Ed25519 public key (base64) |
159 -| `private_key` | string | Ed25519 private key (base64); keep secret |
151 +| `address` | string | Derived EVM address used for SIWE and identity ownership |
152 +| `public_key` | string | Compressed secp256k1 public key hex |
153 +| `private_key` | string | secp256k1 private key hex; keep secret |
154 +| `admin_secret_key` | string | Relay-only admin login secret, generated automatically when missing |
155 +| `wireguard_public_key` | string | Relay-only WireGuard overlay public key when discovery is enabled |
156 +| `wireguard_private_key` | string | Relay-only WireGuard overlay private key when discovery is enabled |
157
161 -The file path is controlled by `IDENTITY_PATH` / `--identity-path`. The same file can be shared across restarts to keep a stable address.
158 +The same identity file or state directory can be reused across restarts to keep a stable address.
159
160 ### `admin_settings.json`
161
162 Persists admin-panel state for the relay server. Managed automatically by the relay on write; do not edit manually while the server is running.
163
167 -The file path is controlled by `ADMIN_SETTINGS_PATH` / `--admin-settings-path`.
164 +Relay admin settings are stored at `IDENTITY_PATH/admin_settings.json`.
165
166 ---
167
@@ -172,7 +169,7 @@ The file path is controlled by `ADMIN_SETTINGS_PATH` / `--admin-settings-path`.
169
170 Set `ACME_DNS_PROVIDER` (or `--acme-dns-provider`) to one of the values below to enable automated TLS certificate issuance via DNS-01 challenges.
171
175 -When this variable is empty the relay server falls back to manually supplied `fullchain.pem` and `privatekey.pem` files in `KEYLESS_DIR`.
172 +When this variable is empty the relay server falls back to manually supplied `fullchain.pem` and `privatekey.pem` files in `IDENTITY_PATH`.
173
174 ### Cloudflare (`cloudflare`)
175
docs/src/routes/deployment/+page.md
+6
@@ -630,3 +630,9 @@ Verify from the container:
630 ```bash
631 docker exec -it portal-1 nslookup api4.ipify.org
632 ```
633 +
634 +### 8.4 Discovery announce warnings
635 +
636 +If logs show `relay discovery announce failed` with `404 page not found`, the target bootstrap relay is running an older release or does not serve `/discovery/announce`. This is warning-only: direct `/discovery` polling and explicit relay URLs can still work. The warnings stop once bootstrap relays are upgraded or removed from `BOOTSTRAPS`.
637 +
638 +Discovery announce is relay-to-relay only. A relay whose `PORTAL_URL` host is `localhost`, `127.0.0.1`, `::1`, or another loopback/local host is rejected by `/discovery/announce` because other relays and users cannot route to it. To join public discovery, set `PORTAL_URL` to a publicly reachable HTTPS hostname and expose the required TCP/UDP ports.
docs/src/routes/security-model/+page.md
+35 -36
@@ -1,60 +1,59 @@
1 ---
2 title: Security Model
3 -description: How Portal ensures end-to-end encryption and prevents relay-level eavesdropping.
3 +description: How Portal keeps tenant traffic opaque to relay operators.
4 ---
5
6 # Security Model
7
8 -Portal's security model is designed so that relay operators **cannot read tunnel traffic**, even though all data passes through their servers.
8 +Portal is designed so relay operators do not receive tenant traffic plaintext.
9
10 -## End-to-End TLS
10 +## Tenant TLS
11
12 -Tunnel traffic is encrypted with TLS between the client (browser) and your local service. The relay only sees opaque TCP bytes.
12 +For the default stream path, the relay only peeks at the TLS ClientHello long enough to read SNI and choose a lease. After that it bridges encrypted bytes over a reverse session.
13
14 +```text
15 +Client browser
16 + -> Relay SNI router
17 + -> Reverse session
18 + -> SDK tenant TLS terminator
19 + -> Local service
20 ```
15 -Client (browser) <-- TLS --> Your local app
16 - | ^
17 - | opaque TCP bytes |
18 - v |
19 - Relay server --- forwards ----->
20 -```
21
22 -The relay performs **TCP passthrough** — it connects raw TCP streams without terminating TLS.
22 +Tenant TLS terminates on the SDK side. The local service receives the decrypted stream from the tunnel process, while the relay only handles routing metadata and ciphertext.
23 +
24 +## Keyless Signing
25 +
26 +For relay-hosted names, the SDK builds a tenant-facing TLS server config backed by the relay's `/v1/sign` endpoint. The relay signs handshake digests with its certificate key, but it does not receive the negotiated tenant TLS session keys.
27
24 -## MITM Detection
28 +Relay API TLS is separate from tenant TLS:
29
26 -Portal includes built-in MITM detection:
30 +- Relay API HTTPS protects `/sdk/*`, `/discovery`, `/admin`, installers, and `/v1/sign`.
31 +- Tenant TLS protects end-user traffic for lease hostnames.
32 +- The internal QUIC datagram backhaul uses `SNI_PORT/udp` with ALPN `portal-tunnel`.
33
28 -1. The tunnel client generates a TLS certificate locally
29 -2. The certificate fingerprint is embedded in the public URL
30 -3. Connecting clients verify the fingerprint matches the server certificate
31 -4. Any relay-level interception would present a different certificate, triggering a mismatch
34 +## MITM Self-Probe
35
33 -## Relay Trust Model
36 +`portal expose` runs an asynchronous TLS passthrough self-probe after real tenant traffic starts. The SDK connects to its own public hostname, exports TLS keying material from the client side, recognizes the returning probe after SDK-side TLS termination, and compares exporter values.
37
35 -| What relays CAN see | What relays CANNOT see |
36 -|---------------------|----------------------|
37 -| Connection metadata (IP, timing) | Request/response content |
38 -| Tunnel name and domain | HTTP headers or body |
39 -| Traffic volume (bytes) | TLS-encrypted payload |
40 -| Connection duration | Application-layer data |
38 +Matching exporter values mean the sampled connection preserved passthrough. A mismatch is treated as suspected relay-side TLS termination. By default, `portal expose` bans that relay; use `--ban-mitm=false` for warning-only behavior.
39
42 -## SIWE Authentication
40 +## Relay Visibility
41
44 -Portal supports Sign-In with Ethereum (SIWE) for identity:
42 +| Relays can see | Relays cannot see |
43 +|---|---|
44 +| Source IP and timing metadata | HTTP headers or body |
45 +| Tunnel hostname/SNI | Tenant TLS session keys |
46 +| Traffic volume and connection duration | Application payload on the stream path |
47 +| Requested TCP/UDP transport metadata | Local service plaintext on the tenant TLS stream path |
48 +| Raw TCP/UDP payloads when the application protocol is unencrypted | Application-level encrypted raw TCP/UDP payloads |
49
46 -- Proves ownership of a tunnel name without a centralized auth server
47 -- ENS names provide portable, human-readable identity
48 -- No passwords or API keys stored anywhere
50 +Raw TCP and UDP port transports do not add tenant TLS. Use application-level encryption for those modes when confidentiality matters.
51
50 -## Best Practices
52 +## Identity
53
52 -1. **Always use HTTPS** — Portal provisions TLS certificates automatically
53 -2. **Verify certificate fingerprints** for sensitive applications
54 -3. **Run your own relay** if you need full control over the infrastructure
55 -4. **Rotate tunnel names** for temporary or throwaway use cases
54 +Registration uses a SIWE challenge signed by the SDK's secp256k1 identity key. The relay then issues a lease-scoped ES256K access token used by renew, unregister, reverse connect, and QUIC datagram authentication.
55
56 ## Next Steps
57
59 -- [Architecture](/architecture) — deep dive into Portal's internal design
60 -- [Self-Hosting](/self-hosting) — run your own relay server
58 +- [Architecture](/architecture) - deep dive into Portal's internal design
59 +- [Self-Hosting](/self-hosting) - run your own relay server
docs/src/routes/self-hosting/+page.md
+14 -14
@@ -23,18 +23,20 @@ You should have a relay running and accepting tunnel connections in about 10 min
23 Run the relay with a single Docker command:
24
25 ```bash
26 +mkdir -p ./relay-data
27 +# Put fullchain.pem and privatekey.pem in ./relay-data first, or configure ACME below.
28 docker run -d \
29 --name portal-relay \
30 --restart unless-stopped \
31 -p 443:443 \
32 -p 4017:4017 \
33 -e PORTAL_URL=https://relay.example.com:4017 \
32 - -e ADMIN_SECRET_KEY=changeme \
33 - -v $(pwd)/relay-data:/data \
34 - ghcr.io/gosuda/portal-tunnel/relay-server:latest
34 + -e IDENTITY_PATH=/portal-certs \
35 + -v $(pwd)/relay-data:/portal-certs \
36 + ghcr.io/gosuda/portal:latest
37 ```
38
37 -Replace `relay.example.com` with your domain and set a strong `ADMIN_SECRET_KEY`.
39 +Replace `relay.example.com` with your domain. The admin secret is generated on first start and stored in `IDENTITY_PATH/identity.json`.
40
41 ## Docker Compose Setup
42
@@ -44,7 +46,7 @@ For a more maintainable setup, use Docker Compose:
46 # compose.yml
47 services:
48 relay:
47 - image: ghcr.io/gosuda/portal-tunnel/relay-server:latest
49 + image: ghcr.io/gosuda/portal:latest
50 restart: unless-stopped
51 ports:
52 - "443:443"
@@ -53,10 +55,9 @@ services:
55 PORTAL_URL: https://relay.example.com:4017
56 API_PORT: "4017"
57 SNI_PORT: "443"
56 - ADMIN_SECRET_KEY: changeme
57 - IDENTITY_PATH: /data/identity.json
58 + IDENTITY_PATH: /portal-certs
59 volumes:
59 - - ./relay-data:/data
60 + - ./relay-data:/portal-certs
61 ```
62
63 Start it:
@@ -72,15 +73,14 @@ docker compose up -d
73 | `PORTAL_URL` | `https://localhost:4017` | Public base URL of your relay. Tunnels use this to register. |
74 | `API_PORT` | `4017` | Admin/API server port. |
75 | `SNI_PORT` | `443` | TCP SNI router port for tunnel traffic. |
75 -| `ADMIN_SECRET_KEY` | _(empty)_ | Secret for admin API access. Set this. |
76 -| `IDENTITY_PATH` | `identity.json` | Path to the relay's identity file (auto-created on first run). |
76 +| `IDENTITY_PATH` | `./.portal-certs` | Relay state directory containing `identity.json`, `admin_settings.json`, and TLS materials. |
77
78 ## Connecting Your Tunnel
79
80 Point `portal-tunnel` at your relay with the `--relays` flag:
81
82 ```bash
83 -portal expose --relays https://relay.example.com:4017 localhost:3000
83 +portal expose --relays https://relay.example.com:4017 --discovery=false localhost:3000
84 ```
85
86 The `--relays` flag accepts a comma-separated list of relay API URLs. If you omit the scheme, `https` is assumed.
@@ -88,7 +88,7 @@ The `--relays` flag accepts a comma-separated list of relay API URLs. If you omi
88 To avoid typing `--relays` every time, use a shell alias:
89
90 ```bash
91 -alias portal-relay='portal expose --relays https://relay.example.com:4017'
91 +alias portal-relay='portal expose --relays https://relay.example.com:4017 --discovery=false'
92 portal-relay localhost:3000
93 ```
94
@@ -105,7 +105,7 @@ DNS propagation typically takes a few minutes but can take up to 48 hours depend
105
106 ## Optional: TLS with ACME
107
108 -By default the relay expects you to place `fullchain.pem` and `privatekey.pem` in the `KEYLESS_DIR` directory (`.portal-certs` by default). For automatic certificate management via DNS-01 challenges, set `ACME_DNS_PROVIDER`:
108 +By default the relay expects you to place `fullchain.pem` and `privatekey.pem` in the `IDENTITY_PATH` directory (`.portal-certs` by default). For automatic certificate management via DNS-01 challenges, set `ACME_DNS_PROVIDER`:
109
110 ```yaml
111 environment:
@@ -166,7 +166,7 @@ sudo ufw allow 4017/tcp
166
167 **Certificate errors**
168
169 -If you see TLS errors on the client side, confirm your certificate files are present in `KEYLESS_DIR` and that `fullchain.pem` includes the full chain (leaf + intermediates). If using ACME, check the relay logs for DNS provider authentication errors:
169 +If you see TLS errors on the client side, confirm your certificate files are present in `IDENTITY_PATH` and that `fullchain.pem` includes the full chain (leaf + intermediates). If using ACME, check the relay logs for DNS provider authentication errors:
170
171 ```bash
172 docker compose logs relay --tail 50
docs/src/routes/tcp-udp-tunneling/+page.md
+1 -1
@@ -99,7 +99,7 @@ This is a complete walkthrough for exposing a Minecraft Java Edition server (`lo
99 ```yaml
100 services:
101 relay:
102 - image: ghcr.io/gosuda/portal-tunnel/relay-server:latest
102 + image: ghcr.io/gosuda/portal:latest
103 environment:
104 TCP_ENABLED: "true"
105 MIN_PORT: "10000"
docs/static/examples/nginx-proxy-multi-service/.env.example
+6 -7
@@ -5,22 +5,22 @@
5 PORTAL_URL=https://portal.example.com
6 BOOTSTRAPS=
7 DISCOVERY=true
8 -IDENTITY_PATH=/portal-certs/identity.json
8 +IDENTITY_PATH=/portal-certs
9
10 # Listener ports
11 API_PORT=4017
12 SNI_PORT=4443
13 +WIREGUARD_PORT=51820
14 # Set when enabling public UDP or raw TCP lease ports.
15 MIN_PORT=0
16 MAX_PORT=0
17 UDP_ENABLED=false
18 TCP_ENABLED=false
19
19 -# TLS/ACME and keyless materials
20 -KEYLESS_DIR=/portal-certs
20 +# TLS/ACME materials live under IDENTITY_PATH as fullchain.pem/privatekey.pem.
21
22 # Supported managed values: cloudflare, gcloud, route53
23 -ACME_DNS_PROVIDER=cloudflare
23 +ACME_DNS_PROVIDER=
24
25 # Cloudflare API token (required when ACME_DNS_PROVIDER=cloudflare)
26 CLOUDFLARE_TOKEN=
@@ -44,8 +44,7 @@ AWS_DNSSEC_KMS_KEY_ARN=
44 # for DNSSEC and ENS TXT automation, even when certificate files are managed manually.
45 ENS_GASLESS_ENABLED=false
46
47 -# Admin/auth configuration
48 -ADMIN_SECRET_KEY=
47 +# Admin/auth configuration. The admin secret is generated and stored in IDENTITY_PATH/identity.json.
48 LANDING_PAGE_ENABLED=false
49 # Enable when the relay is behind nginx/ingress/load balancers and should trust forwarded client IP headers.
50 # Optionally restrict which proxy source ranges may supply those headers; leave empty for default private/loopback proxy ranges.
@@ -54,5 +53,5 @@ TRUSTED_PROXY_CIDRS=127.0.0.0/8
53
54 # Optional: auto-generated thumbnail screenshots for tunnel apps without a thumbnail.
55 # Requires the headless-shell sidecar (chromedp/headless-shell) in docker-compose.
57 -# Leave empty or remove to disable. See docs/deployment.md section 8.
56 +# Leave empty or remove to disable. See docs/src/routes/deployment/+page.md.
57 # HEADLESS_SHELL_URL=ws://headless-shell:9222
docs/static/examples/nginx-proxy-multi-service/docker-compose.yaml
+1 -1
@@ -52,7 +52,7 @@ services:
52
53 # ─── headless-shell (optional thumbnail screenshot sidecar) ─────────────────
54 # Uncomment to enable auto-generated thumbnails for tunnel apps.
55 - # See docs/deployment.md section 8 for details.
55 + # See docs/src/routes/deployment/+page.md for details.
56 # headless-shell:
57 # image: chromedp/headless-shell:stable
58 # restart: unless-stopped
docs/static/examples/nginx-proxy/.env.example
+6 -7
@@ -5,22 +5,22 @@
5 PORTAL_URL=https://portal.example.com
6 BOOTSTRAPS=
7 DISCOVERY=true
8 -IDENTITY_PATH=/portal-certs/identity.json
8 +IDENTITY_PATH=/portal-certs
9
10 # Listener ports
11 API_PORT=4017
12 SNI_PORT=4443
13 +WIREGUARD_PORT=51820
14 # Set when enabling public UDP or raw TCP lease ports.
15 MIN_PORT=0
16 MAX_PORT=0
17 UDP_ENABLED=false
18 TCP_ENABLED=false
19
19 -# TLS/ACME and keyless materials
20 -KEYLESS_DIR=/portal-certs
20 +# TLS/ACME materials live under IDENTITY_PATH as fullchain.pem/privatekey.pem.
21
22 # Supported managed values: cloudflare, gcloud, route53
23 -ACME_DNS_PROVIDER=cloudflare
23 +ACME_DNS_PROVIDER=
24
25 # Cloudflare API token (required when ACME_DNS_PROVIDER=cloudflare)
26 CLOUDFLARE_TOKEN=
@@ -44,8 +44,7 @@ AWS_DNSSEC_KMS_KEY_ARN=
44 # for DNSSEC and ENS TXT automation, even when certificate files are managed manually.
45 ENS_GASLESS_ENABLED=false
46
47 -# Admin/auth configuration
48 -ADMIN_SECRET_KEY=
47 +# Admin/auth configuration. The admin secret is generated and stored in IDENTITY_PATH/identity.json.
48 LANDING_PAGE_ENABLED=false
49 # Enable when the relay is behind nginx/ingress/load balancers and should trust forwarded client IP headers.
50 # Optionally restrict which proxy source ranges may supply those headers; leave empty for default private/loopback proxy ranges.
@@ -54,5 +53,5 @@ TRUSTED_PROXY_CIDRS=127.0.0.0/8
53
54 # Optional: auto-generated thumbnail screenshots for tunnel apps without a thumbnail.
55 # Requires the headless-shell sidecar (chromedp/headless-shell) in docker-compose.
57 -# Leave empty or remove to disable. See docs/deployment.md section 8.
56 +# Leave empty or remove to disable. See docs/src/routes/deployment/+page.md.
57 # HEADLESS_SHELL_URL=ws://headless-shell:9222
docs/static/examples/nginx-proxy/docker-compose.yaml
+1 -1
@@ -45,7 +45,7 @@ services:
45
46 # ─── headless-shell (optional thumbnail screenshot sidecar) ─────────────────
47 # Uncomment to enable auto-generated thumbnails for tunnel apps.
48 - # See docs/deployment.md section 8 for details.
48 + # See docs/src/routes/deployment/+page.md for details.
49 # headless-shell:
50 # image: chromedp/headless-shell:stable
51 # restart: unless-stopped
sdk/expose.go
+1 -5
@@ -20,11 +20,7 @@ import (
20 )
21
22 // Exposure owns the lifecycle of one or more relay listeners and accepts
23 -// traffic from all of them through one net.Listener. The SDK is a pure relay
24 -// client: it never gossips its own descriptor into the discovery mesh and
25 -// never serves the /discovery endpoint. When SDK discovery is enabled, the
26 -// exposure resolves registry seed relays and consumes discovery results from
27 -// attached relays.
23 +// traffic from all of them through one net.Listener.
24 type Exposure struct {
25 cancel context.CancelFunc
26 done <-chan struct{}
types/types.go
+1 -1
@@ -1,7 +1,7 @@
1 package types
2
3 const (
4 - ReleaseVersion = "v2.1.4"
4 + ReleaseVersion = "v2.1.5"
5 SDKVersion = "5"
6 DiscoveryVersion = "6"
7 PortalRelayRegistryURL = "https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json"