main
md 351 lines 12.5 KB
Rendered Raw
1 ---
2 title: CLI Reference
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. 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
12 ## Install
13
14 ### macOS / Linux
15
16 ```bash
17 curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
18 ```
19
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
27 ### From A Relay
28
29 If your relay publishes its own installer:
30
31 ```bash
32 curl -sSL https://portal.example.com/api/install.sh | bash
33 ```
34
35 The installer downloads the `portal` binary and adds it to your `PATH`. It does
36 not create a config file.
37
38 ## Command Overview
39
40 | Command | Purpose |
41 |---------|---------|
42 | `portal expose` | Expose one local service or one routed HTTP bundle |
43 | `portal list` | Print relay URLs resolved for this invocation |
44 | `portal agent` | Run a durable local multi-tunnel agent |
45 | `portal update` | Replace the CLI with the latest release |
46 | `portal version` | Print the current version |
47
48 ## `portal expose`
49
50 Expose a local service:
51
52 ```bash
53 portal expose [flags] <target>
54 ```
55
56 Or run routed HTTP mode:
57
58 ```bash
59 portal expose [flags] --http-route "PATH=UPSTREAM [METHOD[,METHOD...]:USDC_AMOUNT]" [...]
60 ```
61
62 The payment suffix is optional; omit it for free routes.
63
64 ### Target Formats
65
66 | Format | Example | Resolves to |
67 |--------|---------|-------------|
68 | Bare port | `3000` | `127.0.0.1:3000` |
69 | Host and port | `localhost:8080` | `localhost:8080` |
70 | URL host | `http://127.0.0.1:3000` | `127.0.0.1:3000` |
71
72 URL inputs are accepted for address parsing. Paths, queries, and fragments are
73 not supported.
74
75 ### Mode Selection
76
77 | Mode | Example | Notes |
78 |------|---------|-------|
79 | Default HTTPS stream | `portal expose 3000` | Relay routes by SNI; tunnel process terminates tenant TLS |
80 | Routed HTTP | `portal expose --http-route /api=3001 --http-route /=5173` | Tunnel process runs the HTTP reverse proxy |
81 | Dedicated raw TCP | `portal expose localhost:25565 --tcp` | Relay allocates a public TCP port |
82 | UDP relay | `portal expose 8080 --udp --udp-addr 19132` | Relay allocates a public UDP port |
83
84 ### Flags
85
86 | Flag | Type | Default | Description |
87 |------|------|---------|-------------|
88 | `--relays` | string | registry | Additional relay API URLs, comma-separated |
89 | `--discovery` | bool | `true` | Include registry relays and relay discovery expansion |
90 | `--max-active-relays` | int | `3` | Maximum auto-selected relays to keep connected; explicit relays are always included |
91 | `--multi-hop` | string | | Ordered multi-hop relay API URLs, comma-separated |
92 | `--multi-hop-depth` | int | `0` | Automatically select one multi-hop route with this hop count; `0` or `1` disables multi-hop |
93 | `--ban-mitm` | bool | `false` | Ban relay when the MITM self-probe detects TLS termination |
94 | `--identity-path` | string | `identity.json` | Identity JSON file path; created automatically when missing |
95 | `--identity-json` | string | | Identity JSON payload; overrides `--identity-path` contents and is persisted there when both are set |
96 | `--name` | string | auto | Public hostname prefix, one DNS label |
97 | `--description` | string | | Service description metadata |
98 | `--tags` | string | | Service tags metadata, comma-separated |
99 | `--thumbnail` | string | | Service thumbnail URL metadata |
100 | `--owner` | string | | Service owner metadata |
101 | `--hide` | bool | `false` | Hide service from relay listing screens |
102 | `--x402-pay-to` | string | | Sui USDC payment recipient address for this tunnel |
103 | `--x402-testnet` | bool | `false` | Use Sui testnet for tunnel x402 payments; default is Sui mainnet |
104 | `--http-route` | string | | HTTP route mapping in `PATH=UPSTREAM [METHOD[,METHOD...]:USDC_AMOUNT]` form; repeatable; route amounts require `--x402-pay-to` |
105 | `--tcp` | bool | `false` | Request a dedicated raw TCP port on the relay |
106 | `--udp` | bool | `false` | Enable public UDP relay in addition to the default stream path |
107 | `--udp-addr` | string | | Local UDP target; defaults to the primary target when `--udp` is enabled |
108 | `--metrics-addr` | string | | Optional `host:port` for Prometheus `/metrics` |
109
110 ### Constraints
111
112 - `<target>` cannot be combined with `--http-route`.
113 - `--http-route` cannot be combined with `--udp`.
114 - Explicit `--multi-hop` cannot be combined with automatic `--multi-hop-depth`.
115 - Multi-hop currently supports only the default SNI TLS stream transport.
116 - `--tcp` and `--udp` require matching transport support on the relay.
117 - Route payment amounts are part of `--http-route` and require a tunnel-owned
118 `--x402-pay-to`.
119 - Tunnel paid routes use Sui mainnet by default; add `--x402-testnet` for Sui
120 testnet. This is independent of relay-owned x402 facilitator settings.
121
122 ### Examples
123
124 Expose a local web app:
125
126 ```bash
127 portal expose 3000
128 ```
129
130 Use a custom name and relay:
131
132 ```bash
133 portal expose localhost:8080 \
134 --name myapp \
135 --relays https://portal.example.com \
136 --discovery=false \
137 --description "My web application" \
138 --tags webapp,demo
139 ```
140
141 Run routed HTTP mode:
142
143 ```bash
144 portal expose --name myapp \
145 --http-route /api=http://127.0.0.1:3001 \
146 --http-route /=http://127.0.0.1:5173
147 ```
148
149 Route matching is longest-prefix-first. `/api` matches `/api/*` and strips the
150 `/api` prefix before proxying to the upstream.
151
152 Expose a Minecraft server:
153
154 ```bash
155 portal expose localhost:25565 --name minecraft --tcp
156 ```
157
158 Enable UDP alongside the default stream target:
159
160 ```bash
161 portal expose localhost:8080 --udp --udp-addr localhost:19132 --name game
162 ```
163
164 Use an explicit multi-hop route:
165
166 ```bash
167 portal expose 3000 --multi-hop https://entry.example.com,https://exit.example.com
168 ```
169
170 Ask Portal to select one three-hop route:
171
172 ```bash
173 portal expose 3000 --multi-hop-depth 3
174 ```
175
176 Ban relays on MITM probe detection:
177
178 ```bash
179 portal expose 3000 --ban-mitm
180 ```
181
182 Publish a paid HTTP route:
183
184 ```bash
185 portal expose --name paid-app \
186 --http-route "/paid=http://127.0.0.1:3001 GET:0.01" \
187 --http-route /=http://127.0.0.1:5173 \
188 --x402-pay-to 0x...
189 ```
190
191 The optional method list limits which methods require payment; without it, every
192 method on that route prefix is paid.
193
194 The routed HTTP handler also serves `/x402/client.js` and `/x402/prepare` on the
195 public tunnel origin. Frontends served by one of the routes can use the shared
196 browser-only Sui wallet client for an in-page payment flow:
197
198 ```js
199 import { getSuiWallets, x402Fetch } from '/x402/client.js';
200
201 const [wallet] = getSuiWallets();
202 if (!wallet) {
203 throw new Error('Install a Sui wallet');
204 }
205
206 const [account] = await wallet.accounts();
207 if (!account) {
208 throw new Error('Connect a Sui account');
209 }
210
211 const response = await x402Fetch('/paid/photo', { method: 'GET' }, {
212 wallet,
213 account,
214 onEvent: (event) => console.log(event.type, event.message),
215 });
216 ```
217
218 `x402Fetch()` is a convenience wrapper: it asks `/x402/prepare` for the payment
219 transaction, asks the wallet to sign it, then retries the protected request with
220 an `X-PAYMENT` header. `onEvent` receives structured progress events; the older
221 `onStatus(message)` callback is still accepted for simple UIs. Routed HTTP
222 payments use Sui mainnet by default; pass `--x402-testnet` when exposing the
223 tunnel and use `network: 'sui:testnet'` in wallet clients that need an explicit
224 network. For mainnet, omit `network` or pass `sui:mainnet`.
225
226 Native clients should not load `/x402/client.js`. Call `POST /x402/prepare` with
227 `{ "sender": "...", "method": "GET", "path": "/paid/photo" }`, execute
228 `prepareTransaction.transaction` first when present, sign
229 `paymentTransaction.transaction`, and send the resulting x402 payload as the
230 `X-PAYMENT` header on the protected request:
231
232 ```js
233 const payload = {
234 x402Version: prepared.x402Version,
235 payload: {
236 signature,
237 transaction: prepared.paymentTransaction.transaction,
238 },
239 accepted: prepared.paymentRequirements,
240 resource: prepared.resource,
241 };
242 const header = base64(JSON.stringify(payload));
243 ```
244
245 The frontend integration is optional. Requests without a valid `X-PAYMENT`
246 header still receive x402 payment-required responses from the tunnel.
247
248 ## `portal list`
249
250 Print relay URLs resolved for the current invocation:
251
252 ```bash
253 portal list [flags]
254 ```
255
256 | Flag | Type | Default | Description |
257 |------|------|---------|-------------|
258 | `--relays` | string | registry | Additional relay URLs |
259 | `--default-relays` | bool | `true` | Include public registry relays |
260
261 `portal list` does not run the runtime relay discovery expansion loop. It only
262 resolves the registry seed list plus explicit relay URLs.
263
264 ## `portal agent`
265
266 Run a durable local agent that owns multiple tunnels from one config file:
267
268 ```bash
269 portal agent run
270 portal agent dashboard
271 portal agent stop
272 portal agent restart
273 ```
274
275 | Command | Description |
276 |---------|-------------|
277 | `portal agent run` | Install or update and start the managed agent service |
278 | `portal agent run --config config.toml --foreground` | Run the agent in the current terminal |
279 | `portal agent dashboard` | Open the local TUI for tunnels, relays, multi-hop routes, and settings |
280 | `portal agent stop` | Gracefully stop the agent and disable or stop the OS service |
281 | `portal agent restart` | Stop the current agent if present, install or update the service, and start it again |
282
283 The local control API binds only to loopback and uses a token in the agent state
284 directory. See [Portal Agent](/portal-agent) for the workflow and
285 [Configuration Reference](/configuration#configtoml) for the `config.toml`
286 format.
287
288 Agent flags:
289
290 | Command | Flag | Default | Description |
291 |---------|------|---------|-------------|
292 | `portal agent run` | `--config` | platform default | Agent TOML config path |
293 | `portal agent run` | `--foreground` | `false` | Run in the current process without installing the OS service |
294 | `portal agent run` | `--service` | `false` | Internal service entrypoint used by the installed OS service |
295 | `portal agent dashboard` | `--config` | platform default | Config path used for display and state-dir discovery |
296 | `portal agent dashboard` | `--state-dir` | config/default | Agent state directory to attach to |
297 | `portal agent stop` | `--config` | platform default | Config path used to resolve state dir and service name |
298 | `portal agent stop` | `--state-dir` | config/default | Agent state directory to stop |
299 | `portal agent restart` | `--config` | platform default | Config path used to reinstall and restart the service |
300
301 ## `portal update`
302
303 Update the CLI binary:
304
305 ```bash
306 portal update
307 ```
308
309 The updater resolves the latest GitHub release, compares it with the installed
310 version, downloads the matching asset, verifies its SHA256 checksum, and
311 replaces the current executable.
312
313 ## `portal version`
314
315 ```bash
316 portal version
317 ```
318
319 Prints the installed version string and exits.
320
321 ## Behavior Notes
322
323 - `portal expose` and `portal list` check the latest published GitHub Release in
324 the background. A `main` merge or branch artifact is not offered to installed
325 clients until the release is created with matching binary and checksum assets.
326 - `portal expose` loads or creates a signing identity at `identity.json` or
327 `--identity-path`.
328 - Multiple relay URLs are registered independently. A failed relay does not stop
329 healthy relays from serving.
330 - With discovery enabled, the tunnel consumes relay `/discovery` results and
331 reconciles its relay pool.
332 - MITM enforcement is enabled by default for the default stream path.
333 - When the local stream target is unreachable, the tunnel returns an HTTP 503
334 page to browser-style clients.
335 - Routed HTTP mode is HTTP-only and runs inside the tunnel process.
336 - `--tcp` requires relay TCP port transport, a valid `MIN_PORT`/`MAX_PORT`
337 range, and TCP port transport enabled in the admin panel.
338 - `--udp` requires relay UDP transport, a valid `MIN_PORT`/`MAX_PORT` range, UDP
339 enabled in the admin panel, and `SNI_PORT/udp` reachable for the QUIC backhaul.
340 - Bare `portal [flags]` is not accepted; use `portal expose` explicitly.
341 - Runtime `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variable fallbacks
342 are not used.
343
344 ## Next Steps
345
346 - [Getting Started](/getting-started): run your first tunnel
347 - [Portal Agent](/portal-agent): run durable multi-tunnel services
348 - [Wallet and ENS](/wallet-and-ens): understand admin tokens, wallet auth, and ENS gasless DNS
349 - [Concepts](/concepts): understand the relay and transport model
350 - [TCP and UDP Tunneling](/tcp-udp-tunneling): raw TCP and UDP setup
351 - [Deployment](/deployment): run your own relay server