main
md 298 lines 9.93 KB
Rendered Raw
1 ---
2 title: Portal Agent
3 description: Run durable multi-tunnel Portal services from a local config file.
4 ---
5
6 # Portal Agent
7
8 `portal agent` is the long-lived version of `portal expose`. It runs one local
9 agent process, reads a TOML config file, and keeps every declared tunnel
10 registered with the selected relays.
11
12 Use the agent when tunnels should survive terminal closes, login sessions, or
13 manual restarts. Use `portal expose` for one-off development sessions.
14
15 ## What The Agent Owns
16
17 The agent owns:
18
19 - one `config.toml`
20 - one local loopback control API
21 - one OS service when run in managed mode
22 - one or more tunnel runtimes declared under `[[tunnels]]`
23 - tunnel identities stored under the agent state directory unless overridden
24
25 Each tunnel still uses the normal Portal SDK path internally: it registers a
26 lease, opens reverse sessions, renews the lease, and proxies traffic to the
27 configured local target.
28
29 ## Create A Config
30
31 `portal agent run` requires an existing config file. The installer does not
32 create one.
33
34 Default config paths:
35
36 | OS | Config path |
37 |----|-------------|
38 | Linux user | `$XDG_CONFIG_HOME/portal-tunnel/agent/config.toml` or `~/.config/portal-tunnel/agent/config.toml` |
39 | Linux root | `/etc/portal-tunnel/agent/config.toml` |
40 | macOS user | `~/Library/Application Support/Portal Tunnel/Agent/config.toml` |
41 | macOS root | `/Library/Application Support/Portal Tunnel/Agent/config.toml` |
42 | Windows | `%ProgramData%\Portal Tunnel\Agent\config.toml` |
43
44 Minimal config:
45
46 ```toml
47 [agent]
48 control_addr = "127.0.0.1:4018"
49 service_name = "portal-agent"
50
51 [[tunnels]]
52 id = "web"
53 name = "myapp"
54 target = "127.0.0.1:3000"
55 relays = ["https://portal.example.com"]
56 discovery = false
57 description = "Managed web tunnel"
58 tags = ["web"]
59 ```
60
61 Routed HTTP config:
62
63 ```toml
64 [agent]
65 control_addr = "127.0.0.1:4018"
66 service_name = "portal-agent"
67
68 [[tunnels]]
69 id = "frontend"
70 name = "myapp"
71 relays = ["https://portal.example.com"]
72 discovery = false
73 x402_pay_to = "0x..."
74 x402_testnet = true
75
76 [[tunnels.http_routes]]
77 prefix = "/api"
78 upstream = "http://127.0.0.1:3001"
79 methods = ["GET"]
80 amount = "0.01"
81
82 [[tunnels.http_routes]]
83 prefix = "/"
84 upstream = "http://127.0.0.1:5173"
85 ```
86
87 If a route has `amount`, the tunnel serves `/x402/client.js` and
88 `/x402/prepare` on the public tunnel origin. A browser frontend served by the
89 `/` route can import the helper and call `x402Fetch()` from its own UI. Native
90 clients use `/x402/prepare` directly and send the signed payload as
91 `X-PAYMENT`. The tunnel still verifies and settles payment before proxying the
92 paid route. Paid routes use Sui mainnet by default; set `x402_testnet = true`
93 to use Sui testnet.
94
95 Relative paths in the config are resolved from the config file directory.
96
97 ## Run The Agent
98
99 Run as a managed OS service:
100
101 ```bash
102 portal agent run
103 ```
104
105 Run in the current terminal:
106
107 ```bash
108 portal agent run --config config.toml --foreground
109 ```
110
111 Open the local dashboard:
112
113 ```bash
114 portal agent dashboard
115 ```
116
117 Restart or stop:
118
119 ```bash
120 portal agent restart
121 portal agent stop
122 ```
123
124 `portal agent run`, `stop`, and `restart` load the config so they can find the
125 state directory and service name. `portal agent dashboard` can attach with only
126 the default state directory or an explicit `--state-dir`.
127
128 `portal agent run --service` is the internal service entrypoint installed by
129 `portal agent run`. Operators normally do not run it directly.
130
131 ## Dashboard
132
133 The dashboard is a local terminal UI. It polls agent status every two seconds
134 and edits the same TOML config file that the service uses.
135
136 Dashboard panes:
137
138 | Pane | Purpose |
139 |------|---------|
140 | Tunnels | Add, select, and delete tunnels |
141 | Settings | Edit max active relays and public metadata |
142 | Relays | Connect or disconnect relays for the selected tunnel |
143 | Multi-hop | Build and apply an ordered multi-hop route |
144
145 Keyboard controls:
146
147 | Key | Action |
148 |-----|--------|
149 | `left` / `right` | Switch panes |
150 | `up` / `down` | Move within the active pane |
151 | `enter` | Apply the active action |
152 | `delete` | Delete the selected tunnel or disconnect the selected relay |
153 | `c` | Connect the selected relay in the Relays pane |
154 | `d` | Disconnect the selected relay in the Relays pane |
155 | `o` | Open the selected public tunnel URL |
156 | `a` | Add the selected relay as a multi-hop hop |
157 | `p` | Apply a drafted multi-hop route |
158 | `esc` | Cancel input or return to the Tunnels pane |
159 | `ctrl+c` | Exit the dashboard |
160
161 The Add Tunnel action opens a form. Fill either `Target` for a simple loopback
162 tunnel or `Routes` for routed HTTP. Routes use this syntax:
163
164 ```text
165 /paid=3001 GET:0.01; /=5173
166 ```
167
168 Each entry is `PATH=UPSTREAM [METHOD[,METHOD...]:USDC_AMOUNT]`. Fill `X402 Pay
169 To` when any route has an amount, and set `X402 Testnet` to `true` for Sui
170 testnet. The form also accepts explicit `Relays`,
171 `Discovery`, and `Max Relays`; max relays caps auto-selected discovery relays
172 while explicit relays are still included.
173
174 After creation, routed HTTP paths, x402 payment amounts, payment network, and
175 discovery mode are read-only in the Settings pane. To change routes, payment
176 amounts, payment network, or discovery mode, edit `http_routes`,
177 `x402_pay_to`, `x402_testnet`, and `discovery` in `config.toml`, then restart
178 the agent or tunnel. Other advanced options such as UDP, TCP, custom
179 identity JSON, or explicit multi-hop defaults are also configured in
180 `config.toml`.
181
182 ## Tunnel Config Fields
183
184 Common fields:
185
186 | Field | Description |
187 |-------|-------------|
188 | `id` | Stable local tunnel ID used by the dashboard and control API |
189 | `name` | Public lease name, used as the subdomain label |
190 | `target` | Local TCP target, equivalent to `portal expose <target>` |
191 | `http_routes` | Routed HTTP mappings; cannot be combined with `target` or `udp` |
192 | `relays` | Explicit relay API URLs |
193 | `discovery` | Include registry and relay discovery expansion |
194 | `max_active_relays` | Maximum auto-selected relays kept connected |
195 | `identity_path` | Tunnel identity JSON path |
196 | `identity_json` | Identity JSON payload; persisted to `identity_path` when both are set |
197 | `udp`, `udp_addr` | UDP transport settings |
198 | `tcp` | Dedicated raw TCP port setting |
199 | `multi_hop` | Explicit ordered multi-hop relay URLs |
200 | `multi_hop_depth` | Automatically choose one multi-hop route with this depth |
201 | `ban_mitm` | Ban relays when the TLS self-probe detects termination; defaults to warning-only |
202 | `description`, `tags`, `owner`, `thumbnail`, `hide` | Public relay metadata |
203 | `x402_pay_to` | Tunnel-owned Sui USDC x402 recipient for paid HTTP routes |
204 | `x402_testnet` | Use Sui testnet for tunnel-owned x402 paid routes; omitted or `false` uses Sui mainnet |
205 | `http_routes[].amount` | Optional Sui USDC x402 amount, such as `0.01`, for one HTTP route prefix |
206 | `http_routes[].methods` | Optional HTTP methods that require payment on that route; empty means every method |
207
208 Constraints match `portal expose`:
209
210 - `target` cannot be combined with `http_routes`.
211 - `http_routes` cannot be combined with `udp`.
212 - `multi_hop` requires at least two relay URLs.
213 - `multi_hop` cannot be combined with `multi_hop_depth`.
214 - Multi-hop currently supports only the default stream transport, not UDP or raw
215 TCP port mode.
216 - `http_routes[].amount` requires `x402_pay_to`.
217 - `http_routes[].methods` requires `http_routes[].amount`.
218
219 ## Identity Layout
220
221 If `identity_path` is omitted:
222
223 - a single tunnel uses `<state_dir>/identity.json`
224 - multiple tunnels use `<state_dir>/<tunnel-id>/identity.json`
225
226 Reusing an identity keeps the same tunnel address and lease identity across
227 restarts. Use separate identity paths when two tunnels should have separate
228 lease identities.
229
230 ## Local Control API
231
232 The agent writes this file while running:
233
234 ```text
235 <state_dir>/agent-endpoint.json
236 ```
237
238 It contains the loopback control address and a random bearer token. CLI commands
239 read this file and send `Authorization: Bearer <token>` to the local control
240 API.
241
242 The agent refuses non-loopback `control_addr` values. Use `127.0.0.1`,
243 `localhost`, or another loopback address.
244
245 Control endpoints:
246
247 | Method | Path | Auth | Purpose |
248 |--------|------|------|---------|
249 | `GET` | `/agent/status` | Bearer token or wallet session | Read agent and tunnel status |
250 | `POST` | `/agent/shutdown` | Bearer token | Ask the agent to stop |
251 | `POST` | `/agent/tunnels` | Bearer token | Add a tunnel |
252 | `PATCH` | `/agent/tunnels/{id}` | Bearer token | Update metadata or max active relays |
253 | `DELETE` | `/agent/tunnels/{id}` | Bearer token | Delete a tunnel |
254 | `POST` | `/agent/tunnels/{id}/relays` | Bearer token | Connect a relay |
255 | `DELETE` | `/agent/tunnels/{id}/relays` | Bearer token | Disconnect a relay |
256 | `POST` | `/agent/tunnels/{id}/multi-hop` | Bearer token | Apply a multi-hop route |
257 | `DELETE` | `/agent/tunnels/{id}/multi-hop` | Bearer token | Clear multi-hop routing |
258
259 Wallet auth endpoints also exist under `/agent/auth/*`. Wallet-authenticated
260 requests are read-only and can only call `/agent/status`; mutating operations
261 use the local bearer token from the state directory.
262
263 ## Agent Wallet Access
264
265 Set `agent.allowed_wallets` to restrict wallet-authenticated status access:
266
267 ```toml
268 [agent]
269 allowed_wallets = ["0x1234567890abcdef1234567890abcdef12345678"]
270 ```
271
272 When `allowed_wallets` is empty, any wallet can sign in to the loopback agent
273 auth endpoint. This does not grant mutation rights; the bearer token still owns
274 config and tunnel changes.
275
276 ## Troubleshooting
277
278 If the dashboard says the agent is unavailable, start it explicitly:
279
280 ```bash
281 portal agent run --config config.toml
282 ```
283
284 If the OS service manager is unavailable:
285
286 ```bash
287 portal agent run --config config.toml --foreground
288 ```
289
290 If a tunnel is stuck in `error`, check the selected tunnel row in the dashboard.
291 Common causes are an invalid local target, a relay URL that cannot be reached, a
292 transport disabled on the relay, or an invalid multi-hop route.
293
294 ## Next Steps
295
296 - [Configuration Reference](/configuration#configtoml): every agent config field
297 - [Wallet and ENS](/wallet-and-ens): admin tokens, wallet auth, and ENS gasless behavior
298 - [CLI Reference](/cli-reference): command flags and examples