Standalone skychat — TCP-direct & CXO¶
The visor-hosted app (skychat-visor.md) lives and dies
with the visor. The --standalone flag detaches the chat-app so it
runs as its own process — it survives visor restarts and is reached
over a direct noise-XK TCP transport and/or CXO feeds.
This is the right tool when chat must stay up across visor rebuilds (e.g. fleets where every PR merge restarts the visor), or for a NAT-pierced, deterministic-latency channel that doesn't depend on dmsg.
The two ways to run it¶
- standalone (
--standalone): no parent visor. Skips thePROC_CONFIGhandshake, disables the skynet + dmsg listenLoops, and keeps--tcp-listen/--tcp-peer+ CXO + the HTTP control surface. Reachable via TCP-direct/CXO only. Pair-RPC endpoints return 503 (no visor to relay through). - embedded + TCP/CXO: drop
--standaloneand the app re-attaches to the visor (skynet+dmsg as usual) and serves the extra TCP/CXO entry points. Best of both, but tied to the visor lifecycle.
Identity¶
Every mode needs a secret key; the derived PK is the address peers pin:
-c /opt/skywire/skywire-config.json # preferred — same SK as the visor → same PK
--sk <64-hex> # explicit
export DMSGCURL_SK=<64-hex> # env (same shape as --sk)
No identity in standalone → a random ephemeral SK each start (PK changes every restart; useless for durable whitelists).
TCP-direct¶
Server side (accept inbound)¶
skywire app skychat \
--standalone \
--addr 127.0.0.1:8002 \
--tcp-listen :8801 \
--tcp-whitelist <peer-pk-1>,<peer-pk-2> \
-c /opt/skywire/skywire-config.json
--addr— local HTTP control surface (history, send, SSE). Keep it loopback unless you intend browser access (--addr "*:8002"+ reverse proxy +--password-file).--tcp-listen :8801— accept noise-XK on TCP; bidirectional once established.--tcp-whitelist— theauthorized_keys. Empty = open to any authenticated key (matches the skynet/CXO convention); set it to pin specific peers.
Peers dial in with the client's --via:
skywire cli skychat send \
--via tcp://<your-pk>@<your-public-ip>:8801 \
-c /their/skywire-config.json -w 8s -m "ping"
The <your-pk> in the URL must match the SK on the listening side — the
noise-XK handshake binds the connection to that PK.
Client side (dial out from behind NAT)¶
For a host that can't accept inbound, hold a persistent outbound link:
skywire app skychat \
--standalone --addr 127.0.0.1:8002 \
--tcp-peer tcp://<remote-pk>@<remote-host>:8801 \
-c /path/to/skywire-config.json
--tcp-peer is repeatable and auto-reconnects; this side needs no port
forward (it initiates the TCP).
Exposing the TCP port outside the LAN¶
--tcp-listen :8801 binds all interfaces; to accept from outside the LAN:
- Home router: forward external TCP
8801→<host-lan-ip>:8801. - VPS / cloud: open the provider firewall and the host firewall
(
ufw allow 8801/tcp,firewall-cmd --add-port=8801/tcp --permanent). - Mesh (Tailscale/WireGuard/ZeroTier): bind the mesh interface —
--tcp-listen 100.x.y.z:8801— and skip NAT forwarding.
CXO-backed (DM + groups, no dmsg)¶
CXO runs over its own native TCP transport — keeping the standalone dmsg-free and restart-stable. The tcp-direct channel above can keep running alongside for coordination; CXO carries the durable, replayable messaging.
Direct messages (P2P)¶
skywire app skychat \
--standalone --addr 127.0.0.1:8002 \
--cxo \
--cxo-listen :8802 \
--cxo-peer tcp://<peer-feedpk>@<peer-host>:8802 \
-c /opt/skywire/skywire-config.json
--cxo— publish your outbound to your CXO feed, subscribe to--cxo-peerfeeds. A reconnect watchdog re-dials the stored address and replays missed history on recovery.--cxo-listen :8802— where peers dial your feed (forward this port like the tcp-direct one).--cxo-peer tcp://<feedpk>@host:port— repeatable; who you subscribe to.
Federated groups¶
skywire app skychat \
--standalone --addr 127.0.0.1:8002 \
--cxo-group <group-id> \
--cxo-group-owner <owner-pk> \
--cxo-listen :8802 \
--cxo-peer tcp://<member-feedpk>@<host>:8802 \
-c /opt/skywire/skywire-config.json
--cxo-group <id>— enable federated group chat (roster, signing, gossip) over native TCP.--cxo-group-owner <pk>— the owner PK. Your role is owner if it equals your identity, else member.- Members come from
--cxo-peer. Drive the group from the client withcli skychat group …(skychat-visor.md).
Persisted history¶
Off by default; enable a local BoltDB:
--persist --persist-db /var/lib/skychat/history.db \
--persist-per-peer-cap 500 --persist-ttl 30 --persist-seed 50 \
--persist-whitelist /etc/skychat/persist-peers.txt
Per-peer FIFO cap, per-minute rate limit, total-size cap, TTL sweep, and a seed count that backfills new SSE clients.
Verifying¶
curl -s http://127.0.0.1:8002/status | jq # HTTP control surface up?
nc -zv <your-public-ip> 8801 # TCP listener reachable?
skywire cli skychat send --via tcp://<your-pk>@<your-public-ip>:8801 \
-c /peer/skywire-config.json -w 8s -m "ping" # full round-trip
Acked by <pk> in Xms (via tcp) confirms the handshake and receive
pipeline.
Remember the SSE-hub split: this standalone's
:8002hub is separate from a visor's:8001hub. Runcli skychat listenagainst the surface you actually want to read, or both. (See README.md.)
See also¶
- README.md — the model, all modes, ports, SSE-hub gotcha
- skychat-visor.md — visor-hosted app +
cli skychat skywire app skychat --help— full, current flag reference