自建远程桌面服务RustDesk的搭建教程

简介

一款安全、快速的远程桌面软件RustDesk

主要的优点:

  • 安全性:RustDesk使用安全的加密通信协议来保护数据传输,确保远程桌面连接的安全性和隐私保护。
  • 跨平台支持:RustDesk支持跨多个操作系统平台,包括Windows、macOS和Linux、Android等,这使得它非常灵活和适用于不同的工作环境。

从名字可以看出,RustDesk是使用Rust所编写,而使用Rust编写的程序,两大特点就是高效和并发,实际使用一台入门(1C1G)EC2或者Lightsail(1C2G)给个人或者小型团队使用是没有问题的。

开源地址

部署前准备

开放端口

  • TCP (21115, 21116, 21117, 21118, 21119)
  • UDP (21116)

安装screen命令

安装wget命令

yum install wget -y #centos
apt install wget -y #ubuntu

安装unzip命令

yum install unzip -y #centos
apt install unzip -y #ubuntu

部署服务

新建存放目录并下载服务端

mkdir /opt/rustdesk #可以修改成自己的目录
wget https://github.com/rustdesk/rustdesk-server/releases/download/1.1.10-3/rustdesk-server-linux-amd64.zip #下载RustDesk最新服务端,这是目前最新服务端如果你是在很久之后看到此文章请到开源地址自行下载最新服务端

解压服务端

unzip rustdesk-server-linux-amd64.zip

运行服务端

cd /opt/rustdesk/amd64
screen -S hbbr
./hbbr -k _
ctrl键+a键+d键 #离开并挂起当前窗口,后台扔继续运行
screen -S hbbs
./hbbs -k _
ctrl键+a键+d键

开始使用

使用客户端连接服务端

查看这个目录,就可以发现,多了一些数据库文件和一个证书文件:

ls
RustDesk
├── db_v2.sqlite3
├── db_v2.sqlite3-shm
├── db_v2.sqlite3-wal
├── hbbr
├── hbbs
├── id_ed25519
├── id_ed25519.pub
└── rustdesk-utils

我们需要拷贝.pub这个公钥文件,用于接下来的客户端配置

cat id_ed25519.pub #把id_ed25519.pub改成你的公钥名称
公钥文件内容:C6CJn7*******************50sCF3y4= #复制这串公钥

下载客户端

安装之后打开RustDesk客户端进行网络配置

点击右上角的设置并填入相应配置

图片[1]-自建远程桌面服务RustDesk的搭建教程-铭心博客

完成配置后,回到主页,显示就绪

然后将被控端也安装客户端也进行当前网络配置后填入ID与密码即可使用

使用Systemctl命令运行服务端

如果你感觉使用上面的方法运行服务端麻烦的话你可以配置使用Systemctl命令运行服务端

hbbs:

[Unit]
Description=RustDesk Hbbs
After=network.target

[Service]
User=mintimate
Type=simple
WorkingDirectory=/home/mintimate/myApplication/RustDesk
ExecStart=/home/mintimate/myApplication/RustDesk/hbbs
ExecStop=/bin/kill -TERM $MAINPID

[Install]
WantedBy=multi-user.target

hbbr:

[Unit]
Description=RustDesk Hbbr
After=network.target

[Service]
User=mintimate
Type=simple
WorkingDirectory=/home/mintimate/myApplication/RustDesk
ExecStart=/home/mintimate/myApplication/RustDesk/hbbr
ExecStop=/bin/kill -TERM $MAINPID

[Install]
WantedBy=multi-user.target

docker部署教程

安装docker

见本站docker安装教程https://halo.blog360.sbs/archives/dockeran-zhuang-jiao-cheng

运行容器

docker 直接运行
# hbbs (id服务)
# -v:挂载文件目录。[本机目录]:[容器目录]
# --net=host:容器应用使用宿主机网络,不需要再配置docker的端口映射或者进行端口映射
# -r [服务器公网IP或域名]:对应当前服务器的地址,例如123.123.123.123,或域名mydomain.com
docker run --restart=always  --name hbbs  -v /opt/rustdesk:/root -td --net=host rustdesk/rustdesk-server  hbbs  -r [服务器公网IP或域名]  
​
# hbbr (中继服务)
docker run  --restart=always --name hbbr   -v /opt/rustdesk:/root -td  --net=host rustdesk/rustdesk-server  hbbr
docker compose运行
version: '3'
​
networks:
  rustdesk-net:
    external: false
