Add the RustDesk server playbook
This commit is contained in:
1 parent
fc31504f57
commit
d248675643
7 files changed
+273
No files matched your search
@@ -0,0 +1,14 @@
|
||||
#################################################
|
||||
# RustDesk Server Playbook
|
||||
#################################################
|
||||
---
|
||||
- name: Deploy the RustDesk ID/relay server behind Caddy
|
||||
hosts: monoco
|
||||
become: true
|
||||
vars_files:
|
||||
- vars/default.yml
|
||||
|
||||
roles:
|
||||
- network
|
||||
- rustdesk
|
||||
- caddy
|
||||
@@ -0,0 +1,116 @@
|
||||
# RustDesk Server on Docker, behind Caddy
|
||||
|
||||
Deploys the self-hosted [RustDesk](https://rustdesk.com) server (OSS) on `monoco`: the
|
||||
ID/rendezvous server `hbbs` and the relay server `hbbr`, both supervised by s6 inside the
|
||||
official `rustdesk/rustdesk-server-s6` container, reachable only through Caddy on
|
||||
`rust.tanshu.com:443`. No RustDesk port is published on the host.
|
||||
|
||||
## How the traffic is carried
|
||||
|
||||
RustDesk's native transports on 21115/21116/21117 are a custom TCP/UDP protocol, not HTTP,
|
||||
so an HTTP reverse proxy cannot carry them. What *is* HTTP is the WebSocket transport that
|
||||
`hbbs` and `hbbr` also listen on (21118 and 21119), and clients that enable WebSocket use
|
||||
it for both rendezvous and relay. Caddy terminates TLS on 443 and forwards two paths into
|
||||
the container over the Docker network:
|
||||
|
||||
| Path | Upstream | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `/ws/id` | `rustdesk:21118` (hbbs) | ID registration and signalling |
|
||||
| `/ws/relay` | `rustdesk:21119` (hbbr) | Relayed sessions |
|
||||
|
||||
The code that decides this is in the client: `hbb_common::websocket::check_ws` maps a
|
||||
domain `host:21116` to `wss://host/ws/id` and `host:21117` to `wss://host/ws/relay`, and
|
||||
the path names are fixed there. Caddy sets `X-Real-IP`, which `hbbs`/`hbbr` trust to keep
|
||||
the real client address for rate limiting and logs (upstream warns that these ports must
|
||||
therefore never be exposed directly).
|
||||
|
||||
## Settings
|
||||
|
||||
- `host`: the public name, `rust.tanshu.com`. Both the Caddy site and the address `hbbs`
|
||||
advertises come from it.
|
||||
- `docker_container`, `image_name`, `image_version`: container name and the
|
||||
`rustdesk/rustdesk-server-s6` tag to run.
|
||||
- `data_location`: host directory bind-mounted to `/data` (`/var/lib/rustdesk/data`),
|
||||
holding the SQLite database and the server key pair.
|
||||
- `rustdesk_relay_server`: handed to clients by `hbbs` as the relay address. It keeps the
|
||||
public name, so clients rewrite it to `wss://rust.tanshu.com/ws/relay`.
|
||||
- `rustdesk_publish_raw_ports` / `rustdesk_raw_ports`: publish 21115/21116/21117 as well.
|
||||
Off by default, see "Clients that cannot use WebSocket" below.
|
||||
- `rustdesk_encrypted_only`: `ENCRYPTED_ONLY=1`, so `hbbr` validates the server key too
|
||||
instead of accepting unkeyed clients.
|
||||
- `rustdesk_always_use_relay`: `ALWAYS_USE_RELAY=Y`, forcing relayed sessions. WebSocket
|
||||
clients are relay-only anyway, so this only affects clients on the raw ports.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A DNS `A` record for `rust.tanshu.com` pointing at `monoco`; Caddy issues the certificate
|
||||
over ports 80/443.
|
||||
- The Caddy container already running on `monoco` (`caddy/playbook.yml`), with its Caddyfile
|
||||
at `/var/lib/caddy/conf/Caddyfile`.
|
||||
|
||||
## Running this Playbook
|
||||
|
||||
The inventory in `/etc/ansible/hosts` already lists `monoco`, which is what `hosts:`
|
||||
targets:
|
||||
|
||||
```command
|
||||
ansible-playbook playbook.yml
|
||||
```
|
||||
|
||||
The playbook creates the `rustdesk_net` network, attaches Caddy to it, starts the
|
||||
container, appends the site block to the Caddyfile, reloads Caddy, and prints the public
|
||||
key.
|
||||
|
||||
## Configuring the clients
|
||||
|
||||
On each client, open **Settings → Network → ID/Relay Server** and set:
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| ID Server | `rust.tanshu.com` |
|
||||
| API Server | `https://rust.tanshu.com` |
|
||||
| Key | the printed value, i.e. `/var/lib/rustdesk/data/id_ed25519.pub` on the host |
|
||||
|
||||
then enable **Settings → Network → Use WebSocket** (`allow-websocket=Y`).
|
||||
|
||||
The **API Server** field looks odd for an OSS deployment, which has no API, but it is what
|
||||
selects the TLS scheme: the client uses `wss://` only when the API server starts with
|
||||
`https`, and otherwise silently falls back to plaintext `ws://rust.tanshu.com/...` on port
|
||||
80. Because the client also skips its own client↔server key exchange whenever WebSocket is
|
||||
enabled — on the assumption that the transport is already encrypted — that fallback would
|
||||
leave the connection unprotected rather than merely failing.
|
||||
|
||||
## Trade-offs
|
||||
|
||||
- **Relayed sessions only.** With WebSocket enabled the client never hole-punches
|
||||
(`force_relay` is set, and `hbbs` skips the same-intranet path for WebSocket peers), so
|
||||
every session flows through `monoco`. Remote control works, but throughput is bounded by
|
||||
the server, and latency is higher than a direct connection.
|
||||
- **Upstream labels this a Pro feature.** The client's own tooltip says "RustDesk server
|
||||
OSS doesn't include this feature", even though the OSS binaries do listen on 21118/21119
|
||||
and the OSS docs describe proxying them. Treat the WSS path as needing a real test: if
|
||||
clients fail to register, the fallback is the raw ports below.
|
||||
- **The address has to be the domain.** `check_ws` only builds a TLS URL and the `/ws`
|
||||
paths for hostnames; an IP would become `ws://ip:21118` and bypass Caddy entirely.
|
||||
|
||||
### Clients that cannot use WebSocket
|
||||
|
||||
Clients without the WebSocket option (older builds, third-party clients, or anything that
|
||||
must connect directly) need the native ports. Set `rustdesk_publish_raw_ports: true` and
|
||||
re-run the playbook, then open 21115-21116 (TCP) plus 21116/udp and 21117 (TCP) on the host
|
||||
and in any cloud firewall:
|
||||
|
||||
```command
|
||||
ufw allow 21115:21117/tcp
|
||||
ufw allow 21116/udp
|
||||
```
|
||||
|
||||
WebSocket clients keep working at the same time; they ignore the raw ports.
|
||||
|
||||
## Notes
|
||||
|
||||
- **Logs.** `docker logs rustdesk` covers both services.
|
||||
- **Server key.** If `/var/lib/rustdesk/data/id_ed25519` is lost, every client has to be
|
||||
reconfigured with the new public key. Back it up.
|
||||
- **Upgrading.** Re-running the playbook pulls the tag in `image_version` and recreates the
|
||||
container; the key pair and database live in `data_location` and survive.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
# Handlers for Caddy role
|
||||
- name: Reload Caddy configuration
|
||||
ansible.builtin.command: "docker exec -w /etc/caddy {{ caddy_container }} caddy reload"
|
||||
listen: "Reload Caddy"
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
- name: Read current Caddyfile
|
||||
ansible.builtin.slurp:
|
||||
path: "{{ caddyfile_path }}"
|
||||
register: caddyfile_raw
|
||||
|
||||
- name: Decode Caddyfile content
|
||||
set_fact:
|
||||
caddyfile_content: "{{ caddyfile_raw['content'] | b64decode }}"
|
||||
|
||||
- name: Check if snippet already exists
|
||||
set_fact:
|
||||
snippet_present: "{{ snippet_block in caddyfile_content }}"
|
||||
|
||||
- name: Add snippet if missing
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ caddyfile_path }}"
|
||||
marker: "# {mark} Ansible managed Caddy snippet for {{ host }}"
|
||||
block: "{{ snippet_block }}"
|
||||
create: yes
|
||||
when: not snippet_present
|
||||
notify: "Reload Caddy"
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Caddy is the only thing that talks to hbbs/hbbr, so it joins the network the
|
||||
# RustDesk container lives on and can resolve it by container name.
|
||||
- name: "Ensure {{ docker_network }} Docker network exists"
|
||||
docker_network:
|
||||
name: "{{ docker_network }}"
|
||||
state: present
|
||||
connected:
|
||||
- "{{ caddy_container }}"
|
||||
appends: yes
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
# Tasks for the RustDesk server role
|
||||
- name: Pull RustDesk server image
|
||||
docker_image:
|
||||
name: "{{ image_name }}:{{ image_version }}"
|
||||
source: pull
|
||||
force_source: yes
|
||||
|
||||
- name: Ensure the RustDesk data directory exists
|
||||
file:
|
||||
path: "{{ data_location }}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
# One container runs both hbbs and hbbr under s6. Its WebSocket ports are left
|
||||
# unpublished: Caddy reaches them over the shared Docker network, so clients
|
||||
# only ever connect to 443.
|
||||
- name: Create the RustDesk server container
|
||||
docker_container:
|
||||
name: "{{ docker_container }}"
|
||||
image: "{{ image_name }}:{{ image_version }}"
|
||||
state: started
|
||||
restart_policy: "unless-stopped"
|
||||
env: "{{ rustdesk_env }}"
|
||||
published_ports: "{{ rustdesk_published_ports }}"
|
||||
volumes:
|
||||
- "{{ data_location }}:/data"
|
||||
networks:
|
||||
- name: "{{ docker_network }}"
|
||||
|
||||
- name: Wait for hbbs to write out its key pair
|
||||
ansible.builtin.wait_for:
|
||||
path: "{{ data_location }}/id_ed25519.pub"
|
||||
timeout: 60
|
||||
register: rustdesk_key_file
|
||||
failed_when: false
|
||||
|
||||
- name: Read the public key clients have to be configured with
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ data_location }}/id_ed25519.pub"
|
||||
register: rustdesk_public_key
|
||||
when: rustdesk_key_file is succeeded
|
||||
|
||||
- name: Show the connection details
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
RustDesk clients: ID Server {{ host }}, API Server https://{{ host }},
|
||||
enable "Use WebSocket", and use
|
||||
{{ rustdesk_public_key['content'] | b64decode | trim }} as the key.
|
||||
when: rustdesk_public_key is not skipped
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
docker_container: rustdesk
|
||||
image_name: "rustdesk/rustdesk-server-s6"
|
||||
image_version: "latest"
|
||||
|
||||
# Public name clients use, and the name Caddy serves. hbbs hands this out as the
|
||||
# relay address, and a WebSocket client rewrites a hostname to
|
||||
# wss://<host>/ws/relay (an IP would get neither TLS nor the path).
|
||||
host: rust.tanshu.com
|
||||
rustdesk_relay_server: "{{ host }}:21117"
|
||||
|
||||
host_directory: rustdesk
|
||||
data_location: "/var/lib/{{ host_directory }}/data"
|
||||
|
||||
docker_network: "{{ docker_container }}_net"
|
||||
caddy_container: caddy
|
||||
|
||||
# hbbs/hbbr also speak their own TCP/UDP protocol on 21115-21117, which is not
|
||||
# HTTP and so cannot go through Caddy. Leave this off to keep the host's only
|
||||
# open ports at 80/443: clients then have to enable WebSocket, and every session
|
||||
# is relayed. Turn it on for clients that do not use WebSocket.
|
||||
rustdesk_publish_raw_ports: false
|
||||
rustdesk_raw_ports:
|
||||
- "21115:21115"
|
||||
- "21116:21116"
|
||||
- "21116:21116/udp"
|
||||
- "21117:21117"
|
||||
|
||||
rustdesk_published_ports: "{{ rustdesk_raw_ports if rustdesk_publish_raw_ports | bool else [] }}"
|
||||
|
||||
# hbbs writes id_ed25519/id_ed25519.pub under /data on first start. Clients are
|
||||
# configured with the public key, and ENCRYPTED_ONLY makes hbbr check it too.
|
||||
rustdesk_encrypted_only: true
|
||||
|
||||
# "Y" pushes every session through the relay. WebSocket clients are relay-only
|
||||
# regardless, so this only matters for clients on the raw ports.
|
||||
rustdesk_always_use_relay: false
|
||||
|
||||
rustdesk_env:
|
||||
RELAY: "{{ rustdesk_relay_server }}"
|
||||
ENCRYPTED_ONLY: "{{ '1' if rustdesk_encrypted_only | bool else '0' }}"
|
||||
ALWAYS_USE_RELAY: "{{ 'Y' if rustdesk_always_use_relay | bool else 'N' }}"
|
||||
|
||||
# Caddy terminates TLS on 443 and carries the WebSocket transports: 21118 is
|
||||
# hbbs's WebSocket listener, 21119 hbbr's. Neither is published on the host.
|
||||
caddyfile_path: /var/lib/caddy/conf/Caddyfile
|
||||
snippet_block: |
|
||||
{{ host }} {
|
||||
reverse_proxy /ws/id* {{ docker_container }}:21118 {
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
|
||||
reverse_proxy /ws/relay* {{ docker_container }}:21119 {
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user