Self-hosted remote desktop. A lightweight Rust agent streams a Windows desktop to your browser through a Hono relay server — no third-party service, no account, you own the whole stack.
Log in from a browser, download the Windows agent, and control a remote PC in real time.
- Browser viewer — login, device list, and a live canvas stream.
- Windows agent (Rust) — DXGI screen capture with GDI fallback, dirty-rectangle JPEG encoding.
- Full control — keyboard & mouse injection (
SendInput) plus clipboard text sync. - Logon-screen support — runs as a Windows service (SYSTEM) and spawns a helper in the user session; falls back to the winlogon token when nobody is logged in.
- Per-device consent — "ask before connecting", with accept / decline / timeout.
- Self-hosted & safe by default — production mode refuses to start if the default credentials or secrets are still in place.
Three logical components. Both the browser and the agent initiate connections to the server (the agent needs no inbound port). Traffic splits into a control plane (enrollment, sessions, device push) and a data plane (screen / input / clipboard). After authenticating the relay session, the server forwards data-plane messages without parsing or transcoding the binary frames. In interactive mode the Agent runs the session worker in-process; in service mode the SYSTEM control process spawns that worker as a helper on the active user or logon desktop.
flowchart LR
subgraph Web["Browser (apps/web · React + AntD)"]
Viewer["Login / devices / canvas"]
end
subgraph Server["Server (apps/server · Hono + ws)"]
Http["HTTP API"]
Static["Static web assets"]
WsUser["WS /user · device control"]
WsAgent["WS /agent · agent control"]
Relay["WS /relay/:id · opaque data pipe"]
end
subgraph Agent["Windows Agent (crates/agent · Rust)"]
Ctrl["Control client<br/>interactive process or SYSTEM service"]
Session["Session worker<br/>relay + capture + input + clipboard"]
end
Static -->|"production HTML / JS / CSS"| Viewer
Viewer -->|"HTTP /api"| Http
Viewer <-->|"device updates / heartbeat"| WsUser
Ctrl <-->|"outbound control WS<br/>enrollment / session.open / heartbeat"| WsAgent
Http -.->|"create session / sendAgent"| WsAgent
Ctrl -->|"interactive: in-process<br/>service: spawn helper"| Session
Viewer <-->|"role=browser<br/>screen / input / clipboard"| Relay
Session <-->|"outbound WS role=agent<br/>screen / input / clipboard"| Relay
| Part | Stack |
|---|---|
| Agent | Rust (Windows DXGI, GDI fallback) |
| Server | pnpm + Hono + ws |
| Web | Vite + React + TypeScript + Ant Design |
Three terminals, from the repo root:
# terminal 1 & 2 — server + web
pnpm install
pnpm dev# terminal 3 — a local agent streaming a test pattern
cargo run -p sweetdesk-agent -- --server ws://127.0.0.1:8787 --token dev-enrollOpen http://127.0.0.1:5173 (bypass any local HTTP proxy; e.g. Clash needs 127.0.0.1 excluded).
- Username
admin - Password
sweetdesk
Once a device shows online, hit Connect. Mouse and keyboard are injected into the remote machine; pasting in the viewer goes through the clipboard.
Viewing the same machine you're on will "nest" (the capture grabs the browser too) — that's normal for full-screen capture. Connect from a second device to avoid it.
Log in, then use Download Windows Agent in the top-right of the device page. The downloaded sweetdesk-agent.exe has the server address and a one-time enrollment ticket baked in. Run it within 30 minutes (configurable with SWEETDESK_ENROLL_TICKET_TTL); download a fresh copy after the ticket expires.
Before that, build the agent binary once in the repo:
cargo build -p sweetdesk-agent --releaseThe server prefers target/release/sweetdesk-agent.exe, falling back to the debug build. For a public deployment, set SWEETDESK_PUBLIC_URL=https://your-domain and the downloaded exe will connect over WSS automatically.
Double-click the exe:
- Yes — install as a Windows service (needs admin, auto-starts on boot; a per-session helper does the actual capture).
- No — run once in the current session.
- Cancel — exit.
Command line:
sweetdesk-agent.exe --install
sweetdesk-agent.exe --uninstall
--uninstall only removes the Windows service. For a clean wipe, use the script below. Passing --server / --token on the command line skips the install dialog.
Right-click and run as administrator:
scripts/uninstall-agent.bat
It re-prompts via UAC when not elevated. The real work lives in scripts/uninstall-agent.ps1. It removes:
| Type | What |
|---|---|
| Process | sweetdesk-agent.exe |
| Service | SweetDeskAgent (stopped, then deleted) |
| Install dir | C:\Program Files\SweetDesk and SweetDesk under ProgramData / AppData |
| User dirs | sweetdesk-agent.exe / .json / .log on each user's desktop, downloads, documents |
| Registry | HKLM\SYSTEM\...\Services\SweetDeskAgent, event-log source, SOFTWARE\SweetDesk, Run entry |
| Other | scheduled tasks, firewall rules, prefetch entries matching SweetDesk |
It never touches the repo source and never does a full-disk scan. If a service registry key is still held, reboot once.
See .env.example. Development defaults:
| Item | Value |
|---|---|
| Web | http://127.0.0.1:5173 |
| Server | http://127.0.0.1:8787 |
| Agent URL | ws://127.0.0.1:8787 |
| Development enroll token | dev-enroll |
| Tenant | default |
Pass --fake to the agent to stream a test pattern instead of a real desktop.
In production the server also serves the built web app, so HTTP and WebSocket share one port in a single process:
# 1. build the web app (produces apps/web/dist)
pnpm --filter @sweetdesk/web build
# 2. build the agent binary (served for download)
cargo build -p sweetdesk-agent --release
# 3. start with production config (defaults are rejected)
SWEETDESK_ENV=production \
SWEETDESK_JWT_SECRET=<long-random-string> \
SWEETDESK_RELAY_SECRET=<different-long-random-string> \
SWEETDESK_ADMIN_PASSWORD=<strong-password> \
SWEETDESK_PUBLIC_URL=https://your-domain \
SWEETDESK_DATA_DIR=/var/lib/sweetdesk \
pnpm --filter @sweetdesk/server startFor HTTPS/WSS, point SWEETDESK_TLS_CERT / SWEETDESK_TLS_KEY at your cert and key (same port). Without them it serves HTTP — put Nginx / Caddy / Cloudflare in front and make sure the /agent, /user, and /relay/* WebSocket upgrades are forwarded.
The device registry (offline devices stay visible and configurable) is persisted at SWEETDESK_DATA_DIR/devices.json.
With SWEETDESK_ENV=production, startup is refused if any of these are still the dev default (or missing):
SWEETDESK_JWT_SECRETSWEETDESK_RELAY_SECRETSWEETDESK_ADMIN_PASSWORD
Optional: SWEETDESK_PORT, SWEETDESK_TENANT, SWEETDESK_CONSENT_TIMEOUT (seconds, default 30), SWEETDESK_ENROLL_TICKET_TTL (seconds, default 1800), SWEETDESK_WEB_ORIGIN (comma-separated CORS allowlist), SWEETDESK_WEB_DIST (default apps/web/dist), SWEETDESK_AGENT_EXE, SWEETDESK_AGENT_URL.
Set SWEETDESK_TRUST_PROXY=true only when the server is behind a trusted reverse proxy that removes or overwrites client-supplied X-Real-IP and X-Forwarded-For headers. Otherwise login rate limiting always uses the direct socket address.
Agents built before per-device credentials do not persist the device key. During migration only, set SWEETDESK_ALLOW_LEGACY_ENROLL=true and set SWEETDESK_ENROLL_TOKEN to the old non-default token. This permits that token to reconnect existing, unconfirmed device records only; it cannot register new devices. Replace each old agent with a freshly downloaded binary, then disable legacy enrollment and remove the old token.
apps/server Hono control plane + WebSocket relay + agent download
apps/web Login / device list / canvas viewer
packages/protocol Control JSON + binary frames (the source is the spec)
crates/agent Rust agent
scripts/smoke.mjs Protocol smoke test
scripts/uninstall-agent.bat Clean uninstall (double-click, elevates)
scripts/uninstall-agent.ps1 Uninstall logic
MIT (see Cargo.toml). A LICENSE file will be added before any public release.