​
services:
  hbbs:
    container_name: hbbs
    ports:
      - 21115:21115
      - 21116:21116
      - 21116:21116/udp
      - 21118:21118
    image: rustdesk/rustdesk-server:latest
    command: hbbs -r rustdesk.example.com:21117 # 公网ip或者域名,为中继连接节点
    volumes:
      - ./data:/root
    networks:
      - rustdesk-net
    depends_on:
      - hbbr
    restart: unless-stopped
​
  hbbr:
    container_name: hbbr
    ports:
      - 21117:21117
      - 21119:21119
    image: rustdesk/rustdesk-server:latest
    command: hbbr
    volumes:
      - ./data:/root
    networks:
      - rustdesk-net
    restart: unless-stopped

rustdesk-web-API-server 部署

首先我们为什么要部署rustdesk-API-server,我们需要指定rustdesk开源版本与pro版本的区别,其核心区别主要有下:

功能开源自托管版Pro 自托管版
Web 管理控制台❌ 无✅ 完整图形化界面(设备/用户/权限管理)
API 接口❌ 无✅ RESTful API(自动化集成)
审计日志❌ 仅基础连接日志✅ 完整操作审计(登录/会话/文件传输)
设备分组/标签❌ 手动管理✅ 可视化批量管理
企业级安全❌ 基础密码验证✅ TOTP 两步验证 (2FA)
会议功能❌ 仅一对一连接✅ 支持多人会议(≤10人)
安卓隐私黑屏❌ 无✅ 被控时屏幕自动黑屏
商业授权❌ AGPLv3 (有传染性)✅ 商业许可(规避开源风险)
官方技术支持❌ 仅社区支持✅ 订阅用户专属支持

对应企业而言,前五条还是比较重要的,而pro版本的订阅价格还比较贵,我们可以通过自行部署其他第三方rustdesk-web-API来解决,当然第三方肯定无法和官方的pro比较,如果权限要求比较细也可以购买pro版本支持一下

那么我们本次部署所使用的第三方rustdesk-web-API可以提供哪些功能呢?基本如下

功能详情
Web 管理控制台提供了一个完整图形化界面(设备/用户/权限管理)
API 接口RESTful API 并且提供了swagger接口,可以自行定制开发
设备分组/标签可视化批量管理
安全提升可强制要求登录后才允许使用远程

项目地址:https://github.com/lejianwen/rustdesk-api

安装docker

见本站docker安装教程

docker compose运行

添加docker-compose.yml文件

version: '3'  # 必须声明版本(推荐 ≥3.5)
​
services:
  rustdesk:
    image: lejianwen/rustdesk-server-s6:latest
    container_name: rustdesk-server
    ports:
      - "21114:21114"    # 注意:所有端口必须加引号(YAML规范)
      - "21115:21115"
      - "21116:21116"    # TCP端口
      - "21116:21116/udp" # UDP端口(无需重复定义TCP)
      - "21117:21117"
      - "21118:21118"
      - "21119:21119"
    environment: 
      - RELAY=rustdesk.domain.com:21117 # 默认的中继服务器
      - ENCRYPTED_ONLY=1
      - MUST_LOGIN=Y  # 是否强制登陆才运行使用
      - TZ=Asia/Shanghai # 时区
     # - RUSTDESK_API_GORM_TYPE=mysql
     # - RUSTDESK_API_MYSQL_USERNAME=root
     # - RUSTDESK_API_MYSQL_PASSWORD=123456
     # - RUSTDESK_API_MYSQL_ADDR=192.168.1.1
     # - RUSTDESK_API_MYSQL_DBNAME=rustdesk_db
      - RUSTDESK_API_APP_SHOW_SWAGGER=0 # 是否启用swagger 
      - RUSTDESK_API_RUSTDESK_KEY=1222112= #jwt key
      - RUSTDESK_API_RUSTDESK_API_SERVER=https://rustdesk.domain.com # 默认访问的api路径
      - RUSTDESK_API_RUSTDESK_RELAY_SERVER=rustdesk.domain.com:21117
      - RUSTDESK_API_RUSTDESK_ID_SERVER=rustdesk.domain.com:21116
    volumes:
      - ./data:/data
      - ./data/api:/app/data #将数据库挂载 sqlite 数据库文件挂载 为mysql时可以不配置
    restart: unless-stopped
    networks:
      - rustdesk-net
​
networks:  # 顶级配置(顶格书写)
  rustdesk-net:
    driver: bridge

完整参数配置

变量名说明示例
TZ时区Asia/Shanghai
RUSTDESK_API_LANG语言en,zh-CN
RUSTDESK_API_APP_WEB_CLIENT是否启用web-client; 1:启用,0:不启用; 默认启用1
RUSTDESK_API_APP_REGISTER是否开启注册; truefalse 默认falsefalse
RUSTDESK_API_APP_SHOW_SWAGGER是否可见swagger文档;1显示,0不显示,默认0不显示1
RUSTDESK_API_APP_TOKEN_EXPIREtoken有效时长168h
RUSTDESK_API_APP_DISABLE_PWD_LOGIN是否禁用密码登录; truefalse 默认falsefalse
RUSTDESK_API_APP_REGISTER_STATUS注册用户默认状态; 1 启用,2 禁用, 默认 11
RUSTDESK_API_APP_CAPTCHA_THRESHOLD验证码触发次数; -1 不启用, 0 一直启用, >0 登录错误次数后启用 ;默认 33
RUSTDESK_API_APP_BAN_THRESHOLD封禁IP触发次数; 0 不启用, >0 登录错误次数后封禁IP; 默认 00
—–ADMIN配置—–———-———-
RUSTDESK_API_ADMIN_TITLE后台标题RustDesk Api Admin
RUSTDESK_API_ADMIN_HELLO后台欢迎语,可以使用html
RUSTDESK_API_ADMIN_HELLO_FILE后台欢迎语文件,如果内容多,使用文件更方便。 会覆盖RUSTDESK_API_ADMIN_HELLO./conf/admin/hello.html
—–GIN配置—–———-———-
RUSTDESK_API_GIN_TRUST_PROXY信任的代理IP列表,以,分割,默认信任所有192.168.1.2,192.168.1.3
—–GORM配置—–———-—————————
RUSTDESK_API_GORM_TYPE数据库类型sqlite或者mysql,默认sqlitesqlite
RUSTDESK_API_GORM_MAX_IDLE_CONNS数据库最大空闲连接数10
RUSTDESK_API_GORM_MAX_OPEN_CONNS数据库最大打开连接数100
RUSTDESK_API_RUSTDESK_PERSONAL是否启用个人版API, 1:启用,0:不启用; 默认启用1
—–MYSQL配置—–———-———-
RUSTDESK_API_MYSQL_USERNAMEmysql用户名root
RUSTDESK_API_MYSQL_PASSWORDmysql密码111111
RUSTDESK_API_MYSQL_ADDRmysql地址192.168.1.66:3306
RUSTDESK_API_MYSQL_DBNAMEmysql数据库名rustdesk
—–RUSTDESK配置—–———-———-
RUSTDESK_API_RUSTDESK_ID_SERVERRustdesk的id服务器地址192.168.1.66:21116
RUSTDESK_API_RUSTDESK_RELAY_SERVERRustdesk的relay服务器地址192.168.1.66:21117
RUSTDESK_API_RUSTDESK_API_SERVERRustdesk的api服务器地址http://192.168.1.66:21114
RUSTDESK_API_RUSTDESK_KEYRustdesk的key123456789
RUSTDESK_API_RUSTDESK_KEY_FILERustdesk存放key的文件./conf/data/id_ed25519.pub
RUSTDESK_API_RUSTDESK_WEBCLIENT MAGICQUERYONLINEWeb client v2 中是否启用新的在线状态查询方法; 1:启用,0:不启用,默认不启用0
RUSTDESK_API_RUSTDESK_WS_HOST自定义Websocket Hostwss://192.168.1.123:1234
—-PROXY配置—–———-———-
RUSTDESK_API_PROXY_ENABLE是否启用代理:falsetruefalse
RUSTDESK_API_PROXY_HOST代理地址http://127.0.0.1:1080
—-JWT配置—-——–——–
RUSTDESK_API_JWT_KEY自定义JWT KEY,为空则不启用JWT 如果没使用lejianwen/rustdesk-server中的MUST_LOGIN,建议设置为空
RUSTDESK_API_JWT_EXPIRE_DURATIONJWT有效时间168h

