wanctl controls devices across the public internet from a terminal or an AI agent's shell. It carries commands and files through an end-to-end-encrypted relay, while each device keeps final authority through a local approval policy.
- End-to-end mutual TLS 1.3 with Ed25519 identities and explicit fingerprint pinning.
- Device-side policy for command, file, log, and elevated operations, including human approval on rule misses.
- Friend relationships and per-device sharing with bounded exec/read/write capabilities.
- GitHub OAuth login with first-user administration and invite-based admission.
- One Go binary for relay, portal, agent, controller, and MCP roles.
- Proxy-agnostic HTTP long-poll transport that works through ordinary reverse proxies; WebSocket remains optional.
- CLI and MCP surfaces designed for scripted and AI-agent-driven control.
For a persistent public deployment, follow the self-hosting guide. It starts Postgres, the relay, and the portal with Docker Compose and covers GitHub OAuth, HTTPS termination, admission, and device enrollment.
To try the encrypted relay path locally without Postgres or OAuth, run the local smoke test.
A freshly installed binary does not yet know which instance it talks to. The
first command that needs a relay asks, on a terminal, which one to use: the
project's hosted instance at https://wanctl.z10.dev — invite-only, so sign in
with GitHub there and ask for access — or a relay you run yourself. The answer
is saved through the same machinery as wanctl config set relay=... portal=...
and the command then carries on.
Nothing unattended is ever blocked by that question. It is skipped when a relay
is already configured (a --relay flag, WANCTL_RELAY, the config file, or a
value baked into the build), when stdin or stdout is not a terminal, and when
WANCTL_NO_PROMPT=1 is set; those runs get an error that names both doors and
the exact command for each instead.
Every release ships signed binaries for the whole matrix below; the one-line
installers (install.sh, install.ps1) detect the OS and CPU and fetch the
matching one, and wanctl update does the same from inside the binary.
| OS | 64-bit | 32-bit |
|---|---|---|
| Linux | amd64, arm64 | 386, arm (armv6/v7), mips, mipsle (softfloat, routers) |
| macOS | amd64, arm64 (Apple silicon) | — (macOS has run no 32-bit code since Catalina) |
| Windows | amd64, arm64 | 386 |
| Android | arm64, amd64 — binary + APK each | arm (armeabi-v7a), 386 — binary + APK each |
The list lives in scripts/release-targets.sh, which the release build, the
publisher's checks and CI's cross-compile gate all read.
A running wanctl agent keeps itself current. It checks the same signed
release manifest wanctl update uses — a minute after it starts, then every six
hours — and when a newer release is on offer it verifies the signature,
swaps the binary and restarts itself in place, keeping its pid so systemd,
launchd and wanctl status all still point at it. It waits while a shell
session, a background job or a console session is open, and it never tries to
elevate: a binary in a root-owned directory such as /usr/local/bin logs that
sudo wanctl update is needed and leaves itself alone. Development builds are
never replaced. Turn it off with wanctl config set auto_update=off (or
WANCTL_AUTO_UPDATE=off); wanctl status shows which way it is set.
controller (you/agent) --+ +-- device (wanctl agent)
wanctl exec/push/logs | relay (public broker) | policy engine + approval
+-- byte-pipe + registry ---+ JSONL event log
| token auth + ACL + audit|
E2E mutual-TLS ========+======== over the pipe ====+==== (relay sees only ciphertext)
| |
portal (web, SSO) -----+ issues tokens, ACL -----+ (thin proxy to relay /admin/*)
The relay authenticates tokens and authorizes connections but cannot decrypt a session. Controllers and devices establish mutual TLS over the relayed byte pipe, then devices independently enforce local policy. The portal has no database of its own; it authenticates users and scopes calls to the relay's Postgres-backed admin API. See Architecture for the trust model, transports, sharing rules, and component map.
wanctl requires the Go release named by the go directive in go.mod (currently 1.26.6); the Go tool downloads it automatically.
go build -o wanctl .
go test ./...The same binary selects its role by subcommand:
wanctl relay --addr :8080
wanctl portal --addr :8080
wanctl agent --relay https://relay.example.com --token TOKEN --name DEVICE
wanctl peers # your online devices, plus any shared with you
wanctl exec --target DEVICE "uname -a"- Self-hosting
- Environment variables
- Architecture and security model
- Android
- Release signing
- Security audit
Licensed under the Apache License 2.0. See NOTICE for attribution information.