Refactor documentation for clarity and detail

Kim committed May 11, 2026 at 14:54 UTC c35a01a5c6537230b5bda879a2a9e74cbdfb8466
9 files changed +784 -480
README.md
+89 -69
@@ -1,54 +1,79 @@
1 -# PORTAL - Public Open Relay To Access Localhost
1 +# Portal - The Trustless Relay Network for Localhost
2
3 [English](./README.md) | [简体中文](./README.zh-CN.md)
4
5 <p align="center"><img width="800" alt="Portal Demo" src="./portal.gif" /></p>
6
7 -<p align="center">Expose your local application to the public internet - no port forwarding, no NAT, no DNS setup.<br />Portal is a trustless relay network where relays cannot access your traffic. Connect to any relay or chain several for better anonymity.</p><br />
7 +<p align="center"><b>Expose local services to the public internet with zero trust in the relay operator.</b><br/>No port forwarding. No inbound firewall rules. No manual DNS setup. No surveillance.</p>
8
9 -## Features
9 +## Why Portal? The Trustless Advantage
10
11 -- **Public HTTPS for localhost**: NAT-friendly publishing via TCP passthrough
12 -- **End-to-end TLS**: TLS terminates on the client, relays can`t access plaintext or session keys
13 -- **Relay discovery pools**: Choose multiple discovered relays as a flexible connection pool
14 -- **Multi-hop relay routing**: Improve anonymity by splitting the traffic path across multiple relays
15 -- **Self-hosted relays**: Run your own relay or connect to public relays
16 -- **No login, no API keys**: Authenticate ownership using SIWE, with ENS-based identity support
17 -- **Raw TCP/UDP routing**: Native TCP reverse sessions, optional UDP, and dedicated TCP ports for non-TLS services
11 +Most tunneling services (ngrok, Cloudflare Tunnel) terminate your TLS connection at their edge. This means **they can read your plaintext traffic**. Portal is built on a fundamentally different model: **relays are blind by design**.
12 +
13 +- **End-to-End Encryption with ECH** — Your HTTPS traffic stays encrypted through the relay, and ECH-capable clients avoid exposing the real hostname in plaintext SNI. Portal keeps TLS on your machine, so relay operators cannot read your web traffic or easily profile it by hostname.
14 +
15 +- **Built-in MITM Detection** — Portal actively self-probes its own connection after real traffic begins. It compares TLS keying material exported on both the client and server sides. A mismatch is treated as suspected relay-side TLS termination and the relay is banned by default.
16 +
17 +- **Self-Hostable, Fully Open Source** — Run your own relay with a single command. The relay is MIT-licensed with no enterprise tier, no feature gating, and no call-home. Your relay, your rules.
18 +
19 +- **Anonymous Relay Network** — Because relays are trustless, you can connect to any public relay in the registry without compromising your privacy. Combine self-hosted relays with public relays in a pool — or chain them in a multi-hop route — to split trust across independent operators you choose.
20 +
21 +- **Multi-Hop Relay Routing** — Chain multiple relays together (similar to Tor). No single relay knows both the origin and the destination of the traffic. Use `--multi-hop-depth 3` to select a three-hop route automatically.
22 +
23 +- **No Accounts, No API Keys** — Authentication uses SIWE (Sign-In with Ethereum) with a locally generated secp256k1 key pair. No email, no registration, no vendor lock-in.
24
25 ## Comparison
26
27 | | Portal | ngrok | Cloudflare Tunnel | frp |
28 |---|---|---|---|---|
23 -| End-to-end encryption | **Yes** | Optional | No | No |
24 -| TLS termination | Client-side | Edge (default) | Edge (always) | Server-side |
25 -| MITM detection | **Built-in** | No | No | No |
29 +| End-to-end tenant TLS | **Yes** | No | No | No |
30 +| SNI hiding (ECH) | **Yes** | No | No | No |
31 +| MITM self-probe | **Built-in** | No | No | No |
32 +| Multi-hop routing | **Yes** | No | No | No |
33 +| Multi-relay failover | **Yes** | Managed | Built-in | No |
34 | Self-hostable | **Yes** | Enterprise only | No | Yes |
27 -| multi-relay failover | **Yes** | Managed | Built-in multi-DC | No |
28 -| multi-hop routing | **Yes** | No | No | No |
35 | Custom domain | **Yes** | Paid plans | Yes | Yes |
30 -| Transport | Raw TCP / UDP | HTTP/S, TCP, TLS | HTTP/S, TCP, UDP | HTTP/S, TCP, UDP |
31 -| Non-TLS TCP port routing | **Yes** | Paid plans | No | Yes |
32 -| Open source | **MIT** | No | Client only (Apache 2.0) | Apache 2.0 |
33 -| Account required | **No** (SIWE) | Yes | Yes | No |
36 +| Raw TCP port routing | **Yes** | Paid plans | No | Yes |
37 +| UDP routing | **Yes** | Yes | Yes | Yes |
38 +| Open source | **MIT** | No | Client only | Apache 2.0 |
39 +| Account required | **No** | Yes | Yes | No |
40
41 ## Quick Start
42
37 -### Expose your local app:
43 +### Expose a local service
44 +
45 +**macOS / Linux:**
46
47 ```bash
48 curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
49 portal expose 3000
50 ```
51
52 +**Windows (PowerShell):**
53 +
54 ```powershell
55 $ProgressPreference = 'SilentlyContinue'
56 irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
57 portal expose 3000
58 ```
59
50 -Then access your app via a public HTTPS URL.
51 -For install details, see [cmd/portal-tunnel/README.md](cmd/portal-tunnel/README.md).
60 +Portal prints a public HTTPS URL for your local app instantly. More examples:
61 +
62 +```bash
63 +# Custom name and relay
64 +portal expose 3000 --name myapp --relays https://portal.example.com --discovery=false
65 +
66 +# Mount frontend and API behind one URL
67 +portal expose --name myapp \
68 + --http-route /api=http://127.0.0.1:3001 \
69 + --http-route /=http://127.0.0.1:5173
70 +
71 +# Raw TCP port (Minecraft, databases, SSH)
72 +portal expose localhost:25565 --name minecraft --tcp
73 +
74 +# Three-hop route for maximum anonymity
75 +portal expose 3000 --multi-hop-depth 3
76 +```
77
78 ### Run your own relay
79
@@ -58,70 +83,65 @@ cd portal-tunnel && cp .env.example .env
83 docker compose up
84 ```
85
61 -For deployment to a public domain, see [Deployment](docs/src/routes/deployment/+page.md).
86 +For public deployment with DNS automation (ACME), TCP/UDP port ranges, and admin settings, see [Deployment](docs/src/routes/deployment/+page.md).
87
63 -### Run native app (Advanced)
88 +## How End-to-End Encryption Works
89
65 -See [portal-toys](https://github.com/gosuda/portal-toys) for more examples.
90 +```text
91 +Browser
92 + → Relay SNI router (reads only routing token, forwards raw bytes)
93 + → Reverse session
94 + → Portal tunnel (performs TLS handshake locally, derives session keys)
95 + → Local service
96 +```
97 +
98 +1. The relay accepts the incoming connection and reads only the TLS ClientHello for SNI-based routing.
99 +2. It forwards the raw encrypted stream over the reverse session without terminating TLS.
100 +3. The Portal tunnel on your side completes the TLS handshake locally. Session keys are derived on your machine.
101 +4. For relay-hosted domains, the tunnel obtains certificate signatures via `/v1/sign`, using the relay only as a keyless signing oracle. The relay signs handshake digests but never receives session keys.
102 +5. After the handshake, the relay continues forwarding ciphertext without access to plaintext.
103
67 -## Architecture
104 +When ECH is enabled, the relay also cannot see the actual tenant hostname. It routes by an opaque token derived from the tunnel identity, while the real SNI stays inside the ECH-protected ClientHello.
105
69 -See [Architecture](docs/src/routes/architecture/+page.md).
106 +## How Multi-Hop Routing Works
107
71 -## Examples
108 +```text
109 +Browser
110 + → Entry relay (sees only the opaque route hostname)
111 + → Middle relay (sees only the next-hop token)
112 + → Exit relay (sees only the reverse session token)
113 + → Portal tunnel
114 + → Local service
115 +```
116
73 -| Example | Description |
74 -|---------|-------------|
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 |
117 +Each relay in the chain knows only its immediate neighbors. No single relay holds the full path. Tenant TLS still terminates only on your side — no relay in the chain receives tenant TLS plaintext.
118
119 ## Public Relay Registry
120
121 Portal's official public relay registry is:
122
82 -`https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json`
83 -
84 -Portal tunnel clients can include this registry by default, and the relay UI also reads from the same path to show the official relay list.
85 -
86 -If you operate a public Portal relay, open a Pull Request to add your relay URL to `registry.json`. Keeping the registry updated makes public relays easier for the community to discover.
87 -
88 -## How Portal Provides End-to-End Encryption
89 -
90 -Portal is designed so that tenant TLS terminates on your side rather than at the relay. In the normal data path, the relay forwards encrypted traffic without access to tenant TLS plaintext.
91 -
92 -1. The relay accepts the public connection and reads only the TLS ClientHello required for SNI-based routing.
93 -2. It forwards the tenant connection as raw encrypted bytes over the reverse session without terminating tenant TLS.
94 -3. The Portal client on your side acts as the TLS server and completes the tenant handshake locally.
95 -4. For relay-hosted domains, the Portal client obtains certificate signatures via `/v1/sign`, using the relay only as a keyless signing oracle.
96 -5. Session keys are derived entirely on your side. The relay provides certificate signatures only and does not receive tenant traffic secrets.
97 -6. After the handshake, the relay continues forwarding ciphertext without needing tenant TLS plaintext to keep routing traffic.
98 -
99 -Portal also checks that the relay is preserving TLS passthrough. The Portal client connects to its own public endpoint and compares TLS exporter values observed on both client-controlled ends. If they differ, `portal expose` rejects the relay by default.
100 -
101 -## How Portal Provides Multi-Hop Relay Routing
102 -
103 -Portal can route a tunnel through an ordered chain of relays. This splits responsibility and visibility across multiple nodes instead of relying on a single relay.
123 +```text
124 +https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json
125 +```
126
105 -1. The client selects multiple relays and forms a relay chain.
106 -2. Public traffic enters through the ingress relay, which only knows the hostname it serves.
107 -3. Each relay forwards to the next hop without learning the whole route.
108 -4. The last relay reaches your Portal client through the reverse session.
109 -5. Tenant TLS still terminates only on your side. No relay receives tenant TLS plaintext.
127 +Tunnel clients include this registry by default. If you operate a public Portal relay, open a pull request to add your relay URL to `registry.json`.
128
111 -This improves anonymity by splitting routing knowledge across independent relays while preserving Portal's end-to-end encrypted traffic model.
129 +## Documentation
130
113 -For CLI usage, see [cmd/portal-tunnel/README.md](cmd/portal-tunnel/README.md).
131 +- [CLI Reference](cmd/portal-tunnel/README.md)
132 +- [Concepts](docs/src/routes/concepts/+page.md)
133 +- [Security Model](docs/src/routes/security-model/+page.md)
134 +- [Architecture](docs/src/routes/architecture/+page.md)
135 +- [Deployment](docs/src/routes/deployment/+page.md)
136 +- [Configuration Reference](docs/src/routes/configuration/+page.md)
137
138 ## Contributing
139
117 -We welcome contributions from the community!
118 -
119 -1. Fork the repository
120 -2. Create a feature branch (git checkout -b feature/amazing-feature)
121 -3. Commit your changes (git commit -m 'Add amazing feature')
122 -4. Push to the branch (git push origin feature/amazing-feature)
123 -5. Open a Pull Request
140 +1. Fork the repository.
141 +2. Create a feature branch (`git checkout -b feature/amazing-feature`).
142 +3. Make the change with focused tests or docs.
143 +4. Open a pull request.
144
145 ## License
146
127 -MIT License - see [LICENSE](LICENSE)
147 +MIT License — see [LICENSE](LICENSE).
cmd/portal-tunnel/README.md
+234 -98
@@ -1,8 +1,21 @@
1 # Portal CLI
2
3 -`cmd/portal-tunnel` builds the `portal` tunnel CLI. It connects a local service to one or more Portal relays.
3 +`cmd/portal-tunnel` builds the `portal` CLI. It connects local services to
4 +Portal relays without requiring inbound firewall rules, port forwarding, or
5 +manual DNS setup.
6
5 -## Usage
7 +Portal's default model is intentionally simple:
8 +
9 +- The relay owns transport, lease registration, routing, and relay policy.
10 +- The tunnel process owns the exposed endpoint behavior.
11 +- In the default HTTPS stream path, tenant TLS terminates in the tunnel process,
12 + not at the relay.
13 +- In routed HTTP mode, the tunnel process runs the HTTP reverse proxy. The relay
14 + is still not an HTTP proxy.
15 +- In raw TCP and UDP modes, the relay allocates public transport endpoints and
16 + forwards traffic to the tunnel process.
17 +
18 +## Install
19
20 Install directly from the official GitHub release assets:
21
@@ -19,41 +32,103 @@ portal expose 3000
32 portal list
33 ```
34
22 -If your relay publishes its own installer, you can use that instead:
35 +If your relay publishes its own installer, use that relay instead:
36
37 ```bash
38 curl -sSL https://portal.example.com/install.sh | bash
26 -portal expose 3000
27 -portal list
39 +portal expose 3000 --relays https://portal.example.com --discovery=false
40 ```
41
42 ```powershell
43 $ProgressPreference = 'SilentlyContinue'
44 irm https://portal.example.com/install.ps1 | iex
33 -portal expose 3000
34 -portal list
45 +portal expose 3000 --relays https://portal.example.com --discovery=false
46 ```
47
37 -Custom relay and metadata example:
48 +## Choosing A Mode
49 +
50 +Use the default stream mode for most local web apps:
51
52 ```text
40 -portal expose localhost:8080 \
41 - --name myapp \
42 - --identity-path ~/.config/portal/myapp.identity.json \
43 - --relays https://portal.example.com \
44 - --description "Service description" \
45 - --tags tag1,tag2 \
46 - --thumbnail https://example.com/thumb.png \
47 - --owner "Portal Operator"
53 +portal expose 3000 --name myapp
54 +```
55 +
56 +This publishes `myapp.<relay-root-host>` as HTTPS. The relay routes by SNI and
57 +bridges the connection to the tunnel process. The tunnel process performs the
58 +tenant TLS handshake locally and then proxies the byte stream to
59 +`127.0.0.1:3000`.
60 +
61 +Use routed HTTP mode when one public URL should mount multiple local HTTP
62 +services:
63 +
64 +```text
65 +portal expose --name myapp \
66 + --http-route /api=http://127.0.0.1:3001 \
67 + --http-route /=http://127.0.0.1:5173
68 ```
69
50 -TCP port routing example (e.g., Minecraft server):
70 +This is a tunnel-controlled HTTP reverse proxy. The relay still only transports
71 +connections. Because the tunnel process parses HTTP in this mode, this is the
72 +right mode for HTTP-specific behavior such as path routing, response header
73 +policy, redirect rewriting, and cookie path remapping.
74 +
75 +Use dedicated raw TCP mode for non-HTTP services that need a public TCP port:
76
77 ```text
78 portal expose localhost:25565 --name minecraft --tcp
79 ```
80
56 -Multi-port HTTP aggregation example:
81 +The relay allocates a TCP port from its configured port range and bridges raw
82 +TCP to the local target. This path does not add TLS; use application-level
83 +encryption when the protocol needs confidentiality.
84 +
85 +Use UDP mode when the service needs a public UDP port:
86 +
87 +```text
88 +portal expose localhost:8080 --udp --udp-addr localhost:19132 --name game
89 +```
90 +
91 +The primary target still receives stream traffic. UDP datagrams are forwarded to
92 +`--udp-addr`; when omitted, UDP uses the primary target.
93 +
94 +## Relay And SEO Boundaries
95 +
96 +The relay cannot safely inject HTTP headers, `robots.txt`, `noindex`, or content
97 +policy into the default passthrough stream path. It does not own the HTTP
98 +response body, and it is not supposed to terminate tenant TLS.
99 +
100 +If a relay is used as a public multi-tenant service, do not put arbitrary user
101 +tunnels under a brand domain that also carries first-party SEO value. Use a
102 +separate tunnel domain for shared wildcard leases, and keep brand, docs, admin,
103 +and product pages on first-party hosts.
104 +
105 +Routed HTTP mode can enforce HTTP policy only inside cooperating tunnel
106 +processes. It is useful for product features, but it is not a substitute for
107 +domain separation because users can still choose the default passthrough path.
108 +
109 +## Commands
110 +
111 +### `portal expose [flags] <target>`
112 +
113 +Expose one local target through the default stream path.
114 +
115 +```text
116 +portal expose 3000
117 +portal expose localhost:8080 --name myapp
118 +portal expose http://127.0.0.1:8080 --name local-http
119 +```
120 +
121 +`<target>` accepts:
122 +
123 +- a bare port, such as `3000`
124 +- a `host:port`
125 +- an `http://host:port` or `https://host:port` URL
126 +
127 +Bare ports resolve to `127.0.0.1:<port>`. URL inputs are accepted for address
128 +parsing only; paths, queries, and fragments are not supported.
129 +
130 +Instead of `<target>`, repeat `--http-route PATH=UPSTREAM` to run routed HTTP
131 +mode:
132
133 ```text
134 portal expose --name myapp \
@@ -61,66 +136,104 @@ portal expose --name myapp \
136 --http-route /=http://127.0.0.1:5173
137 ```
138
64 -## Commands
139 +Route matching is longest-prefix-first. A route like
140 +`/api=http://127.0.0.1:3001` matches `/api/*` and strips the `/api` prefix before
141 +proxying to the upstream.
142
66 -### `portal expose [flags] <target>`
143 +Routed HTTP mode automatically:
144 +
145 +- forwards `X-Forwarded-*`
146 +- rewrites matching upstream `Location` redirects back to the public route path
147 +- strips loopback cookie domains
148 +- remaps cookie paths to the mounted route prefix
149 +
150 +Mode constraints:
151 +
152 +- `<target>` cannot be combined with `--http-route`.
153 +- `--http-route` cannot be combined with `--udp`.
154 +- Multi-hop currently supports only the default SNI TLS stream transport.
155 +- `--multi-hop` cannot be combined with automatic `--multi-hop-depth`.
156
68 -- `<target>` accepts a bare port like `3000`, a `host:port`, or an `http(s)://host:port` URL.
69 -- Bare ports resolve to `127.0.0.1:<port>`.
70 -- Instead of `<target>`, you can repeat `--http-route PATH=UPSTREAM` to aggregate multiple local HTTP services behind one public URL.
71 -- Route matching is longest-prefix-first. `/api=http://127.0.0.1:3001` matches `/api/*` and strips the `/api` prefix before proxying to the upstream.
72 -- Routed HTTP mode automatically forwards `X-Forwarded-*`, rewrites upstream `Location` redirects back to the public route path, and strips loopback cookie domains while remapping cookie paths to the mounted route prefix.
73 -- `--name` is optional. When omitted, the CLI generates a name for that run.
74 -- `--relays` adds explicit relay API URLs for that run. Explicit relays are always kept connected and are not counted against `--max-active-relays`.
75 -- `--multi-hop` sets one ordered multi-hop relay path for that run.
76 -- `--multi-hop-depth` automatically selects one multi-hop relay path with that hop count.
77 -- `--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.
78 -- `--ban-mitm` enables strict rejection when the TLS self-probe detects termination in the path.
79 -- `--tcp` requests a dedicated TCP port on the relay for raw TCP services that do not use TLS (e.g., Minecraft, game servers).
80 -- `--udp` requests a public UDP port on the relay and forwards datagrams to `--udp-addr` or the primary target.
81 -
82 -Flags:
157 +Common flags:
158
159 ```text
85 ---relays Portal relay API URLs (comma-separated, https only)
86 ---multi-hop Ordered multi-hop relay API URLs, comma-separated
87 ---multi-hop-depth Automatically select one multi-hop route with this hop count; 0 or 1 disables multi-hop
88 ---discovery Include public registry relays and discover additional relay bootstraps
89 ---max-active-relays Maximum number of auto-selected relays; explicit --relays are always included
90 ---ban-mitm Ban relay when the MITM self-probe detects TLS termination
91 ---identity-path Identity JSON file path; created automatically when missing
92 ---identity-json Identity JSON payload; overrides --identity-path contents and is persisted there when both are set
93 ---name Public hostname prefix (single DNS label); auto-generated when omitted
94 ---description Service description metadata
95 ---tags Service tags metadata (comma-separated)
96 ---thumbnail Service thumbnail URL metadata
97 ---owner Service owner metadata
98 ---hide Hide service from relay listing screens
99 ---tcp Request a dedicated TCP port on the relay for raw TCP services (no TLS)
100 ---udp Enable public UDP relay in addition to the default stream path
101 ---udp-addr Local UDP target address; defaults to the primary target when --udp is enabled
102 ---http-route HTTP route mapping in PATH=UPSTREAM form; repeat for multiple routes
160 +--name Public hostname prefix; auto-generated when omitted
161 +--relays Additional relay API URLs, comma-separated
162 +--discovery Include registry relays and relay discovery expansion
163 +--max-active-relays Maximum auto-selected relays; explicit relays are always included
164 +--multi-hop Ordered multi-hop relay API URLs, comma-separated
165 +--multi-hop-depth Automatically select one multi-hop route with this hop count
166 +--ban-mitm Ban relay when the TLS self-probe detects termination
167 +--identity-path Identity JSON file path; created automatically when missing
168 +--identity-json Identity JSON payload; overrides --identity-path when set
169 +--description Service description metadata
170 +--tags Service tags metadata, comma-separated
171 +--thumbnail Service thumbnail URL metadata
172 +--owner Service owner metadata
173 +--hide Hide service from relay listing screens
174 +--http-route HTTP route mapping in PATH=UPSTREAM form; repeatable
175 +--tcp Request a dedicated raw TCP port on the relay
176 +--udp Enable public UDP relay in addition to the default stream path
177 +--udp-addr Local UDP target; defaults to the primary target when --udp is enabled
178 +--metrics-addr Optional host:port for Prometheus /metrics
179 +```
180 +
181 +Custom relay and metadata example:
182 +
183 +```text
184 +portal expose localhost:8080 \
185 + --name myapp \
186 + --identity-path ~/.config/portal/myapp.identity.json \
187 + --relays https://portal.example.com \
188 + --discovery=false \
189 + --description "Service description" \
190 + --tags tag1,tag2 \
191 + --thumbnail https://example.com/thumb.png \
192 + --owner "Portal Operator"
193 ```
194
195 ### `portal list [flags]`
196
107 -- Prints the relay URLs that the CLI will use for the current invocation.
108 -- `--relays` adds explicit relay URLs, and `--default-relays=false` disables the public registry list for the current listing run.
109 -- Unlike `portal expose`, `portal list` does not run the relay discovery expansion loop. It only resolves the registry seed list plus explicit `--relays` values.
197 +Print the relay URLs that the CLI will use for the current invocation.
198 +
199 +```text
200 +portal list
201 +portal list --relays https://portal.example.com --default-relays=false
202 +```
203 +
204 +`portal list` resolves the registry seed list plus explicit relays. Unlike
205 +`portal expose`, it does not run the runtime relay discovery expansion loop.
206
207 ### `portal agent run [flags]`
208
113 -Runs Portal as a managed long-lived tunnel agent.
209 +Run Portal as a managed long-lived tunnel agent.
210 +
211 +```text
212 +portal agent run
213 +portal agent dashboard
214 +portal agent stop
215 +portal agent restart
216 +```
217 +
218 +The agent service owns multiple tunnel definitions from one config file. The
219 +local control API binds to loopback and is authenticated with a token stored in
220 +the agent state directory.
221
115 -- `portal agent run` reads the platform default config path, installs or updates the OS service, starts it in the background, and exits after the agent is ready.
116 -- `portal agent run --config config.toml --foreground` runs the agent in the current terminal and opens the dashboard when the terminal is interactive.
117 -- `portal agent dashboard` attaches to an already running agent.
118 -- The service process owns multiple tunnel definitions from one `config.toml`.
119 -- The local control API is bound to loopback and authenticated with a token stored in the agent state directory.
120 -- `portal agent dashboard` opens the mouse-capable local TUI for tunnel add/delete, per-tunnel relay add/delete/listing, and multi-hop route changes.
121 -- `portal agent stop` asks the local agent to shut down, then disables/stops the OS service so intentional shutdown is not immediately restarted.
122 -- `portal agent restart` stops the running agent if present, installs or updates the OS service from the existing config, and starts it again.
123 -- `portal agent run`, `stop`, and `restart` require an existing config file. `portal agent dashboard` can attach with only the default state directory or an explicit `--state-dir`.
222 +Useful commands:
223 +
224 +- `portal agent run` reads the platform default config path, installs or updates
225 + the OS service, starts it in the background, and exits after the agent is ready.
226 +- `portal agent run --config config.toml --foreground` runs the agent in the
227 + current terminal and opens the dashboard when the terminal is interactive.
228 +- `portal agent dashboard` attaches to a running agent and opens the local TUI.
229 +- `portal agent stop` asks the local agent to shut down, then disables or stops
230 + the OS service so intentional shutdown is not immediately restarted.
231 +- `portal agent restart` stops the running agent if present, installs or updates
232 + the service from the existing config, and starts it again.
233 +
234 +`portal agent run`, `stop`, and `restart` require an existing config file.
235 +`portal agent dashboard` can attach with only the default state directory or an
236 +explicit `--state-dir`.
237
238 Default paths:
239
@@ -147,45 +260,68 @@ relays = ["https://portal.example.com"]
260 discovery = false
261 description = "Managed web tunnel"
262 tags = ["web"]
150 -```
263
152 -Runtime controls:
153 -
154 -```text
155 -portal agent run
156 -portal agent dashboard
157 -portal agent stop
158 -portal agent restart
264 +[[tunnels]]
265 +id = "frontend"
266 +name = "myapp-http"
267 +relays = ["https://portal.example.com"]
268 +discovery = false
269 +http_routes = [
270 + { prefix = "/api", upstream = "http://127.0.0.1:3001" },
271 + { prefix = "/", upstream = "http://127.0.0.1:5173" },
272 +]
273 ```
274
161 -Legacy execution compatibility has been removed:
162 -
163 -- Use `portal expose ...` explicitly; bare `portal [flags]` is no longer accepted.
164 -- Runtime `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variable fallbacks are no longer used.
165 -- Pass either the local target as the positional `<target>` argument or repeat `--http-route` for routed HTTP mode.
166 -
275 ## Install Behavior
276
277 - `install.sh` installs the downloaded binary as `portal`.
170 -- `install.ps1` installs `portal.exe` for the current Windows user and updates the user `PATH`.
278 +- `install.ps1` installs `portal.exe` for the current Windows user and updates
279 + the user `PATH`.
280 - The installer does not write a config file.
172 -- `portal expose 3000` still works after install because discovery is enabled by default.
173 -- To target only a specific relay, use `--relays https://portal.example.com --discovery=false`.
281 +- `portal expose 3000` works after install because discovery is enabled by
282 + default.
283 +- To target only a specific relay, use
284 + `--relays https://portal.example.com --discovery=false`.
285
175 -## Notes
286 +## Operational Notes
287
177 -- `portal expose` loads or creates the signing identity at `identity.json` by default. Reusing the same `--identity-path` keeps the same address across runs.
288 +- `portal expose` loads or creates the signing identity at `identity.json` by
289 + default.
290 +- Reusing the same `--identity-path` keeps the same tunnel address across runs.
291 - Use different `--identity-path` values when you want separate local identities.
179 -- Multiple relay URLs are registered independently. Each relay gets its own lease registration and public URLs.
180 -- Relay publishes each service at `<name>.<portal root host>`.
181 -- The tunnel consumes one aggregate SDK listener, so the CLI no longer manages per-relay listener loops itself.
182 -- Relay startup and reconnect failures are retried independently in the background. A relay that is down does not stop healthy relays from continuing to serve traffic.
183 -- The tunnel starts once relay URLs pass local validation. Remote compatibility checks, lease registration, and reconnects continue in the background until each relay becomes ready.
184 -- With discovery enabled, the tunnel uses the public registry as discovery seed input and can expand through relay discovery. Explicit `--relays` values are always included separately from the auto-selected relay pool. With `--discovery=false`, only the explicit relay URLs are used. Published public URLs appear only for relays that have registered successfully.
185 -- Explicit `--relays` listeners retry indefinitely with `RetryCount=0`. Auto-selected discovery relays are created with `RetryCount=10` and are dropped from the active set after that budget is exhausted.
186 -- Retry count limits retries when positive. `RetryCount=0` retries indefinitely.
187 -- Tenant TLS is provisioned automatically through the relay keyless signer. The SDK fetches the relay certificate chain and uses `/v1/sign` for remote signing.
188 -- `portal expose` enables MITM strict enforcement by default. Use `--ban-mitm=false` to keep warning-only behavior when the TLS self-probe suspects relay termination.
189 -- When the local service is unreachable, the tunnel returns an HTTP 503 page.
190 -- `--tcp` allocates a dedicated TCP port within the relay's configured `MIN_PORT-MAX_PORT` range. The relay bridges raw TCP connections to the local target without TLS. Requires `TCP_ENABLED=true`, a valid `MIN_PORT/MAX_PORT` range, and TCP port enabled in the admin panel.
191 -- `--http-route` mode is HTTP-only and cannot be combined with `--udp`.
292 +- Relay publishes each default stream service at `<name>.<portal-root-host>`.
293 +- Multiple relay URLs are registered independently. Each relay gets its own
294 + lease registration and public URL.
295 +- The tunnel consumes one aggregate SDK listener; the CLI does not run per-relay
296 + listener loops itself.
297 +- Relay startup and reconnect failures are retried independently in the
298 + background. One unhealthy relay does not stop healthy relays from serving.
299 +- The tunnel starts once relay URLs pass local validation. Remote compatibility
300 + checks, lease registration, and reconnects continue in the background until
301 + each relay becomes ready.
302 +- With discovery enabled, the tunnel uses the public registry as discovery seed
303 + input and can expand through relay discovery.
304 +- Explicit `--relays` values are always included separately from the
305 + auto-selected relay pool.
306 +- With `--discovery=false`, only explicit relay URLs are used.
307 +- Published public URLs appear only for relays that register successfully.
308 +- Explicit relay listeners retry indefinitely. Auto-selected discovery relays
309 + are dropped from the active set after their retry budget is exhausted.
310 +- Tenant TLS is provisioned automatically through the relay keyless signer. The
311 + SDK fetches the relay certificate chain and uses `/v1/sign` for remote signing.
312 +- `portal expose` enables MITM strict enforcement by default. Use
313 + `--ban-mitm=false` for warning-only behavior.
314 +- When the local stream target is unreachable, the tunnel returns an HTTP 503
315 + page to browser-style clients.
316 +- `--tcp` requires the relay to have TCP port transport enabled and a valid
317 + `MIN_PORT`/`MAX_PORT` range.
318 +- `--udp` requires the relay to have UDP transport enabled and a valid
319 + `MIN_PORT`/`MAX_PORT` range.
320 +
321 +## Compatibility Notes
322 +
323 +- Use `portal expose ...` explicitly; bare `portal [flags]` is not accepted.
324 +- Runtime `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variable fallbacks
325 + are not used.
326 +- Pass either a positional local target or repeat `--http-route`; do not use
327 + both in the same tunnel.
docs/src/lib/components/landing/CoreFeaturesGrid.svelte
+17 -17
@@ -1,40 +1,40 @@
1 <script lang="ts">
2 const features = [
3 {
4 - eyebrow: 'HTTPS',
5 - title: 'Public HTTPS for localhost',
4 + eyebrow: 'Relay',
5 + title: 'Trustless relay transport',
6 description:
7 - 'Publish local apps through TCP passthrough without opening inbound ports.'
7 + 'Relays own routing and transport, while the tunnel process owns endpoint behavior.'
8 },
9 {
10 eyebrow: 'TLS',
11 - title: 'End-to-end TLS on your side',
11 + title: 'Client-side tenant TLS',
12 description:
13 - 'Tenant TLS terminates locally with MITM detection, so relays cannot access plaintext.'
13 + 'Tenant TLS terminates locally with MITM detection, so relays cannot read plaintext.'
14 },
15 {
16 - eyebrow: 'Setup',
17 - title: 'One-command setup',
16 + eyebrow: 'HTTP',
17 + title: 'Tunnel-controlled HTTP routing',
18 description:
19 - 'Start relays and tunnels with minimal setup and a short copy-paste path.'
19 + 'Mount frontend, API, and other local HTTP services behind one public URL.'
20 },
21 {
22 - eyebrow: 'Relay',
23 - title: 'Self-hosted relays and pools',
22 + eyebrow: 'Ports',
23 + title: 'Raw TCP and UDP leases',
24 description:
25 - 'Connect to public relays, use discovered relays as a pool with failover, or run your own.'
25 + 'Expose game servers, custom TCP protocols, and datagram services through relay ports.'
26 },
27 {
28 - eyebrow: 'Transport',
29 - title: 'Raw TCP with optional UDP',
28 + eyebrow: 'Mesh',
29 + title: 'Relay pools and multi-hop',
30 description:
31 - 'Carry web traffic and arbitrary protocols without SSH or WebSocket overlays.'
31 + 'Use discovered relays for failover or chain relays to split path visibility.'
32 },
33 {
34 - eyebrow: 'Identity',
35 - title: 'SIWE ownership with ENS support',
34 + eyebrow: 'Ops',
35 + title: 'Self-hosted or public relays',
36 description:
37 - 'Authenticate ownership with SIWE and keep identity portable with ENS-based naming support.'
37 + 'Run your own relay, attach explicit relays, or use the public relay registry.'
38 }
39 ] as const;
40 </script>
docs/src/routes/+page.svelte
+2 -2
@@ -6,10 +6,10 @@
6 </script>
7
8 <svelte:head>
9 - <title>Portal — Expose Local Apps to the Public Internet</title>
9 + <title>Portal - Expose Local Apps to the Public Internet</title>
10 <meta
11 name="description"
12 - content="Portal lets you expose local services to the public internet through secure, trustless relay tunnels with end-to-end TLS encryption."
12 + content="Portal exposes local services through trustless relay transport with client-side tenant TLS, routed HTTP, raw TCP, UDP, relay pools, and multi-hop routing."
13 />
14 </svelte:head>
15
docs/src/routes/cli-reference/+page.md
+121 -100
@@ -1,13 +1,15 @@
1 ---
2 title: CLI Reference
3 -description: Complete reference for the Portal CLI commands, flags, and usage examples.
3 +description: Complete reference for Portal CLI commands, flags, and usage examples.
4 ---
5
6 # CLI Reference
7
8 -The `portal` CLI exposes local services through Portal relay servers. This page documents all commands, flags, and usage patterns.
8 +The `portal` CLI exposes local services through Portal relay servers. The relay
9 +provides transport and routing. The tunnel process decides whether a connection
10 +is handled as the default HTTPS stream, routed HTTP, raw TCP, or UDP.
11
10 -## Installation
12 +## Install
13
14 ### macOS / Linux
15
@@ -15,14 +17,14 @@ The `portal` CLI exposes local services through Portal relay servers. This page
17 curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
18 ```
19
18 -### Windows (PowerShell)
20 +### Windows PowerShell
21
22 ```powershell
23 $ProgressPreference = 'SilentlyContinue'
24 irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
25 ```
26
25 -### From a relay
27 +### From A Relay
28
29 If your relay publishes its own installer:
30
@@ -30,76 +32,104 @@ If your relay publishes its own installer:
32 curl -sSL https://portal.example.com/install.sh | bash
33 ```
34
33 -The installer downloads the `portal` binary and adds it to your PATH. No configuration file is written. Already installed? Run `portal update` to get the latest version.
35 +The installer writes the `portal` binary only. It does not write a config file.
36
35 -## Commands
37 +## Command Overview
38
37 -### `portal expose`
39 +| Command | Purpose |
40 +|---------|---------|
41 +| `portal expose` | Expose one local service or one routed HTTP bundle |
42 +| `portal list` | Print relay URLs resolved for this invocation |
43 +| `portal agent` | Run a durable local multi-tunnel agent |
44 +| `portal update` | Replace the CLI with the latest release |
45 +| `portal version` | Print the current version |
46
39 -Expose a local service to the internet.
47 +## `portal expose`
48 +
49 +Expose a local service:
50
51 ```bash
52 portal expose [flags] <target>
53 ```
54
45 -**Target formats:**
55 +Or run routed HTTP mode:
56 +
57 +```bash
58 +portal expose [flags] --http-route PATH=UPSTREAM [--http-route PATH=UPSTREAM]
59 +```
60 +
61 +### Target Formats
62
63 | Format | Example | Resolves to |
64 |--------|---------|-------------|
65 | Bare port | `3000` | `127.0.0.1:3000` |
50 -| Host:port | `localhost:8080` | `localhost:8080` |
51 -| URL | `http://127.0.0.1:3000` | `127.0.0.1:3000` |
66 +| Host and port | `localhost:8080` | `localhost:8080` |
67 +| URL host | `http://127.0.0.1:3000` | `127.0.0.1:3000` |
68
53 -Instead of a positional target, you can use `--http-route` for multi-service routing.
69 +URL inputs are accepted for address parsing. Paths, queries, and fragments are
70 +not supported.
71
55 -**Flags:**
72 +### Mode Selection
73 +
74 +| Mode | Example | Notes |
75 +|------|---------|-------|
76 +| Default HTTPS stream | `portal expose 3000` | Relay routes by SNI; tunnel process terminates tenant TLS |
77 +| Routed HTTP | `portal expose --http-route /api=3001 --http-route /=5173` | Tunnel process runs the HTTP reverse proxy |
78 +| Dedicated raw TCP | `portal expose localhost:25565 --tcp` | Relay allocates a public TCP port |
79 +| UDP relay | `portal expose 8080 --udp --udp-addr 19132` | Relay allocates a public UDP port |
80 +
81 +### Flags
82
83 | Flag | Type | Default | Description |
84 |------|------|---------|-------------|
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 -| `--multi-hop` | string | | Ordered multi-hop relay API URLs, comma-separated |
62 -| `--multi-hop-depth` | int | `0` | Automatically select one multi-hop route with this hop count; 0 or 1 disables multi-hop |
85 +| `--relays` | string | registry | Additional relay API URLs, comma-separated |
86 +| `--discovery` | bool | `true` | Include registry relays and relay discovery expansion |
87 | `--max-active-relays` | int | `3` | Maximum auto-selected relays to keep connected; explicit relays are always included |
88 +| `--multi-hop` | string | | Ordered multi-hop relay API URLs, comma-separated |
89 +| `--multi-hop-depth` | int | `0` | Automatically select one multi-hop route with this hop count; `0` or `1` disables multi-hop |
90 | `--ban-mitm` | bool | `true` | Ban relay when the MITM self-probe detects TLS termination |
65 -| `--identity-path` | string | `./identity.json` | Identity JSON file path; created automatically when missing |
91 +| `--identity-path` | string | `identity.json` | Identity JSON file path; created automatically when missing |
92 | `--identity-json` | string | | Identity JSON payload; overrides `--identity-path` contents and is persisted there when both are set |
67 -| `--name` | string | _(auto)_ | Public hostname prefix (single DNS label); auto-generated when omitted |
93 +| `--name` | string | auto | Public hostname prefix, one DNS label |
94 | `--description` | string | | Service description metadata |
69 -| `--tags` | string | | Service tags metadata (comma-separated) |
95 +| `--tags` | string | | Service tags metadata, comma-separated |
96 | `--thumbnail` | string | | Service thumbnail URL metadata |
97 | `--owner` | string | | Service owner metadata |
98 | `--hide` | bool | `false` | Hide service from relay listing screens |
73 -| `--tcp` | bool | `false` | Request a dedicated TCP port for raw TCP services (no TLS) |
99 +| `--http-route` | string | | HTTP route mapping in `PATH=UPSTREAM` form; repeatable |
100 +| `--tcp` | bool | `false` | Request a dedicated raw TCP port on the relay |
101 | `--udp` | bool | `false` | Enable public UDP relay in addition to the default stream path |
75 -| `--udp-addr` | string | | Local UDP target address; defaults to the primary target when `--udp` is enabled |
76 -| `--http-route` | string | | HTTP route mapping in `PATH=UPSTREAM` form; repeat for multiple routes |
102 +| `--udp-addr` | string | | Local UDP target; defaults to the primary target when `--udp` is enabled |
103 +| `--metrics-addr` | string | | Optional `host:port` for Prometheus `/metrics` |
104
78 -**Examples:**
105 +### Constraints
106
80 -Basic usage:
107 +- `<target>` cannot be combined with `--http-route`.
108 +- `--http-route` cannot be combined with `--udp`.
109 +- Explicit `--multi-hop` cannot be combined with automatic `--multi-hop-depth`.
110 +- Multi-hop currently supports only the default SNI TLS stream transport.
111 +- `--tcp` and `--udp` require matching transport support on the relay.
112 +
113 +### Examples
114 +
115 +Expose a local web app:
116
117 ```bash
118 portal expose 3000
119 ```
120
86 -With custom name and relay:
121 +Use a custom name and relay:
122
123 ```bash
124 portal expose localhost:8080 \
125 --name myapp \
126 --relays https://portal.example.com \
127 + --discovery=false \
128 --description "My web application" \
129 --tags webapp,demo
130 ```
131
96 -TCP port routing (Minecraft server):
97 -
98 -```bash
99 -portal expose localhost:25565 --name minecraft --tcp
100 -```
101 -
102 -Multi-service HTTP routing:
132 +Run routed HTTP mode:
133
134 ```bash
135 portal expose --name myapp \
@@ -107,52 +137,58 @@ portal expose --name myapp \
137 --http-route /=http://127.0.0.1:5173
138 ```
139
110 -Route matching is longest-prefix-first. `/api` matches `/api/*` and strips the `/api` prefix before proxying to the upstream. Routed HTTP mode automatically forwards `X-Forwarded-*` headers, rewrites upstream `Location` redirects, and remaps cookie paths.
140 +Route matching is longest-prefix-first. `/api` matches `/api/*` and strips the
141 +`/api` prefix before proxying to the upstream.
142 +
143 +Expose a Minecraft server:
144
112 -Disable relay discovery:
145 +```bash
146 +portal expose localhost:25565 --name minecraft --tcp
147 +```
148 +
149 +Enable UDP alongside the default stream target:
150
151 ```bash
115 -portal expose 3000 --relays https://portal.example.com --discovery=false
152 +portal expose localhost:8080 --udp --udp-addr localhost:19132 --name game
153 ```
154
118 -Explicit multi-hop route:
155 +Use an explicit multi-hop route:
156
157 ```bash
121 -portal expose 3000 --multi-hop https://entry.example.com,https://transit.example.com,https://exit.example.com
158 +portal expose 3000 --multi-hop https://entry.example.com,https://exit.example.com
159 ```
160
124 -Automatic multi-hop route:
161 +Ask Portal to select one three-hop route:
162
163 ```bash
164 portal expose 3000 --multi-hop-depth 3
165 ```
166
130 -Warning-only MITM mode:
167 +Keep MITM probe failures warning-only:
168
169 ```bash
170 portal expose 3000 --ban-mitm=false
171 ```
172
136 -### `portal list`
173 +## `portal list`
174
138 -Print the relay URLs that the CLI will use.
175 +Print relay URLs resolved for the current invocation:
176
177 ```bash
178 portal list [flags]
179 ```
180
144 -**Flags:**
145 -
181 | Flag | Type | Default | Description |
182 |------|------|---------|-------------|
148 -| `--relays` | string | _(registry)_ | Additional relay URLs |
183 +| `--relays` | string | registry | Additional relay URLs |
184 | `--default-relays` | bool | `true` | Include public registry relays |
185
151 -Unlike `portal expose`, `portal list` does not run the relay discovery expansion loop. It only resolves the registry seed list plus explicit `--relays` values.
186 +`portal list` does not run the runtime relay discovery expansion loop. It only
187 +resolves the registry seed list plus explicit relay URLs.
188
153 -### `portal agent`
189 +## `portal agent`
190
155 -Run a durable local agent that owns multiple tunnels from one config file.
191 +Run a durable local agent that owns multiple tunnels from one config file:
192
193 ```bash
194 portal agent run
@@ -161,77 +197,62 @@ portal agent stop
197 portal agent restart
198 ```
199
164 -`portal agent run` reads the platform default `config.toml`, installs or updates the OS-managed service, starts it in the background, and exits after the agent is ready. Use `--foreground` for local debugging without service registration.
165 -Use `portal agent dashboard` to attach to an already running managed agent. It uses `agent.state_dir` when a config file exists and otherwise falls back to the platform default state directory. With `--foreground` in an interactive terminal, the dashboard attaches in the same process.
166 -
167 -**Subcommands:**
168 -
200 | Command | Description |
201 |---------|-------------|
171 -| `portal agent run` | Install/update and start the managed agent service |
202 +| `portal agent run` | Install or update and start the managed agent service |
203 | `portal agent run --config config.toml --foreground` | Run the agent in the current terminal |
173 -| `portal agent dashboard` | Open the mouse-capable local TUI for tunnels, relay attach/detach, relay lists, and multi-hop route changes |
174 -| `portal agent stop` | Gracefully stop the agent and disable/stop the OS service |
175 -| `portal agent restart` | Stop the current agent if present, install/update the service, and start it again |
204 +| `portal agent dashboard` | Open the local TUI for tunnels, relays, and multi-hop routes |
205 +| `portal agent stop` | Gracefully stop the agent and disable or stop the OS service |
206 +| `portal agent restart` | Stop the current agent if present, install or update the service, and start it again |
207
177 -The local control API binds only to loopback and uses a token in the agent state directory. See [Configuration Reference](/configuration#configtoml) for the `config.toml` format.
208 +The local control API binds only to loopback and uses a token in the agent state
209 +directory. See [Configuration Reference](/configuration#configtoml) for the
210 +`config.toml` format.
211
179 -### `portal update`
212 +## `portal update`
213
181 -Update the CLI binary to the latest release.
214 +Update the CLI binary:
215
216 ```bash
217 portal update
218 ```
219
187 -The update flow:
188 -
189 -1. Checks the latest version by resolving the GitHub releases redirect URL
190 -2. Compares with the currently installed version
191 -3. If a newer version exists: downloads the binary, verifies its SHA256 checksum, and replaces the current executable
192 -4. If already up to date: prints a message and exits
193 -
194 -No flags. Works on macOS, Linux, and Windows. Requires write access to the directory containing the `portal` binary.
195 -
196 -**Examples:**
197 -
198 -```bash
199 -# Update to the latest version
200 -portal update
201 -# Already up to date (v2.1.5).
202 -
203 -# Or when a new version is available:
204 -portal update
205 -# Updating v2.1.5 → v2.2.0 ...
206 -# Updated v2.1.5 → v2.2.0
207 -```
208 -
209 -### `portal version`
220 +The updater resolves the latest GitHub release, compares it with the installed
221 +version, downloads the matching asset, verifies its SHA256 checksum, and
222 +replaces the current executable.
223
211 -Print the currently installed version.
224 +## `portal version`
225
226 ```bash
227 portal version
228 ```
229
217 -No flags. Outputs the version string (e.g., `v2.1.5`) and exits.
230 +Prints the installed version string and exits.
231
232 ## Behavior Notes
233
221 -- **Update notifications** - `portal expose` and `portal list` check for new releases in the background. The check runs at most once every 24 hours (cached locally) and never blocks command execution. When a newer version is found, a hint is printed to stderr after the command output.
222 -- **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.
223 -- **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.
224 -- **Retry semantics** - Relay startup and reconnect failures are retried in the background. The tunnel starts as soon as relay URLs pass local validation.
225 -- **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.
226 -- **MITM enforcement** - Enabled by default. The TLS self-probe runs asynchronously after real connections begin, with a 30-second cooldown between probes.
227 -- **503 on unreachable local service** - When the local target is unreachable, the tunnel returns an HTTP 503 page to the client.
228 -- **HTTP route mode** - Cannot be combined with `--udp`. Routes are HTTP-only.
229 -- **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.
230 -- **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.
231 -- **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.
234 +- `portal expose` and `portal list` check for new releases in the background.
235 +- `portal expose` loads or creates a signing identity at `identity.json` or
236 + `--identity-path`.
237 +- Multiple relay URLs are registered independently. A failed relay does not stop
238 + healthy relays from serving.
239 +- With discovery enabled, the tunnel consumes relay `/discovery` results and
240 + reconciles its relay pool.
241 +- MITM enforcement is enabled by default for the default stream path.
242 +- When the local stream target is unreachable, the tunnel returns an HTTP 503
243 + page to browser-style clients.
244 +- Routed HTTP mode is HTTP-only and runs inside the tunnel process.
245 +- `--tcp` requires relay TCP port transport, a valid `MIN_PORT`/`MAX_PORT`
246 + range, and TCP port transport enabled in the admin panel.
247 +- `--udp` requires relay UDP transport, a valid `MIN_PORT`/`MAX_PORT` range, UDP
248 + enabled in the admin panel, and `SNI_PORT/udp` reachable for the QUIC backhaul.
249 +- Bare `portal [flags]` is not accepted; use `portal expose` explicitly.
250 +- Runtime `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variable fallbacks
251 + are not used.
252
253 ## Next Steps
254
235 -- **[Getting Started](/getting-started)** - Quick tutorial for your first tunnel
236 -- **[Concepts](/concepts)** - How Portal's encryption and relay model works
237 -- **[Deployment](/deployment)** - Run your own relay server
255 +- [Getting Started](/getting-started): run your first tunnel
256 +- [Concepts](/concepts): understand the relay and transport model
257 +- [TCP and UDP Tunneling](/tcp-udp-tunneling): raw TCP and UDP setup
258 +- [Deployment](/deployment): run your own relay server
docs/src/routes/concepts/+page.md
+118 -73
@@ -1,129 +1,174 @@
1 ---
2 title: Concepts
3 -description: Understand how Portal provides trustless, end-to-end encrypted tunnels.
3 +description: Understand Portal's relay model, transport modes, and end-to-end TLS design.
4 ---
5
6 # Concepts
7
8 -Portal publishes local services on public HTTPS URLs through relay servers. Unlike most tunnel solutions, **the relay never sees your plaintext traffic**. This page explains how.
8 +Portal publishes local services through relay servers. The important design
9 +choice is that the relay is a transport and routing component, not the owner of
10 +your application traffic.
11
10 -## What is Portal?
12 +## Relay And Tunnel Responsibilities
13
12 -Portal is a reverse tunnel that exposes your local applications to the internet. It differs from alternatives like ngrok or Cloudflare Tunnel in one key way: **relays are trustless**. You don't need to trust the relay operator because they can't read your traffic.
14 +The relay owns:
15
14 -This means you can:
15 -- Use any public relay without security concerns
16 -- Run your own relay for full control
17 -- Switch between relays freely — your security model doesn't depend on the relay operator
16 +- lease registration and renewal
17 +- public hostname and port routing
18 +- SNI route lookup for the default stream path
19 +- relay discovery and relay-to-relay forwarding
20 +- admin policy such as approval, bans, and transport limits
21
19 -## The Trustless Relay Model
22 +The tunnel process owns:
23
21 -In a traditional tunnel, the relay terminates TLS and has full access to your plaintext:
24 +- tenant TLS termination for the default HTTPS stream path
25 +- local target proxying
26 +- routed HTTP reverse proxy behavior
27 +- UDP target forwarding
28 +- identity keys and lease signing
29 +- MITM self-probe validation
30
23 -```text
24 -Client → [TLS] → Relay (decrypts, re-encrypts) → [TLS] → Your App
25 - ↑ Relay sees plaintext
31 +This split is why Portal can use public relays without giving relay operators
32 +tenant plaintext.
33 +
34 +## Default Stream Path
35 +
36 +The default command is:
37 +
38 +```bash
39 +portal expose 3000
40 ```
41
28 -Portal works differently. The relay only routes encrypted traffic:
42 +The public URL is HTTPS, but the relay does not terminate tenant TLS.
43
44 ```text
31 -Client → [TLS] ────────────────────────────── → Your App
32 - ↑ Relay forwards raw bytes
33 - (cannot decrypt)
45 +Browser
46 + -> Relay :443
47 + -> reverse session
48 + -> tunnel process TLS server
49 + -> 127.0.0.1:3000
50 ```
51
36 -The relay reads only the TLS ClientHello (the initial unencrypted handshake message) to extract the SNI hostname for routing. After that, it bridges raw encrypted bytes between the client and your local TLS server.
52 +Flow:
53
38 -## End-to-End TLS
54 +1. A browser connects to the relay and sends a TLS ClientHello.
55 +2. The relay reads the SNI hostname and finds the matching lease.
56 +3. The relay claims a waiting reverse session from the tunnel process.
57 +4. The tunnel process performs the tenant TLS handshake locally.
58 +5. The relay may sign handshake digests through `/v1/sign`, but it does not
59 + receive tenant TLS session keys.
60 +6. After the handshake, the relay forwards encrypted bytes.
61
40 -Here's the detailed flow:
62 +## Routed HTTP Mode
63
42 -1. **SNI routing** — A client connects to the relay on port 443. The relay peeks at the TLS ClientHello to read the SNI hostname (e.g., `myapp.relay.example.com`).
64 +Routed HTTP mode mounts one or more local HTTP upstreams behind one public URL:
65
44 -2. **Reverse session claim** — The relay looks up which tunnel owns that hostname and claims one of its waiting reverse sessions.
45 -
46 -3. **Local TLS termination** — Your Portal client receives the connection and performs the TLS handshake locally. Session keys are derived on your machine — the relay never receives them.
66 +```bash
67 +portal expose --name myapp \
68 + --http-route /api=http://127.0.0.1:3001 \
69 + --http-route /=http://127.0.0.1:5173
70 +```
71
48 -4. **Keyless signing** — For relay-hosted domains, Portal uses the relay's `/v1/sign` endpoint to get certificate signatures. The relay acts as a "keyless" signing oracle — it signs handshake digests but never receives the resulting session keys.
72 +This is not relay-side HTTP proxying. The relay still transports the connection.
73 +The tunnel process receives the stream, parses HTTP, and runs the reverse proxy.
74
50 -5. **Encrypted data flow** — After the handshake, all traffic flows as encrypted bytes through the relay. The relay continues forwarding without needing plaintext.
75 +Routed HTTP mode can:
76
52 -**Result:** TLS terminates on your side. The relay provides routing and certificate signing only.
77 +- match routes longest-prefix-first
78 +- strip the mounted prefix before proxying
79 +- forward `X-Forwarded-*`
80 +- rewrite matching upstream `Location` redirects
81 +- strip loopback cookie domains
82 +- remap cookie paths to route prefixes
83
54 -## MITM Detection
84 +Because HTTP is parsed in the tunnel process, this is the right place for
85 +cooperative HTTP policy such as response headers. It is not a relay-enforced
86 +policy boundary.
87
56 -How do you know the relay is actually forwarding encrypted bytes and not terminating TLS itself? Portal includes a built-in self-probe mechanism:
88 +## Dedicated Raw TCP
89
58 -1. After a real connection is established, Portal opens a separate TLS connection to its own public URL
59 -2. The probe exports TLS keying material on both the client side and the server side
60 -3. If the exported values match, the connection was passed through without relay-side termination
61 -4. A mismatch indicates the relay may be terminating TLS (suspected MITM)
90 +Use raw TCP when clients need a public TCP port instead of a public HTTPS
91 +hostname:
92
63 -By default, `portal expose` enables strict enforcement — if the probe detects TLS termination, the relay is banned. You can use `--ban-mitm=false` for warning-only mode.
93 +```bash
94 +portal expose localhost:25565 --name minecraft --tcp
95 +```
96
65 -> **Note:** The self-probe is a detect-only signal. It raises the cost of relay-side TLS termination but cannot prove passthrough for every user connection.
97 +The relay allocates a port from its configured range and bridges raw TCP to the
98 +tunnel process. This is useful for Minecraft, game servers, and custom TCP
99 +protocols. The raw TCP path does not add TLS; use protocol-level encryption when
100 +needed.
101
67 -## Transport Models
102 +## UDP Relay
103
69 -Portal supports three transport modes:
104 +Use UDP mode for datagram protocols:
105
71 -### TLS Passthrough (default)
106 +```bash
107 +portal expose localhost:8080 --udp --udp-addr localhost:19132
108 +```
109
73 -Standard HTTPS tunneling. Client connects to port 443, relay routes by SNI, your app terminates TLS locally.
110 +The relay allocates a UDP port and carries datagrams over the tunnel backhaul to
111 +the local UDP target. The positional target is still used for stream traffic;
112 +`--udp-addr` selects the local UDP service.
113
75 -```text
76 -Client → Relay :443 (SNI routing) → Reverse Session → Your App (TLS termination)
77 -```
114 +## Multi-Relay And Multi-Hop
115
79 -### Raw TCP Port Routing
116 +With discovery enabled, Portal starts from the public registry plus explicit
117 +relays, then expands through relay discovery. Explicit relays are always kept
118 +connected separately from the auto-selected relay pool.
119
81 -For non-TLS services like Minecraft servers or database connections. The relay allocates a dedicated TCP port and bridges raw TCP without any TLS wrapping.
120 +Use a fixed ordered route:
121
83 -```text
84 -Client → Relay :40001 (dedicated TCP port) → Reverse Session → Your App (raw TCP)
122 +```bash
123 +portal expose 3000 --multi-hop https://entry.example.com,https://exit.example.com
124 ```
125
87 -Enable with `portal expose --tcp`. Requires the relay to have TCP port transport enabled.
126 +Or ask Portal to choose one route of a given depth:
127
89 -### UDP via QUIC
128 +```bash
129 +portal expose 3000 --multi-hop-depth 3
130 +```
131
91 -For UDP services. The relay allocates a UDP port and uses an internal QUIC tunnel to carry datagrams between the client and your app.
132 +Multi-hop currently applies to the default SNI TLS stream transport. It is not
133 +combined with UDP or dedicated raw TCP port mode.
134
93 -```text
94 -UDP Client → Relay :40002 (UDP) → QUIC Tunnel → Your App (raw UDP)
95 -```
135 +## MITM Self-Probe
136
97 -UDP and TCP port allocations are independent — the same numeric range can serve both.
137 +Portal runs a TLS passthrough self-probe after real stream traffic starts:
138
99 -## Identity and Authentication
139 +1. The tunnel opens a client connection to its own public URL.
140 +2. The tunnel also receives that connection as the tenant TLS server.
141 +3. Both controlled ends export TLS keying material.
142 +4. Matching exporter values indicate passthrough for that sampled connection.
143 +5. A mismatch is treated as suspected relay-side TLS termination.
144
101 -Portal uses **SIWE (Sign-In with Ethereum)** for identity:
145 +By default, `portal expose` bans a relay when the self-probe detects
146 +termination. Use `--ban-mitm=false` for warning-only behavior.
147
103 -- On first run, Portal generates a secp256k1 key pair stored as `identity.json`
104 -- Registration uses a challenge/response flow — you sign a message proving key ownership
105 -- No accounts, no API keys, no email required
106 -- Reuse the same `--identity-path` across runs to maintain your identity
148 +The probe is a detection signal, not a mathematical proof for every future
149 +connection. It raises the cost of relay-side termination while preserving the
150 +transport model.
151
108 -The relay issues a lease-scoped JWT access token after registration, used for all subsequent operations (renew, connect, unregister).
152 +## Identity And Lease Authentication
153
110 -## Relay Discovery
154 +On first run, Portal creates a local secp256k1 identity at `identity.json` unless
155 +you pass another `--identity-path`.
156
112 -Portal maintains a public relay registry at:
157 +Lease registration uses challenge signing. After registration, the relay issues
158 +a lease-scoped access token used for renew, unregister, reverse connect, and
159 +datagram authentication.
160
114 -```text
115 -https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json
116 -```
161 +Reusing the same identity path keeps the same tunnel identity across runs.
162
118 -When you run `portal expose`, the CLI:
119 -1. Loads the registry seed list
120 -2. Adds any explicit `--relays` URLs
121 -3. Optionally discovers additional relays through relay-to-relay synchronization
163 +## Domain Boundary
164
123 -Use `--discovery=false` to limit connections to only explicitly specified relays.
165 +The default stream path prevents the relay from safely injecting `robots.txt`,
166 +`noindex`, or arbitrary HTTP headers into user responses. That is a feature of
167 +the trust model, but it also means public multi-tenant relays should use a
168 +separate wildcard tunnel domain instead of a brand or docs domain.
169
170 ## Next Steps
171
127 -- **[CLI Reference](/cli-reference)** — Full command and flag documentation
128 -- **[Architecture](/architecture)** — Deep dive into system design and protocol details
129 -- **[Getting Started](/getting-started)** — Quick install and first tunnel setup
172 +- [Getting Started](/getting-started): run your first tunnel
173 +- [CLI Reference](/cli-reference): command and flag details
174 +- [Architecture](/architecture): protocol-level design notes
docs/src/routes/getting-started/+page.md
+70 -51
@@ -5,15 +5,16 @@ description: Install Portal and expose your first local service to the internet.
5
6 # Getting Started
7
8 -This guide walks you through installing Portal and exposing your first local service to the public internet in under a minute.
8 +This guide installs the `portal` CLI and exposes a local service through a
9 +public relay.
10
11 ## Prerequisites
12
12 -- A local application running on any port (e.g., a web server on port 3000)
13 - macOS, Linux, or Windows
14 - Internet connectivity
15 +- A local service to expose, such as a web app on port `3000`
16
16 -## Install the CLI
17 +## Install The CLI
18
19 ### macOS / Linux
20
@@ -21,100 +22,118 @@ This guide walks you through installing Portal and exposing your first local ser
22 curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
23 ```
24
24 -### Windows (PowerShell)
25 +### Windows PowerShell
26
27 ```powershell
28 $ProgressPreference = 'SilentlyContinue'
29 irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
30 ```
31
31 -The installer downloads the `portal` binary and places it in your PATH. No configuration file is created — Portal works out of the box.
32 -
33 -## Updating the CLI
34 -
35 -Already have Portal installed? Update to the latest version with a single command:
36 -
37 -```bash
38 -portal update
39 -```
40 -
41 -Portal checks for the latest GitHub release, downloads the new binary, verifies its SHA256 checksum, and replaces the current executable in place. If you're already on the latest version, it simply reports that no update is needed.
42 -
43 -You can check the currently installed version at any time:
44 -
45 -```bash
46 -portal version
47 -```
32 +The installer downloads the `portal` binary and adds it to your `PATH`. It does
33 +not create a config file. Portal works out of the box because relay discovery is
34 +enabled by default.
35
36 ## Expose Your First App
37
51 -Start a local application (or use any existing one), then run:
38 +Start your local app, then run:
39
40 ```bash
41 portal expose 3000
42 ```
43
57 -Replace `3000` with whatever port your application is running on. Portal accepts:
58 -- A bare port: `3000` (resolves to `127.0.0.1:3000`)
59 -- A host:port: `localhost:8080`
60 -- A URL: `http://127.0.0.1:3000`
44 +Portal accepts:
45 +
46 +| Input | Example | Meaning |
47 +|-------|---------|---------|
48 +| Bare port | `3000` | `127.0.0.1:3000` |
49 +| Host and port | `localhost:8080` | that exact local address |
50 +| URL host | `http://127.0.0.1:3000` | parsed as `127.0.0.1:3000` |
51
62 -Portal will output a public HTTPS URL like:
52 +Portal prints a public HTTPS URL:
53
54 ```text
55 https://your-name.relay.example.com
56 ```
57
68 -Open that URL in any browser — you're now accessing your local app through an encrypted tunnel.
58 +Open the URL in a browser. The relay routes the connection, but tenant TLS
59 +terminates in the tunnel process running on your machine.
60
70 -## What Just Happened?
61 +## What Happened
62
63 When you ran `portal expose`:
64
74 -1. **Identity created** — Portal generated a signing identity at `./identity.json` (reuse it across runs to keep the same address)
75 -2. **Relay discovery** — Portal found available public relays from the official registry
76 -3. **Lease registered** — Your service was registered with a relay using SIWE (Sign-In with Ethereum) authentication
77 -4. **Reverse session** — A persistent connection was established from your machine to the relay
78 -5. **TLS provisioned** — The relay provided certificate signing, but TLS terminates locally on your machine (end-to-end encryption)
65 +1. Portal loaded or created a local identity at `identity.json`.
66 +2. Portal selected relay URLs from the public registry and discovery.
67 +3. The tunnel process registered a lease with one or more relays.
68 +4. The tunnel process opened reverse sessions to those relays.
69 +5. A public HTTPS hostname was assigned.
70 +6. Incoming connections were routed by the relay and handled by your tunnel
71 + process.
72
80 -The relay never sees your plaintext traffic. See [Concepts](/concepts) to understand how this works.
73 +The relay provides routing and keyless certificate signing, but it does not
74 +receive tenant TLS session keys on the default stream path.
75
82 -## Customizing Your Tunnel
76 +## Choose The Right Mode
77
84 -### Set a custom hostname
78 +Most web apps use the default stream mode:
79
80 ```bash
81 portal expose 3000 --name myapp
82 ```
83
90 -Your app will be available at `myapp.relay.example.com`.
91 -
92 -### Use a specific relay
84 +Use routed HTTP mode when one public URL should mount multiple local HTTP
85 +services:
86
87 ```bash
95 -portal expose 3000 --relays https://portal.example.com --discovery=false
88 +portal expose --name myapp \
89 + --http-route /api=http://127.0.0.1:3001 \
90 + --http-route /=http://127.0.0.1:5173
91 ```
92
98 -### Expose a TCP service (e.g., Minecraft)
93 +Use dedicated raw TCP mode for non-HTTP servers such as Minecraft:
94
95 ```bash
96 portal expose localhost:25565 --name minecraft --tcp
97 ```
98
104 -This allocates a dedicated TCP port on the relay — no TLS wrapping, perfect for game servers.
99 +Use UDP mode for datagram services:
100
106 -### Multi-service HTTP routing
101 +```bash
102 +portal expose localhost:8080 --udp --udp-addr localhost:19132 --name game
103 +```
104 +
105 +## Use A Specific Relay
106
107 ```bash
109 -portal expose --name myapp \
110 - --http-route /api=http://127.0.0.1:3001 \
111 - --http-route /=http://127.0.0.1:5173
108 +portal expose 3000 --relays https://portal.example.com --discovery=false
109 +```
110 +
111 +`--discovery=false` limits the tunnel to the explicit relay URLs you supplied.
112 +
113 +## Keep A Stable Identity
114 +
115 +By default, Portal writes `identity.json` in the current working directory. Use a
116 +fixed path when you want stable identity across projects or restarts:
117 +
118 +```bash
119 +portal expose 3000 \
120 + --name myapp \
121 + --identity-path ~/.config/portal/myapp.identity.json
122 +```
123 +
124 +## Update The CLI
125 +
126 +```bash
127 +portal update
128 +portal version
129 ```
130
114 -Routes are matched longest-prefix-first. The `/api` prefix is stripped before proxying.
131 +`portal update` checks the latest GitHub release, downloads the matching asset,
132 +verifies its SHA256 checksum, and replaces the current executable.
133
134 ## Next Steps
135
118 -- **[Concepts](/concepts)** — Understand Portal's trustless relay model and end-to-end encryption
119 -- **[CLI Reference](/cli-reference)** — Complete command and flag documentation
120 -- **[Deployment](/deployment)** — Run your own relay server
136 +- [Concepts](/concepts): understand Portal's relay and transport model
137 +- [CLI Reference](/cli-reference): complete command and flag documentation
138 +- [TCP and UDP Tunneling](/tcp-udp-tunneling): raw TCP and UDP examples
139 +- [Deployment](/deployment): run your own public relay
docs/src/routes/tcp-udp-tunneling/+page.md
+70 -51
@@ -5,39 +5,52 @@ description: Tunnel raw TCP and UDP services like game servers through Portal.
5
6 # TCP/UDP Tunneling
7
8 -Portal supports raw TCP and UDP tunneling in addition to its default HTTPS proxy mode. This lets you expose services that don't speak HTTP — game servers, databases, custom binary protocols, and anything that needs a dedicated port on the public internet.
8 +Portal supports dedicated raw TCP and UDP relay modes in addition to the default
9 +HTTPS stream mode. Use these modes for services that need a public port instead
10 +of a browser HTTPS hostname.
11
12 ## Overview
13
12 -By default, Portal routes traffic through TLS SNI multiplexing on port 443 — perfect for web services. For everything else:
14 +The default stream path is best for web services:
15
14 -- **Raw TCP** — allocates a dedicated public port on the relay. Clients connect to `relay-host:port` with no TLS wrapping, exactly as if the service were directly internet-facing.
15 -- **UDP** — transports datagrams over a QUIC backhaul between the client and your local service. Ideal for game servers, VoIP, and other latency-sensitive protocols.
16 +```bash
17 +portal expose 3000
18 +```
19 +
20 +For protocols that do not fit a public HTTPS URL, use one of the port modes:
21 +
22 +- **Dedicated raw TCP**: allocates a public TCP port on the relay and bridges raw
23 + TCP to your local service.
24 +- **UDP relay**: allocates a public UDP port on the relay and carries datagrams
25 + over the tunnel backhaul to your local UDP service.
26
17 -Both modes require the relay server to have a port range configured and the corresponding transport enabled.
27 +Both modes require the relay server to have a port range configured and the
28 +matching transport enabled.
29
30 ## Relay Configuration
31
21 -Enable TCP and UDP transports on your relay by setting these environment variables:
32 +Enable TCP and UDP transports on your relay with these environment variables:
33
34 | Variable | Default | Description |
24 -|---|---|---|
35 +|----------|---------|-------------|
36 | `TCP_ENABLED` | `false` | Enable raw TCP port allocation |
37 | `UDP_ENABLED` | `false` | Enable UDP/QUIC datagram transport |
27 -| `MIN_PORT` | `0` (disabled) | Inclusive minimum of the port allocation range |
28 -| `MAX_PORT` | `0` (disabled) | Inclusive maximum of the port allocation range |
38 +| `MIN_PORT` | `0` | Inclusive minimum of the port allocation range; `0` disables allocation |
39 +| `MAX_PORT` | `0` | Inclusive maximum of the port allocation range; `0` disables allocation |
40
30 -`MIN_PORT` and `MAX_PORT` are shared between TCP and UDP. Both transports draw from the same pool. Setting either to `0` disables port allocation entirely.
41 +`MIN_PORT` and `MAX_PORT` are shared by TCP and UDP. The protocols are
42 +independent, so the same numeric port can be used by one TCP lease and one UDP
43 +lease at the same time.
44
32 -<div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 0.75rem 1rem; border-radius: 0.375rem; margin: 1rem 0;">
33 - <strong>Note:</strong> The relay's firewall or cloud security group must allow inbound traffic on the entire <code>MIN_PORT</code>–<code>MAX_PORT</code> range, for both TCP and UDP protocols, depending on which transports you enable.
34 -</div>
45 +Your firewall or cloud security group must allow inbound traffic on the exposed
46 +range for each protocol you enable.
47
36 -## TCP Tunneling
48 +## Dedicated Raw TCP
49
38 -Raw TCP tunneling allocates a dedicated port on the relay. Incoming TCP connections to that port are bridged directly to your local service with no TLS.
50 +Raw TCP mode allocates a public TCP port on the relay. Incoming connections to
51 +that port are bridged to your local TCP target.
52
40 -### Step 1 — Configure the relay
53 +Configure the relay:
54
55 ```bash
56 TCP_ENABLED=true
@@ -45,33 +58,35 @@ MIN_PORT=10000
58 MAX_PORT=20000
59 ```
60
48 -### Step 2 — Expose your local service
61 +Expose your local service:
62
63 ```bash
64 portal expose --tcp --name myapp localhost:8080
65 ```
66
54 -Portal registers a lease and receives an allocated port from the relay's range. The output will show the assigned port:
67 +The relay returns an assigned TCP address:
68
69 ```text
70 TCP port: relay.example.com:12345
71 ```
72
60 -### Step 3 — Connect clients
61 -
62 -Clients connect directly to the relay host and allocated port:
73 +Clients connect directly to that address:
74
75 ```text
76 relay.example.com:12345
77 ```
78
68 -No special client software is needed — any TCP client works.
79 +No Portal client is needed on the connecting side. Any TCP client can connect.
80 +Raw TCP mode does not add TLS, so use application-level encryption if the
81 +protocol needs confidentiality.
82
70 -## UDP Tunneling
83 +## UDP Relay
84
72 -UDP tunneling transports datagrams over a QUIC backhaul. The relay listens on an allocated UDP port and forwards datagrams to your local UDP service.
85 +UDP mode allocates a public UDP port on the relay. Datagrams are carried over a
86 +QUIC backhaul between the relay and the tunnel process, then forwarded to your
87 +local UDP target.
88
74 -### Step 1 — Configure the relay
89 +Configure the relay:
90
91 ```bash
92 UDP_ENABLED=true
@@ -79,22 +94,21 @@ MIN_PORT=10000
94 MAX_PORT=20000
95 ```
96
82 -### Step 2 — Expose your local service
83 -
84 -Use `--udp` to enable UDP, and `--udp-addr` to specify the local UDP target address:
97 +Expose your local service:
98
99 ```bash
100 portal expose --udp --udp-addr localhost:19132 --name myapp localhost:8080
101 ```
102
90 -- `--udp-addr` — the local UDP address that receives relayed datagrams. Defaults to the primary target address when `--udp` is set without `--udp-addr`.
91 -- The primary target (`localhost:8080` above) is still used for any TCP/HTTP traffic on the same lease.
103 +`--udp-addr` is the local UDP address that receives relayed datagrams. When it is
104 +omitted, Portal uses the primary target address. The primary positional target
105 +is still used for stream traffic on the same lease.
106
107 ## Minecraft Server Example
108
95 -This is a complete walkthrough for exposing a Minecraft Java Edition server (`localhost:25565`) to the public internet.
109 +This exposes a Minecraft Java Edition server running on `localhost:25565`.
110
97 -### Relay docker-compose snippet
111 +Relay docker-compose snippet:
112
113 ```yaml
114 services:
@@ -107,14 +121,13 @@ services:
121 ports:
122 - "443:443"
123 - "4017:4017"
110 - - "10000-20000:10000-20000" # TCP port range
124 + - "10000-20000:10000-20000"
125 ```
126
113 -<div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 0.75rem 1rem; border-radius: 0.375rem; margin: 1rem 0;">
114 - <strong>Note:</strong> Docker's port range syntax (<code>10000-20000:10000-20000</code>) creates one mapping per port. Keep ranges reasonably small — large ranges (10 000+ ports) can slow Docker startup.
115 -</div>
127 +Docker's port range syntax creates one mapping per port. Keep ranges reasonably
128 +small because very large ranges can slow Docker startup.
129
117 -### Expose the server
130 +Expose the server:
131
132 ```bash
133 portal expose --tcp --name minecraft localhost:25565
@@ -126,33 +139,39 @@ Output:
139 TCP port: relay.example.com:13742
140 ```
141
129 -### Connect in Minecraft
130 -
131 -In the Minecraft multiplayer screen, add a server with the address:
142 +In Minecraft, add a server with this address:
143
144 ```text
145 relay.example.com:13742
146 ```
147
137 -That's it. The port number will remain the same as long as the tunnel is active and the lease is held by the same identity.
148 +The assigned port remains stable while the lease is active and held by the same
149 +identity.
150
139 -## Combining TCP + UDP
151 +## Combining TCP And UDP
152
141 -A single lease can carry both a raw TCP port and a UDP relay simultaneously. This is useful for servers that use both protocols — for example, a game server that streams data over TCP and sends position updates over UDP.
153 +A single lease can carry both a raw TCP port and a UDP relay:
154
155 ```bash
156 portal expose --tcp --udp --udp-addr localhost:19132 localhost:25565
157 ```
158
147 -This registers one lease that gets:
148 -- A dedicated TCP port for `localhost:25565`
149 -- A dedicated UDP port forwarding datagrams to `localhost:19132`
159 +This registers one lease with:
160 +
161 +- a dedicated TCP port for `localhost:25565`
162 +- a dedicated UDP port forwarding datagrams to `localhost:19132`
163
151 -Both ports are drawn from the same `MIN_PORT`–`MAX_PORT` range on the relay.
164 +Both ports are drawn from the same `MIN_PORT` to `MAX_PORT` range on the relay.
165
166 ## Limitations
167
155 -- **Port range capacity** — the relay can serve at most `MAX_PORT - MIN_PORT + 1` concurrent TCP+UDP leases. Plan the range accordingly.
156 -- **No TLS on raw TCP** — the raw TCP path has no TLS wrapping. Traffic between the relay and connecting clients is unencrypted. Use application-level encryption (e.g. SSH, WireGuard) if the service requires confidentiality.
157 -- **UDP max packet size** — datagrams are capped at **1350 bytes**. Packets larger than this are dropped. Most game protocols fit within this limit, but verify if you use a custom protocol.
158 -- **Flow idle timeout** — UDP flows that have seen no traffic for **30 seconds** are cleaned up on the relay. Long-lived connections should send keepalive packets if they may be idle.
168 +- **Port range capacity**: the relay can serve at most
169 + `MAX_PORT - MIN_PORT + 1` concurrent TCP leases and the same number of UDP
170 + leases. Plan the range accordingly.
171 +- **No TLS on raw TCP**: raw TCP mode does not add TLS. Use application-level
172 + encryption when the service requires confidentiality.
173 +- **UDP max packet size**: datagrams are capped at 1350 bytes. Larger packets
174 + are dropped.
175 +- **Flow idle timeout**: UDP flows with no traffic for 30 seconds are cleaned up
176 + on the relay. Long-lived protocols should send keepalive packets if they may
177 + be idle.
docs/src/routes/what-is-portal/+page.md
+63 -19
@@ -1,36 +1,80 @@
1 ---
2 title: What is Portal?
3 -description: An introduction to Portal — a permissionless localhost tunnel and public relay system.
3 +description: An introduction to Portal, a permissionless localhost tunnel and public relay system.
4 ---
5
6 # What is Portal?
7
8 -Portal is an open-source localhost tunnel that publishes local services to the public internet through relay servers — without login, billing, or cloud SaaS dependencies.
8 +Portal is an open-source tunnel system for publishing local services through
9 +public relay servers. It is built around one boundary: **relays provide
10 +transport, while your tunnel process owns the endpoint behavior**.
11
10 -## Key Properties
12 +That means the normal HTTPS stream path does not work like a hosted reverse
13 +proxy. The relay routes by SNI and forwards the connection. Tenant TLS
14 +terminates in the tunnel process on your machine, so the relay does not receive
15 +tenant plaintext or session keys.
16
12 -- **Permissionless** — no account or API key required. Run the command and you're live.
13 -- **End-to-end TLS** — tenant TLS terminates locally with MITM detection; relays never see plaintext.
14 -- **Self-hostable** — use the public relay registry, discovered relay pools with failover, or run your own relay.
15 -- **Raw TCP** — carries HTTP, gRPC, WebSocket, and arbitrary TCP protocols without SSH or WebSocket overlays.
17 +## Core Properties
18
17 -## How It Works
19 +- **Permissionless**: no SaaS account or API key is required.
20 +- **Trustless stream path**: tenant TLS terminates locally, not at the relay.
21 +- **Mode-per-service transport**: use HTTPS stream, routed HTTP, raw TCP, or UDP
22 + depending on the service.
23 +- **Self-hostable relays**: use the public registry, explicit relay URLs, or your
24 + own relay.
25 +- **Relay pools and multi-hop**: keep multiple relays connected or route through
26 + an ordered relay chain.
27 +- **Local identity**: lease ownership is proven with a locally stored secp256k1
28 + identity and challenge signing.
29
19 -1. You start a local app (e.g., `localhost:3000`).
20 -2. You run `portal-tunnel` which connects to a relay server.
21 -3. The relay assigns a public HTTPS URL (e.g., `your-name.relay.example.com`).
22 -4. Incoming traffic is forwarded through the relay to your local app via an encrypted tunnel.
30 +## The Mental Model
31 +
32 +```text
33 +Public client
34 + -> Relay transport and routing
35 + -> Tunnel process on your machine
36 + -> Local service
37 +```
38 +
39 +The relay decides where traffic should go. The tunnel process decides what the
40 +traffic means.
41 +
42 +For the default stream path, the tunnel process accepts the connection as a TLS
43 +server and then proxies bytes to your local target. For routed HTTP mode, the
44 +tunnel process runs an HTTP reverse proxy and can apply HTTP-specific behavior.
45 +For raw TCP and UDP, the relay allocates public transport endpoints and forwards
46 +traffic to the tunnel process.
47 +
48 +## Transport Modes
49 +
50 +| Mode | Example | Best for |
51 +|------|---------|----------|
52 +| Default HTTPS stream | `portal expose 3000` | Web apps, APIs, WebSockets, gRPC over HTTP |
53 +| Routed HTTP | `portal expose --http-route /api=3001 --http-route /=5173` | Multiple local HTTP services behind one URL |
54 +| Dedicated raw TCP | `portal expose localhost:25565 --tcp` | Minecraft, game servers, custom TCP protocols |
55 +| UDP relay | `portal expose 8080 --udp --udp-addr 19132` | UDP game servers and datagram protocols |
56
57 ## When to Use Portal
58
26 -| Use Case | Example |
59 +| Use case | Example |
60 |----------|---------|
28 -| Share a dev server | Show a colleague your local branch |
29 -| Webhook development | Receive Stripe/GitHub webhooks locally |
30 -| Demo to clients | Temporary public URL for a staging app |
31 -| IoT / edge devices | Expose a device behind NAT |
61 +| Share a dev server | Show a local branch to a teammate |
62 +| Webhook development | Receive Stripe, GitHub, or Discord webhooks locally |
63 +| Client demos | Publish a temporary public URL for a staging app |
64 +| Multi-service app demos | Mount frontend and API services under one public URL |
65 +| Home servers | Expose a Minecraft server through a relay TCP port |
66 +| Edge devices | Reach a device behind NAT without opening inbound ports |
67 +
68 +## What Portal Does Not Promise
69 +
70 +Portal's default stream mode intentionally prevents the relay from controlling
71 +user HTTP responses. That is good for the trust model, but it means a public
72 +multi-tenant relay should not put arbitrary user tunnels under a brand domain
73 +that also carries first-party SEO value. Use a separate tunnel domain for shared
74 +wildcard leases.
75
76 ## Next Steps
77
35 -- [Prerequisites](/prerequisites) — what you need before installing
36 -- [Getting Started](/getting-started) — install and run your first tunnel
78 +- [Getting Started](/getting-started): install the CLI and expose your first app
79 +- [Concepts](/concepts): understand the trustless relay and transport model
80 +- [CLI Reference](/cli-reference): commands, flags, and examples