查看密码

执行

docker logs -f docker容器ID

查看密码

总结

  1. 注意这个服务包已经包含了hbbr与hbbs,可以不用再部署了。
  2. 默认超管密码将会在容器日志中输出。
  3. 默认容器已经禁用了注册功能。

RustDesk 发布教程

在发布之前,我们需要了解rustdesk各个端口的作用:

了解端口,确认发布端口

hbbs 服务所需端口

协议端口号说明
TCP21114用于 Web 控制台(可选)
TCP21115用于 NAT 类型测试
TCP/UDP21116必须同时启用 TCP 和 UDP,用于 ID 注册、心跳服务(UDP)以及 TCP 打洞、连接服务(TCP)
TCP21118用于支持 Web 客户端(可选)

hbbr 服务所需端口

协议端口号说明
TCP21117用于中继服务
TCP21119用于支持 Web 客户端(可选)

那么可以根据一下需求来

RustDesk 官方开源版本

端口是否必须
21116 tcp/udp
21117 tcp建议开放,用于在p2p失败后中继
21115 tcp建议不开放

rustdesk-web-API-server 第三方带服务版本

端口是否必须
21116 tcp/udp
21114(443 or 80) tcp开放 将web 使用443或者80发布
21117 tcp建议开放,用于在p2p失败后中继
21115 tcp建议不开放
21119 tcp建议不开放
21118建议不开放

使用nginx代理发布端口

非web端口nginx stream 发布·

  1. 修改nignx配置文件,设置stream模块
