A host management hub for fleets of Linux machines. Install the Platypus agent on every host you own; the agent dials back to your Platypus server over TLS + protobuf; from the server you get an interactive shell, file management, and network tunnelling on every managed host — through one central control plane.
A Vercel-style flat-nav UI with a project dashboard, a host/listener/session
browser, a multi-host dispatch console, and an admin user manager. See the
full gallery at docs/screenshots/. Re-run
make screenshots to regenerate them from the live app.
Platypus ships as two backend binaries plus a standalone desktop client:
| Binary | Role |
|---|---|
platypus-server |
Daemon. Accepts inbound agent connections on TLS ingress ports; exposes a REST + WebSocket API for admin tooling; serves agent binaries for distribution. |
platypus-agent |
The process that runs on each managed host. Dials back to the server over TLS + protobuf. |
platypus-desktop |
Native (Wails v2) desktop GUI. Connect to any reachable server with URL + secret; tabbed UI for sessions, terminals, listeners, files, tunnels. See desktop/. |
The server is purely an API — no embedded web UI. Multiple desktops can connect to the same server simultaneously.
- TLS + protobuf channel between agent and server
- Multiple ingress ports so fleets in different networks share one hub
- Interactive shell per host, streamed over WebSocket
- File read / write / upload / download with chunked transfer
- Network tunnels: local-port-forward, remote-port-forward, dynamic SOCKS5
- Bearer-token-authenticated REST API
- Python SDK
- Auto-start listeners from
config.yml - Graceful shutdown (drains connections on SIGINT/SIGTERM within 30s)
The fastest way to get a full Platypus system running (including the Server, Web UI, and a MinIO-backed Agent Distributor) is using Docker Compose:
git clone https://github.com/WangYihang/Platypus
cd Platypus
docker-compose up -d- Web UI: Open
https://localhost:9443in your browser (the container ships with a self-signed cert; your browser will prompt you to accept it on first visit). - Login: The
bootstrap_secretis written to/app/data/bootstrap.secretinside the container on first boot (mode 0600). Read it withdocker compose exec platypus-server cat /app/data/bootstrap.secret, use it once to create the first admin, then delete the file. After bootstrap completes the secret is no longer accepted. - Add Agents: Click "Add Agent" in the UI to get a one-line
curl ... | shcommand, signed and pinned to the project CA, to deploy agents to your fleet.
Requires Go 1.24+ and protoc (only if you regenerate protobuf code).
git clone https://github.com/WangYihang/Platypus
cd Platypus
make build # → ./build/{platypus-server,platypus-agent}Other useful targets: make test, make lint, make snapshot (cross-platform via goreleaser), make help.
Contributors should install the git hooks so gofmt / goimports / go vet /
golangci-lint run before each commit:
pip install pre-commit # or: pipx install pre-commit
make hooks # one-time: wires .git/hooks/pre-commit
make pre-commit # optional: run all hooks against every file nowRequires Node 22+, Wails CLI dependencies (wails doctor), and the platform's WebView libraries (webkit2gtk-4.1 on Linux, WebView2 on Windows, WKWebView on macOS).
make desktop-deps # one-time: install Wails CLI + pnpm packages
make desktop-build # → desktop/build/bin/platypus-desktop
make desktop-dev # hot-reload dev modeSame pages, same features (minus real-time event push), runs in any browser:
make web-ui # → desktop/frontend/dist-web/ (static bundle)
make web-ui-serve # preview at http://localhost:8080dist-web/ is fully static; drop it on GitHub Pages / S3 / nginx. Point it at any platypus-server via the login form.
Full notes in desktop/README.md.
Download the appropriate archive for your OS/arch from the Releases page, extract, and run.
docker build -t platypus-server .
docker run --rm -p 9443:9443 \
-e PLATYPUS_DEV=1 \
-v $(pwd)/config.yml:/config.yml \
platypus-serverPLATYPUS_DEV=1 enables the on-disk KEK fallback so a fresh start
"Just Works"; for production, drop that env var and set
PLATYPUS_CA_KEK (32 bytes hex; openssl rand -hex 32) instead so
the CA private-key encryption key is never written next to the
encrypted database.
./build/platypus-server # foreground; Ctrl-C for graceful shutdownFor production, run the server under systemd rather than backgrounding it manually.
- Single-instance only. The opaque-token verifier caches verified
tokens in a per-process LRU. Revocation (logout, password change,
AAT revoke, user delete) drops the entry on the same process
synchronously — but a multi-replica deployment has no cross-process
invalidation channel today, so a revoked token can keep working on
other replicas for up to the cache TTL (30s). Don't run more than
one
platypus-serveragainst the same DB until cross-process revocation lands. Vertical scale + an HA stand-by is the supported shape; horizontal load-balanced replicas are not. - PLATYPUS_CA_KEK in the environment. The CA private key is sealed
with AES-256-GCM against this 32-byte hex value. Without it set, the
server refuses to start unless
PLATYPUS_DEV=1is also present (which enables a dev-only on-disk fallback at<data-dir>/ca.kek— unsafe for production because the ciphertext and key end up on the same volume). - Desktop-class viewport, no mobile layout (yet). The Web UI targets ≥ 1280px viewports; the rail / sidebar / status-bar chrome takes ~300px, leaving the content body in the 80–100 character reading sweet spot. There is no responsive collapse for narrow windows today — sub-1024px viewports will show horizontal scrollers on tables. Tablet / phone support is on the backlog (P4 in the UX audit) but not blocking; admin work is desk work.
- Object-store credentials. When the distributor is enabled
(
distributor.store.endpointset), setPLATYPUS_DISTRIBUTOR_STORE_ACCESS_KEY_IDandPLATYPUS_DISTRIBUTOR_STORE_SECRET_ACCESS_KEYrather than baking them intoconfig.yml. The bundledconfig.docker.ymlships with the YAML fields blank for that reason.
- Server:
192.168.88.129- Unified TLS ingress (REST API + agent link + installer):
0.0.0.0:9443
- Unified TLS ingress (REST API + agent link + installer):
- Managed host:
192.168.88.130(runsplatypus-agent)
First, run ./platypus-server. A config.yml is generated from
assets/config.example.yml if none exists. Defaults are sensible.
To enrol a new managed host:
- Open the Web UI / desktop app, navigate to the project, click Add Agent.
- Pick the target OS / arch and TTL, click Generate.
- Copy the one-liner the UI displays and run it on the managed host.
The server-rendered one-liner looks like:
curl -fsSL https://<server>:9443/api/v1/install/<one-shot-token> | shThe single-use install token expires shortly after issuance and is burnt on the first successful hit. The script the server returns:
- embeds the project CA so the agent binary download is verified with
curl --cacert(no-k, no TOFU); - injects a single-use PAT so the agent can complete enrolment and obtain its mTLS client certificate;
- refuses to run if the server has no project CA configured, unless
the operator explicitly sets
PLATYPUS_INSECURE_DOWNLOAD=1.
Do not hand-write
curl http://...URLs against the legacy/agent/<host>:<port>path — that flow is removed. Always render the install command via the UI so it carries the trust anchor.
Open the desktop app (platypus-desktop) or the web UI (make web-ui-serve),
fill in the server URL and secret, and you're in. Tabs: Sessions (every
agent that's dialled in), Terminal, Files, Tunnels, Listeners.
- REST API
- Python SDK
This project exists thanks to all the people who contribute.
Thank you to all our backers! 🙏 [Become a backer]
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]
