







[](https://github.com/sponsors/UNITRONIX)
[](https://buymeacoffee.com/unitronix)
**A clean-room RustDesk-compatible server written in Go โ single binary replacing hbbs + hbbr โ with full protocol support, TLS everywhere, PostgreSQL backend, CDAP (Custom Device API Protocol) for IoT/SCADA/network devices, and a modern Node.js web management console.**
[Architecture](#-architecture) โข [Installation](#-installation) โข [Configuration](#-configuration) โข [Security](#-security-architecture) โข [API](#-api-reference) โข [Troubleshooting](#-troubleshooting)
### ๐ Honorary Supporter
BetterDesk is proudly supported by **[INSOLVE](https://insolve.pl)** โ recognized as an *Honorary Supporter* of the project. See [SPONSORS.md](SPONSORS.md) for the full list of supporters and how to become one.
---
## โ
End-to-End Encryption โ Fully Working
> **E2E encryption between RustDesk clients is fully functional.** Both P2P (punch-hole) and relay sessions establish NaCl-encrypted channels with proper `SignedId` + `PublicKey` handshake. The green lock indicator appears in the RustDesk client for all connection modes.
>
> Additionally, you can enable **TLS on relay ports** (`--tls-relay` / `TLS_RELAY=Y`) for an extra transport-level encryption layer on top of the E2E channel.
---
## ๐ Table of Contents
- [Overview](#-overview)
- [Architecture](#-architecture)
- [BetterDesk Go Server](#-betterdesk-go-server)
- [Protocol Implementation](#protocol-implementation)
- [Cryptography](#cryptography)
- [Database Backends](#database-backends)
- [TLS Support](#tls-support)
- [Rate Limiting & DDoS Protection](#rate-limiting--ddos-protection)
- [Enrollment & Device Tokens](#enrollment--device-tokens)
- [Web Console (Node.js)](#-web-console-nodejs)
- [Security Architecture](#-security-architecture)
- [Installation](#-installation)
- [Linux](#linux)
- [Windows](#windows)
- [Docker](#docker)
- [RustDesk Client Configuration](#-rustdesk-client-configuration)
- [Desktop Client Login](#desktop-client-login)
- [Enabling Pro Features](#enabling-pro-features)
- [TLS / SSL Certificates](#-tls--ssl-certificates)
- [Configuration Reference](#-configuration-reference)
- [API Reference](#-api-reference)
- [Migration Guide](#-migration-guide)
- [Monitoring & Metrics](#-monitoring--metrics)
- [Troubleshooting](#-troubleshooting)
- [E2E Encryption](#-e2e-encryption)
- [Chat E2E Encryption](#-chat-e2e-encryption)
- [Unattended Access & Wake-on-LAN](#-unattended-access--wake-on-lan)
- [Technology Stack](#-technology-stack)
- [Contributing](#-contributing)
- [License](#-license)
---
## ๐ Overview
**BetterDesk** is a complete RustDesk infrastructure solution consisting of two main components:
1. **BetterDesk Server** โ A clean-room Go implementation that replaces both `hbbs` (signal) and `hbbr` (relay) with a **single binary**. It implements the full RustDesk wire protocol, including UDP/TCP/WebSocket signal, TCP/WebSocket relay, NaCl secure handshake, and a comprehensive HTTP REST API.
2. **BetterDesk Console** โ A Node.js (Express.js) web management panel with device monitoring, TOTP 2FA, RBAC, address book sync, and RustDesk Client API.
### Why BetterDesk?
| Feature | Original RustDesk Server | BetterDesk Server |
|---------|------------------------|-------------------|
| **Binaries** | 2 (hbbs + hbbr) | **1 single binary** |
| **Language** | Rust | Go (pure Go, no CGO) |
| **Database** | SQLite only | **SQLite + PostgreSQL** |
| **TLS** | Not built-in | **TLS everywhere** (auto-detect plain/TLS on same port) |
| **API** | Minimal | **Full REST API** with JWT + API keys |
| **Status tracking** | Binary (online/offline) | **4-tier** (Online/Degraded/Critical/Offline) |
| **Rate limiting** | None | **IP, bandwidth, connection** limits |
| **Metrics** | None | **Prometheus** exposition format |
| **Admin console** | None | **TCP admin** (telnet/netcat) |
| **Device enrollment** | Open only | **Open/Managed/Locked** modes |
| **Audit trail** | None | **Ring-buffer + JSON file** audit logging |
| **Hot reload** | Restart required | **SIGHUP** config reload |
| **Multi-instance** | File-based only | **PostgreSQL LISTEN/NOTIFY** for real-time sync |
| **Web panel** | None | **Full Node.js console** with TOTP 2FA |
| **User roles** | Single admin | **4-tier RBAC** (Admin, Operator, Viewer, Pro) |
| **Chat** | None | **E2E encrypted** operatorโdevice chat |
| **Wake-on-LAN** | None | **Built-in WOL** via magic packet |
| **Access policies** | None | **Unattended access** schedules + operator restrictions |
> **๐ค AI-Assisted Development**: This project was developed with significant assistance from AI coding tools (Claude/GitHub Copilot). All code has been reviewed, tested, and validated for production use.
---
## ๐๏ธ Architecture
```
RustDesk Desktop/Mobile Clients
โ
โโโ UDP (:21116) โโโโโโโบ Signal Server โโโบ RegisterPeer, PunchHole, RequestRelay
โโโ TCP (:21116) โโโโโโโบ Signal Server โโโบ NaCl Secure Handshake โ encrypted channel
โโโ TCP (:21115) โโโโโโโบ NAT Test โโโบ TestNatRequest, OnlineRequest, ConfigUpdate
โโโ WS (:21118) โโโโโโโบ WS Signal โโโบ WebSocket-based signal (web clients)
โ
โโโ TCP (:21117) โโโโโโโบ Relay Server โโโบ UUID pairing โ bidirectional io.Copy pipe
โโโ WS (:21119) โโโโโโโบ WS Relay โโโบ WebSocket relay (web clients)
โ
โโโ HTTP (:21114) โโโโโโบ Go HTTP API โโโบ Login, AB, heartbeat, REST (default)
โโโ HTTP (:21121) โโโโโโบ Node proxy โโโบ same handlers on Go (backward compat)
Admin / Web Console
โ
โโโ (internal) โโโโโโโโโบ Go :21114 โโโบ Panel via BETTERDESK_API_URL
โโโ WS via panel โโโโโโโบ Event stream โโโบ Real-time peer status
โโโ TCP (admin) โโโโโบ Admin Console โโโบ telnet/netcat management (127.0.0.1 only)
โโโ HTTP (:5000) โโโโโบ Web Console โโโบ Node.js + Express + EJS (LAN)
TOTP 2FA, RBAC, device management
Database Layer
โ
โโโ SQLite โโโบ Pure Go (modernc.org/sqlite), WAL mode, single binary
โโโ PostgreSQL โโโบ pgx/v5 + pgxpool, LISTEN/NOTIFY, row-level locking
```
### Port Map
| Port | Protocol | Service | Direction |
|------|----------|---------|-----------|
| **21115** | TCP | NAT type test + OnlineRequest | WAN |
| **21116** | TCP + UDP | Signal Server (registration, punch hole) | WAN |
| **21117** | TCP | Relay Server (bidirectional stream) | WAN |
| **21118** | WS(S) | WebSocket Signal | WAN |
| **21119** | WS(S) | WebSocket Relay | WAN |
| **21114** | HTTP | Go server API โ default (`-api-port`), direct RustDesk + REST | WAN |
| **21121** | HTTP | Node backward-compat proxy โ Go :21114 | WAN |
| **5000** | HTTP | Web Console (admin panel) | LAN |
> All TCP/WS ports support **dual-mode TLS** โ plain and TLS on the same port with automatic detection.
---
## ๐ง BetterDesk Go Server
The Go server (`betterdesk-server/`) is a ~20,000 LOC clean-room implementation of the RustDesk signal and relay protocol. It compiles to a **single static binary** with no external dependencies (pure Go, no CGO required).
### Server Modes
```bash
# Default: run everything (signal + relay + API + admin)
./betterdesk-server -mode all
# Signal only (no relay)
./betterdesk-server -mode signal
# Relay only
./betterdesk-server -mode relay
```
### Protocol Implementation
The server implements the complete RustDesk rendezvous/relay protocol:
#### Signal Protocol (UDP/TCP/WS)
| Message | Direction | Description |
|---------|-----------|-------------|
| `RegisterPeer` | Client โ Server | Heartbeat registration (~12s interval), updates in-memory peer map |
| `RegisterPeerResponse` | Server โ Client | Suggested heartbeat interval, request online status |
| `RegisterPk` | Client โ Server | Public key registration with UUID consistency check |
| `RegisterPkResponse` | Server โ Client | Registration result |
| `PunchHoleRequest` | Client A โ Server | Request hole-punch to Client B |
| `PunchHole` | Server โ Client B | Forward punch request with Client A's address |
| `PunchHoleSent` | Client B โ Server | Confirm punch sent |
| `PunchHoleResponse` | Server โ Client A | Relay response with Client B's NAT info |
| `RequestRelay` | Client โ Server | Request relay session |
| `RelayResponse` | Server โ Clients | Relay server address + UUID for pairing |
| `FetchLocalAddr` / `LocalAddr` | Bidirectional | LAN address exchange for direct connections |
| `TestNatRequest` | Client โ Server (:21115) | NAT type detection |
| `TestNatResponse` | Server โ Client | NAT type + `ConfigUpdate` (relay/rendezvous servers) |
| `OnlineRequest` | Client โ Server (:21115) | Bulk online status query (bitmask response) |
#### Relay Protocol (TCP/WS)
The relay is a **pure opaque byte pipe**:
1. Client A connects, sends `RequestRelay{uuid: "..."}` โ registered as pending
2. Client B connects with the same UUID โ pair found
3. Bidirectional `io.Copy` begins immediately (relay is a pure opaque byte pipe)
4. Timeouts: 30s pairing, 30s idle (extended on activity via `idleTimeoutConn`)
The relay does **not** parse, inspect, or modify traffic between paired clients. E2E encryption is entirely between the two RustDesk clients.
#### Wire Protocol (codec/)
Matches `hbb_common::bytes_codec::BytesCodec`:
- **TCP**: Variable-length framing โ bottom 2 bits of header = header length - 1, remaining bits = payload length (little-endian). Supports 1-4 byte headers (max frame: 64 KB)
- **UDP**: Raw protobuf (no framing)
- **WebSocket**: Raw protobuf per binary WS frame
### Cryptography
#### Server-Client Secure Handshake (NaCl)
```
Server Client
โ โ
โ KeyExchange{keys: [Ed25519_sign(Cv_pub)]} โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ โ
โ KeyExchange{keys: [client_cv_pub, โ
โ nacl_box(symmetric_key)]} โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโ All traffic encrypted with NaCl secretbox โโโ
```
1. Server generates Ed25519 keypair โ derives Curve25519 public key โ signs with Ed25519
2. Client verifies signature โ generates its own Curve25519 keypair
3. Client encrypts a random symmetric key using NaCl box (Curve25519 DH)
4. All subsequent messages use NaCl secretbox with sequential nonces
5. **Backward compatible**: Auto-detects old (plain) vs new (encrypted) clients
#### Key Storage
- **Private key**: `id_ed25519` file (auto-generated if missing)
- **Public key**: `id_ed25519.pub` (base64-encoded, same format as original RustDesk)
- **Key format**: Standard Ed25519 (32-byte seed), compatible with existing RustDesk clients
#### Password Hashing
- **Algorithm**: PBKDF2-HMAC-SHA256
- **Iterations**: 100,000
- **Salt**: 16-byte cryptographically random
- **Comparison**: Constant-time (`subtle.ConstantTimeCompare`)
### Database Backends
#### SQLite (Default)
```bash
./betterdesk-server -db ./db_v2.sqlite3
```
- **Driver**: `modernc.org/sqlite` โ pure Go, no CGO required
- **Journal mode**: WAL (Write-Ahead Logging) for concurrent reads
- **Foreign keys**: Enabled by default
- **Connection limit**: 1 (SQLite single-writer constraint)
- **Write serialization**: `sync.RWMutex` around all write operations
- **Tables**: `peers`, `server_config`, `id_change_history`, `users`, `api_keys`, `device_tokens`
#### PostgreSQL
```bash
./betterdesk-server -db "postgres://user:password@localhost:5432/betterdesk?sslmode=disable"
```
- **Driver**: `pgx/v5` with `pgxpool` connection pooling
- **Pool size**: Configurable via `pool_max_conns` DSN parameter (default: 10)
- **Native types**: `BOOLEAN`, `BYTEA`, `TIMESTAMPTZ`, `BIGSERIAL`
- **Row-level locking**: `SELECT ... FOR UPDATE` in `ChangePeerID` (replaces SQLite global mutex)
- **LISTEN/NOTIFY**: Real-time cross-instance event push for multi-server deployments
- **Partial indexes**: `WHERE banned = TRUE`, `WHERE peer_id != ''` for performance
- **Auto-detection**: DSN starting with `postgres://` or `postgresql://` automatically selects PostgreSQL
#### Schema Overview
```sql
-- Core tables (both backends)
peers -- Device records (21 fields: id, uuid, pk, ip, note, status, etc.)
server_config -- Key-value configuration store
id_change_history -- Device ID change audit trail
users -- Admin/operator/viewer accounts (PBKDF2 + TOTP)
api_keys -- API key management (SHA256 hash storage)
device_tokens -- Enrollment tokens (Dual Key System)
```
### TLS Support
BetterDesk supports TLS on all transport layers with a unique **dual-mode auto-detection** system:
```bash
# Enable TLS on signal ports (21116 TCP + 21115 + 21118 WSS)
./betterdesk-server -tls-signal -tls-cert server.crt -tls-key server.key
# Enable TLS on relay ports (21117 TCP + 21119 WSS)
./betterdesk-server -tls-relay -tls-cert server.crt -tls-key server.key
# Enable TLS on everything
./betterdesk-server -tls-signal -tls-relay -tls-cert server.crt -tls-key server.key
# HTTPS on API
./betterdesk-server -tls-cert server.crt -tls-key server.key -force-https
```
#### DualModeListener (config/tls.go)
Accepts **both plain and TLS connections on the same port**:
1. Peeks first byte of incoming connection
2. If `0x16` (TLS ClientHello) โ upgrades to `tls.Server()`
3. Otherwise โ passes through as plain TCP via `peekedConn`
This means existing RustDesk clients (no TLS) continue to work alongside TLS-enabled clients without port changes. Minimum TLS version: **TLS 1.2**.
### Rate Limiting & DDoS Protection
| Layer | Type | Default | Configurable |
|-------|------|---------|-------------|
| **IP rate limiter** | Sliding window per-IP | 20 registrations/min | Via constants |
| **Login rate limiter** | Per-IP | 5 attempts / 5 min | Via constants |
| **Bandwidth limiter** | Token bucket | 1 GB/s global, 16 MB/s per-session | Via constants |
| **Connection limiter** | Per-IP concurrent | 20 relay connections/IP | `-relay-max-conns-ip` |
| **TCP punch cache** | TTL + max size | 2 min TTL, 10K max entries | Via constants |
| **WebSocket origins** | Whitelist | Accept all | `WS_ALLOWED_ORIGINS` env |
| **Relay idle timeout** | Per-session | 30s (extended on activity) | Via constants |
| **Relay pair timeout** | Pending sessions | 30s | Via constants |
### Enrollment & Device Tokens
The **Dual Key System** controls which devices can register with the server:
| Mode | Behavior |
|------|----------|
| `open` (default) | Accept all device registrations |
| `managed` | New devices need admin approval OR a valid enrollment token |
| `locked` | Only devices with pre-issued valid enrollment tokens can register |
```bash
# Set enrollment mode
./betterdesk-server -mode all
# Then via API: PUT /api/enrollment/mode {"mode": "managed"}
```
Device tokens have statuses: `pending`, `active`, `revoked`, `expired`. Bulk generation is supported via `POST /api/tokens/generate-bulk`.
### 4-Tier Device Status System
```
ONLINE โ Last heartbeat < 15s ago
DEGRADED โ 2 consecutive missed heartbeats (15-30s)
CRITICAL โ 4+ missed heartbeats (30-60s)
OFFLINE โ Beyond RegTimeout (30s) with no heartbeat
```
- **Heartbeat interval**: Clients send `RegisterPeer` every ~12s
- **Cleaner goroutine**: Runs every 3s, checks all peers, publishes status transition events
- **Debounced DB sync**: Memory โ database sync every 60s (avoids write storms)
### Admin TCP Console
A lightweight management interface accessible via `telnet` or `netcat`:
```bash
./betterdesk-server -admin-port 9090 -admin-password "secret"
# Then: telnet 127.0.0.1 9090
```
**Commands**: `status`, `peers [count|info