mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-27 04:11:01 +00:00
8e7a567f69
* feat: pilot agent outbound-mode for remote nodes Adds a second mode for managing remote nodes: the agent dials an outbound WebSocket tunnel to the primary, so the remote host no longer needs an inbound port, a reachable URL, or its own TLS certificate. Works behind NAT, residential routers, and corporate firewalls. The primary multiplexes HTTP and WebSocket requests over a single tunnel via a hybrid JSON + binary frame protocol, bridged through a per-tunnel loopback server so existing proxy and upgrade handlers route pilot-mode nodes identically to proxy-mode ones. Enrollment uses a single-use 15-minute pilot_enroll JWT exchanged for a long-lived pilot_tunnel credential on first connect. Proxy mode continues to work unchanged and both modes are supported side-by-side. * test(e2e): switch to proxy mode before asserting api_url field Remote nodes default to Pilot Agent mode, which hides the api_url input. The SSRF-validation tests need proxy mode, so the helper now selects Distributed API Proxy after picking Remote type before asserting the field is visible. * fix(e2e): wire Combobox id prop so node-mode selector resolves The Combobox trigger button had no id, leaving its Label orphaned and making getByRole name-based lookups fail. Adding id to the primitive, passing id="node-mode" from NodeManager, and updating the E2E helper to use #node-mode fixes both the a11y regression and the CI timeout.
97 lines
6.1 KiB
Plaintext
97 lines
6.1 KiB
Plaintext
---
|
|
title: Pilot Agent
|
|
description: Add remote nodes behind NAT, residential networks, or corporate firewalls without exposing any inbound port.
|
|
---
|
|
|
|
Pilot Agent mode connects a remote server to your primary Sencho instance through a single outbound WebSocket tunnel. Every request the primary sends to that node, HTTP or WebSocket, rides through this tunnel. The remote host never opens an inbound port and never needs a TLS certificate.
|
|
|
|
## When to use Pilot Agent
|
|
|
|
Pick Pilot Agent when the remote host:
|
|
- Sits behind NAT or a residential router.
|
|
- Lives on a corporate network that blocks inbound connections.
|
|
- Roams between networks (laptops, mobile hotspots, rotating cloud IPs).
|
|
- Would otherwise need a reverse proxy, a dynamic DNS entry, or a self-signed TLS certificate just to be reachable.
|
|
|
|
Pick **Distributed API Proxy** (documented in [Multi-Node Management](/features/multi-node)) when the remote host already has a stable, reachable URL, for example a VPS with a public IP or a LAN server on a home network. Both modes are supported side-by-side, one per node.
|
|
|
|
## How it works
|
|
|
|
The agent runs inside a second container on the remote host, using the same `saelix/sencho:latest` image. Setting `SENCHO_MODE=pilot` plus a primary URL and a one-time enrollment token puts the container into agent mode. On boot it dials the primary at `wss://<primary>/api/pilot/tunnel` and holds that connection open. For every tunneled request the primary demultiplexes frames to an internal loopback server, which re-issues the request locally on the agent host against its Docker socket. License tier, role checks, and all other authorization continue to flow from the primary, exactly like proxy mode.
|
|
|
|
Only outbound HTTPS from the remote to the primary is required. Nothing else is exposed.
|
|
|
|
## Enrollment walkthrough
|
|
|
|
### 1. Add the node on the primary
|
|
|
|
On your primary instance open **Settings → Nodes** and click **Add Node**.
|
|
|
|
- **Type:** Remote
|
|
- **Mode:** Pilot Agent (the default for remote nodes)
|
|
- **Name:** any label, for example `homelab-nuc`
|
|
- **Compose Directory:** the folder on the remote host where stack folders will live
|
|
|
|
Click **Add Node**. The form is replaced by an enrollment dialog with a one-line `docker run` command.
|
|
|
|
<Frame>
|
|
<img src="/images/pilot-agent/enrollment-dialog.png" alt="Pilot Agent enrollment dialog with docker run command" />
|
|
</Frame>
|
|
|
|
### 2. Run the command on the remote host
|
|
|
|
Copy the command and paste it on the remote host. It looks like:
|
|
|
|
```bash
|
|
docker run -d --restart=unless-stopped --name sencho-agent \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
-v sencho-agent-data:/app/data \
|
|
-v /opt/docker/sencho:/app/compose \
|
|
-e SENCHO_MODE=pilot \
|
|
-e SENCHO_PRIMARY_URL=https://sencho.example.com \
|
|
-e SENCHO_ENROLL_TOKEN=<short-lived token> \
|
|
saelix/sencho:latest
|
|
```
|
|
|
|
The enrollment token is single-use and expires after 15 minutes. On first connect the agent exchanges it for a long-lived tunnel credential, persisted inside the container volume at `/app/data/pilot.jwt`. Subsequent restarts reconnect automatically without needing a new token.
|
|
|
|
### 3. Confirm the node is online
|
|
|
|
The node flips to **Online** in the primary within a few seconds of the agent container starting. The **Endpoint** column shows `tunnel` alongside the time the primary last saw a frame from the agent.
|
|
|
|
Open the node in the sidebar switcher and use it exactly like any other node: deploy stacks, tail logs, open container terminals, watch host stats.
|
|
|
|
## Day-to-day operation
|
|
|
|
Once enrolled, a Pilot Agent node is indistinguishable from a proxy-mode node in the UI. The same switcher, the same stacks page, the same editor, the same host console, the same dashboard.
|
|
|
|
The agent reconnects automatically if the tunnel drops (transient network blip, primary restart, remote host reboot). It applies an exponential backoff between retries starting at 1 second and capping at 60 seconds, so reconnect traffic stays bounded even during long outages.
|
|
|
|
## Regenerating enrollment
|
|
|
|
If the agent container is destroyed before its first successful connect, or the enrollment token expires before you paste it on the remote, open the node's edit dialog on the primary and click **Regenerate enrollment token**. A fresh 15-minute token is issued and the previous tunnel (if any) is disconnected so the new agent replaces it cleanly.
|
|
|
|
## Troubleshooting
|
|
|
|
<AccordionGroup>
|
|
<Accordion title="Node stays Offline after starting the agent container">
|
|
Check the agent container's logs for the first `[Pilot]` line. It prints the primary URL it is dialing and the reason for any failed connect. Common causes: the primary URL is wrong or unreachable from the remote host, the enrollment token expired, or the primary is behind a reverse proxy that strips WebSocket upgrades.
|
|
</Accordion>
|
|
|
|
<Accordion title="Tunnel keeps disconnecting and reconnecting">
|
|
Usually caused by an HTTP proxy or load balancer in front of the primary with a short idle timeout. The tunnel sends a ping every 30 seconds, so raise the proxy's WebSocket idle timeout above that (90 seconds is a safe floor). If you terminate TLS on the proxy, make sure WebSocket upgrade passthrough is enabled.
|
|
</Accordion>
|
|
|
|
<Accordion title="Enrollment token expired before I ran the docker command">
|
|
Open **Settings → Nodes** on the primary, click the pencil icon on the pending node, then **Regenerate enrollment token**. The dialog shows a fresh command with a new token.
|
|
</Accordion>
|
|
|
|
<Accordion title="I ran the enrollment command on the wrong host">
|
|
Stop and remove the agent container on the wrong host (`docker stop sencho-agent && docker rm sencho-agent`), then regenerate the enrollment token on the primary and run the command on the correct host.
|
|
</Accordion>
|
|
|
|
<Accordion title="Primary was restored from backup and the agent will not reconnect">
|
|
The agent's persisted tunnel credential is signed with the primary's JWT secret. If that secret is rotated or the primary is rebuilt from scratch, existing tunnels stop verifying. Regenerate enrollment for the affected node and restart the agent container so it consumes the fresh token.
|
|
</Accordion>
|
|
</AccordionGroup>
|