stream {
    include /etc/nginx/stream.d/*;
}
# 注意与http模块同级别
  1. 添加stream 代理端口
    server {
        listen 21116;
        proxy_pass 172.30.0.2:21116;
        proxy_connect_timeout 5s;
    }
​
    # UDP端口代理(P2P打洞)
    server {
        listen 21116 udp reuseport;
        proxy_pass 172.30.0.2:21116;
        proxy_timeout 3s;
    }
​
    server {
        listen 21117;
        proxy_pass 172.30.0.2:21117;
    }

web端口发布

在/etc/nginx/sites-available添加新域名配置文件,vim 你的域名

server {
​
    listen 80;
    server_name test.blog360.sbs;
    #    return 404; # managed by Certbot
​
     location / {
                client_max_body_size 50m;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://172.30.0.2:21114/; #后端 真实服务器
     
                index index.html index.htm A1-index.html;
​
  }
​
​
}

建立软连接

ln -s /etc/nginx/sites-available/你的域名 /etc/nginx/sites-enabled/你的域名

web端口申请免费证书(ssl证书可选)

安装Certbot工具

# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx
​
# CentOS/RHEL
sudo yum install epel-release
sudo yum install certbot python3-certbot-nginx

申请ssl证书(nginx)

sudo certbot --nginx -d your-domain.com
  • 替换 your-domain.com 为你的域名

总结

1. 注意stream 与http同级。

2.发布尽量使用标准端口,若rustdesk-web-API-server使用非标端口注意启动容器配置

pro版本或者rustdesk-web-API-server版本

swagger使用

在配置启用swagger后,打开

  1. 后台文档 /admin/swagger/index.html
  2. PC端文档 /swagger/index.html

即可进入对应swagger文档

图片[2]-自建远程桌面服务RustDesk的搭建教程-铭心博客

用户初始化

未开放注册,若用户初始用户过多,可以通过调用api方式添加用户,这里提供一个Java 同步启用微信用户的方法

/**
 * @program: fd-system
 * @description:
 * @author: Mr.ChangHao
 * @create: 2025-07-09 11:33
 **/
​
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
​
import java.util.HashMap;
import java.util.Map;
​
public class WeComUserSync {
​
    // 企业微信API参数(需替换实际值)
    public static final String QY_HOST = "企业微信url路径";
    public static final String QY_CORP_ID = "企业id";
    public static final String QY_CORP_SECRET = "企业微信密钥";
​
    public static final String RUST_DESK_HOST = "http://192.168.73.129:21114/api";
    public static final String RUST_DESK_TOKEN = "430b712ba7ef99da742c9c15bc5ae4c0";
​
​
    public static void syncUsers() {
        String weComAccessToken = getWeComAccessToken();
        // 1. 获取企业微信所有用户
        JSONArray wecomUsers = getWeComUsers(weComAccessToken);
​
        // 2. 转换数据并调用创建接口
        for (Object user : wecomUsers) {
            JSONObject wecomUser = (JSONObject) user;
            JSONObject targetUser = convertToTargetFormat(wecomUser);
            createUser(targetUser);
        }
    }
​
    public static String getWeComAccessToken() {
        // 构建请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("corpid", QY_CORP_ID);
        params.put("corpsecret", QY_CORP_SECRET);
​
        // 发送GET请求
        HttpResponse response = HttpRequest.get(QY_HOST +"/cgi-bin/gettoken")
                .form(params)
                .execute();
​
        // 解析响应
        JSONObject result = JSONUtil.parseObj(response.body());
​
        // 检查错误码
        if (result.getInt("errcode") != 0) {
            throw new RuntimeException("获取access_token失败: " + result.getStr("errmsg"));
        }
​
        return result.getStr("access_token");
    }
​
​
    public static JSONArray getWeComUsers(String weComAccessToken) {
​
        String departmentId = "1"; // 根部门ID 注意权限范围
        String fetchChild = "1";   // 递归获取子部门
​
        // 构建请求URL
        String url = String.format(QY_HOST +"/cgi-bin/user/list?access_token=%s&department_id=%s&fetch_child=%s",
                weComAccessToken, departmentId, fetchChild);
​
​
        // 发送GET请求
        HttpResponse response = HttpRequest.get(url).execute();
        String body = response.body();
        JSONObject result = JSONUtil.parseObj(body);
​
        // 检查响应状态
        if (result.getInt("errcode") != 0) {
            throw new RuntimeException("企业微信API调用失败: " + result.getStr("errmsg"));
        }
​
        return result.getJSONArray("userlist");
    }
​
    public static JSONObject convertToTargetFormat(JSONObject wecomUser) {
        // 企业微信字段到目标字段的映射
        Map<String, Object> map = new HashMap<>();
        map.put("group_id", 2);                             // 默认分组ID
        map.put("is_admin", false);             // 管理员标识
        map.put("nickname", wecomUser.getStr("name"));       // 昵称
        map.put("status", 1); // 状态转换
        map.put("username", wecomUser.getStr("userid"));     // 用户名
​
        return JSONUtil.parseObj(map);
    }
​
​
    public static void createUser(JSONObject userData) {
​
​
        HttpResponse response = HttpRequest.post(RUST_DESK_HOST +"/admin/user/create")
                .body(userData.toString())   // JSON请求体
                .header("api-token", RUST_DESK_TOKEN)
                .contentType("application/json")
                .execute();
​
        // 检查响应状态(根据实际接口规范调整)
        if (response.getStatus() != 200) {
            System.out.println("用户创建失败: HTTP " + response.getStatus());
        }
​
        JSONObject result = JSONUtil.parseObj(response.body());
        System.out.println("添加响应结果:"+result);
    }
​
​
    public static void updateAllUsersPassword() {
        int page = 1;
        int pageSize = 100; // 每页大小,可根据实际情况调整
        int totalPages = 1; // 初始值,会在循环中更新
​
        while (page <= totalPages) {
            // 1. 分页获取用户
            JSONObject userListResponse = getUserList(page, pageSize);
​
            // 检查响应是否成功
            if (userListResponse == null || userListResponse.getInt("code") != 0) {
                System.err.println("获取用户列表失败,页码: " + page);
                page++;
                continue;
            }
​
            JSONObject data = userListResponse.getJSONObject("data");
            JSONArray users = data.getJSONArray("list");
​
            // 更新总页数
            totalPages = (int) Math.ceil((double) data.getInt("total") / pageSize);
​
            // 2. 遍历用户并更新密码
            for (Object userObj : users) {
                JSONObject user = (JSONObject) userObj;
                updateUserPassword(user);
            }
​
            page++;
        }
    }
​
    public  static JSONObject getUserList(int page, int pageSize) {
        String url = RUST_DESK_HOST + "/admin/user/list"; // 假设用户列表接口路径是基础URL
​
        try {
            HttpResponse response = HttpRequest.get(url)
                    .header("api-token", RUST_DESK_TOKEN)
                    .form("page", page)
                    .form("page_size", pageSize)
                    .execute();
​
            String body = response.body();
​
            return JSONUtil.parseObj(body);
        } catch (Exception e) {
            System.err.println("获取用户列表异常: " + e.getMessage());
            return null;
        }
    }
​
    public static void updateUserPassword(JSONObject user) {
        int userId = user.getInt("id");
        String username = user.getStr("username");
        String newPassword = username + "!@#123"; // 生成密码
​
        String url = RUST_DESK_HOST + "/admin/user/changePwd"; // 假设修改密码接口路径
​
        try {
            if (userId >1) {
                JSONObject requestBody = new JSONObject();
                requestBody.set("id", userId);
                requestBody.set("password", newPassword);
​
                HttpResponse response = HttpRequest.post(url)
                        .body(requestBody.toString())
                        .header("api-token", RUST_DESK_TOKEN)
                        .contentType("application/json")
                        .execute();
​
                JSONObject result = JSONUtil.parseObj(response.body());
​
                if (result.getInt("code") == 0) {
                    System.out.println("用户密码更新成功: " + username);
                } else {
                    System.err.println("用户密码更新失败: " + username +
                            " | 错误信息: " + result.getStr("message"));
                }
            }
        } catch (Exception e) {
            System.err.println("更新密码异常: " + username + " | " + e.getMessage());
        }
    }
​
    public static void main(String[] args) {
//        syncUsers(); 同步企业微信用户
        updateAllUsersPassword(); //批量初始化用户密码
    }
}

Oauth2接入(GitHub)演示

添加对应Oauth2配置

图片[3]-自建远程桌面服务RustDesk的搭建教程-铭心博客
  • 对于Google 和 GithubIssuer 和 Scopes不需要填写.
  • 对于OIDCIssuer是必须的。Scopes是可选的,默认为 openid,profile,email. 确保可以获取 sub,email 和preferred_username
  • github oauth appSettings->Developer settings->OAuth Apps->New OAuth App 中创建,地址 https://github.com/settings/developers callback URL 填写{uri}/api/oauth/callback
  • Authorization callback URL填写http:///api/oidc/callback ,比如http://127.0.0.1:21114/api/oidc/callback

前往GitHub oauth app配置 https://github.com/settings/developers

图片[4]-自建远程桌面服务RustDesk的搭建教程-铭心博客

rustdesk 页面配置

图片[5]-自建远程桌面服务RustDesk的搭建教程-铭心博客

这样是存在bug

图片[6]-自建远程桌面服务RustDesk的搭建教程-铭心博客
curl --location --request POST 'http://192.168.73.129:21114/api/admin/oauth/create' \
--header 'api-token: 60aca81100daccf883c66f32b45d7b72' \
--header 'Content-Type: application/json' \
--data '{
    "id": 0,
    "op": "",
    "oauth_type": "github",
    "issuer": "",
    "client_id": "2221",
    "client_secret": "1123",
    "redirect_url": "",
    "scopes": "",
    "auto_register": true,
    "pkce_enable": false,
    "pkce_method": "S256"
}'

完成后页面展示

图片[7]-自建远程桌面服务RustDesk的搭建教程-铭心博客
图片[8]-自建远程桌面服务RustDesk的搭建教程-铭心博客

第三方接入与OIDC 接入

OIDC 算比较前沿的,国内支持厂商并不多,如果内部业务系统支持可以对接,或者查询所使用的系统看有没有对接的可能,或者直接简单开发对接api实现自定义Oauth2登录

其他权限管理

这里的话各位可以自行探索

© 版权声明
THE END
喜欢就支持一下吧!
点赞200 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容