Skip to content

Repository files navigation

sweetdesk

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.

Agent: Rust Server & Web: TypeScript Web: React + Ant Design WebSocket relay

中文文档


What it does

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.

Architecture

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
Loading

Tech stack

Part Stack
Agent Rust (Windows DXGI, GDI fallback)
Server pnpm + Hono + ws
Web Vite + React + TypeScript + Ant Design

Getting started (development)

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-enroll

Open 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.

Building & installing the agent

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 --release

The 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.

Clean uninstall

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.

Configuration

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.

Deployment (single process)

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 start

For 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.

Required production variables

With SWEETDESK_ENV=production, startup is refused if any of these are still the dev default (or missing):

  • SWEETDESK_JWT_SECRET
  • SWEETDESK_RELAY_SECRET
  • SWEETDESK_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.

Upgrading legacy agents

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.

Repository layout

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

License

MIT (see Cargo.toml). A LICENSE file will be added before any public release.

About

Self-hosted remote desktop: a Rust Windows agent streams screen, keyboard, mouse and clipboard to a browser through a Hono relay server.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages