crates/tunnel, crates/gateway/src/settings.rs, and gateway startup/shutdown wiring in crates/gateway/src/lib.rs.
Ownership model
The client is never the tunnel endpoint. If the desktop app is connected to a remote gateway and enables remote access, the tunnel starts on that remote gateway host.
Static config
GatewayRemoteAccessConfig comes from layered app config and has these defaults:
The runtime validates relay addresses rather than passing arbitrary URLs to rathole. Accepted forms include hostnames and IP socket addresses with a non-zero port. URLs with schemes or paths are rejected.
Runtime settings
GatewayRemoteAccessSettingsOverride is stored in gateway-settings.toml and can include:
enabledserverservice_nametransportsecret_ref
clear_key. The key is normalized, stored through GatewaySecrets::put_remote_access_secret, and referenced by secret ref. The default secret ref is remote_access.
The effective settings snapshot includes has_key and the current status snapshot. Clients should rely on has_key, not on raw key visibility.
Desired state
At startup and after settings updates, the gateway buildsRemoteAccessDesiredState:
- Read the effective remote-access settings.
- Resolve the configured secret ref from the gateway keystore.
- Pass settings plus optional key to
RemoteAccessSupervisor::apply.
Failed with MissingKey instead of starting a tunnel with incomplete credentials.
Supervisor lifecycle
RemoteAccessSupervisor owns one supervised task at a time. apply increments a generation, stops any existing task, validates the new desired state, and starts a new rathole client task when the desired state is runnable.
Validation can short-circuit into status-only states:
When runnable, the supervisor publishes
Starting, creates a shutdown channel, and spawns supervise_rathole.
Rathole client
The supervisor builds an in-memory rathole client config:rathole-client-*.toml files from older or interrupted flows so raw tokens do not linger in remote-access runtime directories.
Status projection
Rathole events are projected intoGatewayRemoteAccessStatusSnapshot:
Each snapshot carries state, optional error kind, optional message, and update timestamp. The gateway includes this in
settings/get and broadcasts changes through remote-access status notifications.
Restart policy
If the rathole task exits unexpectedly, the supervisor publishesReconnecting, waits with exponential backoff and deterministic jitter, and starts it again until max_restarts is reached. max_restarts = 0 means unlimited restarts.
The delay doubles up to restart_max_ms. Jitter is deterministic from the attempt number, which avoids needing randomness in the supervisor while still spreading retry timing.
Shutdown
On settings change, gateway shutdown, or supervisor shutdown, Pioneer sends a watch-channel shutdown signal to the rathole task. The supervisor gives it a short graceful window, then aborts the task if needed. Final status becomesStopped.
Gateway shutdown calls shutdown_remote_access_supervisor before shutting down the WebSocket server. This keeps tunnel process lifetime tied to gateway lifetime.
Protocol boundary
Remote access is exposed through settings, not through a separate remote-access method namespace:settings/getreturnssettings.remote_access.settings/updateappliesremote_accessupdates and persists non-secret settings.- status changes are emitted as remote-access notifications.
Failure modes
Important failure modes are intentionally visible:- relay address malformed;
- unsupported transport requested;
- service name empty, too long, or with unsupported characters;
- key missing or cleared;
- relay connection fails;
- tunnel auth fails;
- relay reports service not found;
- rathole task exits repeatedly until restart limit is reached.
Developer rules
- Do not store remote-access keys in
gateway-settings.toml,gateway.db, or client registry files. - Treat remote access as gateway-owned state; clients must not start local tunnel tasks for a remote gateway.
- Keep the rathole token out of persisted runtime config files.
- Prefer status snapshots and notifications over client-side polling.
- When adding a transport, update config parsing, supervisor validation, protocol enums, settings UI, and this page together.
- When changing status states or error kinds, update client presentation and Settings API.
Related pages
- Gateway Architecture explains gateway startup, settings, and runtime ownership.
- Secret Storage explains where the remote-access key lives.
- Protocol Layer explains settings and notifications as public contract.
- Desktop Remote Access explains the user-facing controls.