Skip to main content
Remote access lets a gateway expose itself through a relay so desktop or mobile clients can connect from another network. The gateway owns this feature. Clients only edit settings, save the relay key, and display status. The implementation lives in 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:
  • enabled
  • server
  • service_name
  • transport
  • secret_ref
Protocol updates may include a key or 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 builds RemoteAccessDesiredState:
  1. Read the effective remote-access settings.
  2. Resolve the configured secret ref from the gateway keystore.
  3. Pass settings plus optional key to RemoteAccessSupervisor::apply.
Enabling remote access without a key is valid settings state but invalid runtime state. The supervisor publishes 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:
The config is not persisted as a runtime file. Startup cleanup still removes stale 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 into GatewayRemoteAccessStatusSnapshot: 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 publishes Reconnecting, 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 becomes Stopped. 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/get returns settings.remote_access.
  • settings/update applies remote_access updates and persists non-secret settings.
  • status changes are emitted as remote-access notifications.
This keeps the control plane consistent with memory, thread episodic context, keepawake, and CLI runtime settings.

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.
Do not hide these behind “offline”. The user action differs: fix config, save a key, create a relay service, rotate a token, or inspect relay reachability.

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.