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"