Files
ServerKit/docs/runbooks/remote-access-two-host.md

4.9 KiB

Runbook — Remote access, two hosts (closing the remote-access proving debt)

Plan 26 Phase 6 #17. The remote-access feature (WireGuard tunnels via agent pairing — /remote-access, TunnelBrokerService, TunnelPublishService) shipped and is unit + panel↔agent e2e tested, but the full two-host flow has never been proven end-to-end on real infrastructure: pair an edge box to a NAT'd home box and publish a home-only service to the public internet through the tunnel. This runbook is that verification.

Status: NOT YET EXECUTED. Requires two real machines — a public-IP edge VPS and a NAT'd home host running a service (e.g. Jellyfin) — with agents paired to the same panel. Dev is a single Windows box; execute on real infra and paste the scrubbed transcript into "Result".

Topology

        internet ──▶ edge VPS (public IP, agent "edge")
                        │  WireGuard tunnel
                        ▼
                     home host (behind NAT, agent "home")
                        │
                        ▼
                     Jellyfin :8096  (home-only service)

The edge box terminates public traffic and forwards it over the WireGuard tunnel to the home box's service. Keys stay on the hosts; the panel is authoritative for config only (see docs/REMOTE_ACCESS_ROADMAP.md).

Preconditions

  • Edge: a VPS with a public IP, the agent installed and paired (server id $EDGE), capable of running WireGuard (the agent embeds wireguard-go) and opening its listen port in the firewall.
  • Home: a machine behind NAT running Jellyfin (or any HTTP service) on :8096, agent installed and paired (server id $HOME).
  • Both agents online in the panel (Servers page shows both green).
  • A domain/subdomain for the public URL (e.g. media.example.com) — ideally via a connected DNS provider so the panel can create the record and cert.
  • $PANEL = panel base URL, $TOKEN = an admin/developer JWT.

Steps

1. Confirm both agents are connected

curl -s "$PANEL/api/v1/servers/available" -H "Authorization: Bearer $TOKEN" \
  | jq '.servers[] | {id, name, status}'

Both the edge and home servers must be online. Note their ids as $EDGE (public) and $HOME (NAT'd).

2. Pair the two boxes into a WireGuard tunnel

curl -s -X POST "$PANEL/api/v1/tunnels/" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"edge_server_id\":\"$EDGE\",\"private_server_id\":\"$HOME\",\"name\":\"home-media\"}" \
  | jq .

Expected: 201 with a tunnel object (note its id → $TID) and a firewall hint. If firewall.auto_open is present it shows whether the agent opened the edge listen port automatically; otherwise open it by hand per the hint.

3. Confirm the tunnel is up (handshake on both peers)

curl -s "$PANEL/api/v1/tunnels/$TID" -H "Authorization: Bearer $TOKEN" | jq .

Expected: live status shows both peers with a recent WireGuard handshake and a non-zero transfer once traffic flows. A missing handshake means the edge listen port isn't reachable (firewall) or a key/config mismatch — fix before publishing.

4. Publish the home service through the tunnel

curl -s -X POST "$PANEL/api/v1/tunnels/$TID/services" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"jellyfin","upstream_host":"127.0.0.1","upstream_port":8096,"public_hostname":"media.example.com"}' \
  | jq .

This wires the edge nginx to reverse-proxy media.example.com over the tunnel to the home box's 127.0.0.1:8096, and (with a DNS provider) creates the A record + best-effort cert. Confirm the DNS record and, if issued, HTTPS.

5. Verify end-to-end from the public internet

From a machine outside both networks (e.g. a phone on cellular):

curl -sI https://media.example.com/System/Info/Public
# or open https://media.example.com in a browser and confirm Jellyfin loads

Expected: the home-only Jellyfin responds through the edge over the tunnel. To prove the traffic really traverses the tunnel and not some other path, stop the tunnel (DELETE /api/v1/tunnels/$TID) and confirm the public URL goes dead, then re-create it and confirm it recovers.

6. Tear down (leave infra clean)

curl -s -X DELETE "$PANEL/api/v1/tunnels/$TID" -H "Authorization: Bearer $TOKEN" | jq .

Result (paste scrubbed transcript here)

Fill this in after executing. Scrub real hostnames/IPs/keys. Include: both agents online, the 201 tunnel create response, the live status showing a WireGuard handshake on both peers, the publish response, and a public curl to media.example.com returning the home service (plus the stop→dead, restart→recovers proof). Once filled with a real pass, mark plan 26 #17 ✅ and update the Fleet Contract remote-access row.

(pending two-host execution)