自建 RustDesk Server 运行一段时间后,很容易出现一个实际问题:
服务器已经连接过不少客户端,但究竟登记过多少设备?哪些设备仍在使用?哪些只是已经废弃的历史记录?
RustDesk Server OSS 并没有像 Pro 版本那样提供完整的设备管理控制台,因此很多信息需要直接从服务端数据库进行审计。
本文以 Docker 部署的 RustDesk Server OSS 为例,介绍如何安全查看设备记录、理解数据库字段、识别旧设备、删除废弃记录,以及如何建立一个长期可用的系统管理命令。
一、先确认 RustDesk Server 的部署方式
RustDesk Server OSS 主要包含两个服务:
hbbs:ID、注册、信令等核心服务hbbr:中继服务
如果采用 Docker 部署,可以先检查:
ps -ef | grep -E '[h]bbs|[h]bbr'
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' \
| grep -Ei 'rustdesk|hbbs|hbbr'
常见部署会看到两个独立容器:
hbbs rustdesk/rustdesk-server:latest
hbbr rustdesk/rustdesk-server:latest
然后检查数据目录挂载:
docker inspect hbbs \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
典型结果类似:
/srv/rustdesk/data -> /root
这意味着 RustDesk 的数据库、服务器密钥等持久化数据实际上都保存在宿主机目录:
/srv/rustdesk/data
二、RustDesk OSS 的设备记录保存在哪里
RustDesk Server OSS 默认使用 SQLite 数据库:
db_v2.sqlite3
数据目录中通常还能看到:
db_v2.sqlite3
db_v2.sqlite3-wal
db_v2.sqlite3-shm
id_ed25519
id_ed25519.pub
这里有一个值得注意的细节。
如果存在:
db_v2.sqlite3-wal
db_v2.sqlite3-shm
说明 SQLite 正在使用 WAL 模式。
因此不能仅根据:
db_v2.sqlite3
主文件的修改时间判断数据库是否还在使用。
大量最新事务可能仍然保存在 WAL 文件中。
也不要在服务运行期间:
rm db_v2.sqlite3-wal
rm db_v2.sqlite3-shm
否则可能破坏数据库一致性。
正常查询时使用:
sqlite3 -readonly /srv/rustdesk/data/db_v2.sqlite3
SQLite 会自动把主数据库和 WAL 中的当前状态一起读取。
三、查看 RustDesk 数据库结构
首先查看数据库有哪些表:
sqlite3 -readonly /srv/rustdesk/data/db_v2.sqlite3 '.tables'
RustDesk Server OSS 的核心设备表通常是:
peer
查看结构:
sqlite3 -readonly /srv/rustdesk/data/db_v2.sqlite3 \
'.schema peer'
常见字段包括:
guid
id
uuid
pk
created_at
user
status
note
info
其中比较重要的是:
id
RustDesk ID。
服务器对这一字段建立唯一索引,因此数据库中的每一条记录对应一个唯一 RustDesk ID。
uuid
客户端提交的 UUID 信息。
pk
客户端公钥。
created_at
这条 peer 记录第一次进入数据库时的时间。
需要特别注意:
created_at不是最后在线时间。
它只能说明这条数据库记录是什么时候创建的。
info
通常保存 JSON 信息,例如:
{"ip":"::ffff:192.0.2.10"}
主要可用于查看客户端注册时的 IP 地址。
note
数据库中存在备注字段。
OSS 本身没有提供完善的设备管理 UI 来利用这个字段,但从数据库设计上看,它可以用于保存管理员自己的设备备注。
四、统计服务器历史登记过多少设备
最简单的查询:
DB='/srv/rustdesk/data/db_v2.sqlite3'
sqlite3 -readonly "$DB" \
'SELECT COUNT(*) AS total_peers FROM peer;'
这得到的是:
当前数据库中保存了多少个唯一 RustDesk ID。
再列出详细记录:
sqlite3 -readonly -header -column "$DB" "
SELECT
id,
created_at,
info
FROM peer
ORDER BY created_at DESC;
"
可以得到类似:
id created_at info
---------- ------------------- ---------------------------
123456789 2026-09-01 03:21:15 {"ip":"::ffff:192.0.2.10"}
234567890 2026-08-11 11:42:50 {"ip":"::ffff:198.51.100.8"}
345678901 2026-07-03 07:25:12 {"ip":"::ffff:203.0.113.20"}
五、为什么数据库里没有主机名
很多人第一次查看 peer 表时会期待看到:
hostname
computer_name
device_name
但 RustDesk Server OSS 默认并不保存这些信息。
数据库中通常只有:
RustDesk ID
UUID
客户端公钥
首次登记时间
IP
而没有:
Windows-PC
Laptop-01
Server-A
这样的系统 hostname。
因此仅从服务端数据库往往无法直接判断:
某个 RustDesk ID 究竟是哪台电脑。
比较实用的方法是,在仍然掌控的客户端上查看自己的 RustDesk ID,然后建立对应关系,例如:
123456789 → workstation-a
234567890 → laptop-b
345678901 → server-c
如果需要长期维护,也可以考虑使用数据库中的 note 字段记录管理员定义的设备名称。
但如果希望保持 RustDesk 原始数据库尽可能不被人工修改,也可以在外部单独维护映射表。
六、IP 地址为什么有些看起来很奇怪
RustDesk 数据库中可能出现:
::ffff:192.0.2.10
这不是异常。
它属于 IPv4-mapped IPv6 address。
可以简单理解成:
::ffff:192.0.2.10
对应:
192.0.2.10
为了查看方便,可以查询时直接去掉前缀:
replace(
json_extract(info, '$.ip'),
'::ffff:',
''
)
例如:
sqlite3 -readonly -header -column "$DB" "
SELECT
id AS ID,
created_at AS CREATED_AT,
replace(json_extract(info, '\$.ip'), '::ffff:', '') AS IP
FROM peer
ORDER BY created_at DESC;
"
七、created_at 为什么不能用于判断设备是否还活着
这是 RustDesk OSS 设备审计中最容易产生误判的地方。
假设某条记录:
created_at = 2026-01-01
并不能说明:
这台设备从 1 月以后就没有再连接。
它完全可能每天都在连接。
原因在于 RustDesk 的运行时会维护客户端最近注册状态,但 OSS 数据库并没有把“最后在线时间”持续写入 peer.created_at。
客户端再次正常注册时,已有 peer 记录通常只是更新相关内容,例如 IP、公钥等,而不会重新创建数据库记录。
因此:
created_at
只能解释为:
当前这条 peer 数据库记录的建立时间。
不能解释为:
last seen
也不能直接解释为:
last connected
八、删除旧 peer 记录会发生什么
这是一个非常有用的 RustDesk OSS 行为。
如果确定某个 RustDesk ID 已经废弃,可以从数据库删除对应 peer。
但必须理解:
删除服务器中的 peer 记录,不等于删除客户端自己的 RustDesk ID。
RustDesk ID 是客户端持有并在注册时提交给服务器的。
因此流程是:
客户端持有 RustDesk ID
↓
向 hbbs 注册
↓
服务器检查数据库
↓
如果不存在
↓
重新插入 peer
这意味着:
如果删除的是一台真正已经废弃的设备:
删除 peer
↓
设备永远不再连接
↓
记录不会回来
如果删除的是一台实际上仍在运行的设备:
删除 peer
↓
客户端再次向 hbbs 注册
↓
服务器重新建立 peer
↓
记录重新出现
这个特性反而可以用于筛选未知历史设备。
九、重新出现以后哪些内容会变化
如果一条 peer 被删除后,同一个客户端重新注册:
RustDesk ID
通常不会变化。
因为 ID 是客户端继续提交的原 ID。
created_at
会变成新记录重新插入数据库时的时间。
例如原来:
ID created_at
123456789 2026-03-01
删除以后,客户端在 9 月重新注册:
ID created_at
123456789 2026-09-20
因此如果按照:
ORDER BY created_at DESC
排序,这台重新出现的设备会直接跑到列表最前面。
IP
会更新成客户端这次注册时服务器观察到的地址。
如果网络已经变化,IP 也可能完全不同。
guid
重新创建数据库 peer 时,内部记录标识也可能重新生成。
因此,“删除旧 peer,然后观察哪些 ID 又回来”是一种相当直观的设备清理办法。
十、安全删除旧设备记录
因为数据库可能处于 WAL 模式,建议不要在 hbbs 正在持续写入时直接做人工删除。
更稳妥的流程是:
停止 hbbs
↓
SQLite 一致性备份
↓
删除指定 peer
↓
数据库完整性检查
↓
启动 hbbs
1. 停止 hbbs
docker stop hbbs
通常没有必要同时停止中继服务器。
2. 使用 SQLite 自己做备份
不要只复制一个:
db_v2.sqlite3
因为 WAL 中可能仍有尚未 checkpoint 的事务。
更安全的方法是:
DB='/srv/rustdesk/data/db_v2.sqlite3'
STAMP="$(date '+%Y%m%d-%H%M%S')"
sqlite3 "$DB" ".backup '${DB}.${STAMP}'"
得到:
db_v2.sqlite3.20260919-091500
这是一个一致性的 SQLite 数据库副本。
3. 删除之前再次确认目标
例如准备删除几个旧 ID:
sqlite3 -header -column "$DB" "
SELECT
id,
created_at,
info
FROM peer
WHERE id IN (
'<DEVICE_ID_1>',
'<DEVICE_ID_2>',
'<DEVICE_ID_3>'
);
"
确认无误后再处理。
4. 使用事务删除
sqlite3 "$DB" "
BEGIN IMMEDIATE;
DELETE FROM peer
WHERE id IN (
'<DEVICE_ID_1>',
'<DEVICE_ID_2>',
'<DEVICE_ID_3>'
);
COMMIT;
"
5. 验证数据库
sqlite3 "$DB" 'PRAGMA quick_check;'
正常应该返回:
ok
然后检查剩余记录:
sqlite3 -header -column "$DB" "
SELECT
id,
created_at,
info
FROM peer
ORDER BY created_at DESC;
"
最后启动服务:
docker start hbbs
十一、为什么“先删再观察”比直接猜哪些设备废弃更可靠
如果服务器运行时间较长,又没有提前维护设备清单,仅仅依据:
注册日期
公网 IP
局域网 IP
并不能完全确认设备身份。
此时比较实用的处理方法是:
已明确确认的设备
↓
保留
完全无法确认的历史设备
↓
删除 peer
继续运行服务器
↓
观察一段时间
重新出现
↓
说明客户端仍然存在
长期不再出现
↓
很可能已经彻底废弃
这种方法利用了 RustDesk 客户端会主动重新注册的特性。
相比依据旧时间戳直接做判断,可靠性更高。
十二、服务器密钥轮换是另一种完全不同的操作
RustDesk 数据目录中通常还有:
id_ed25519
id_ed25519.pub
它们代表服务器自身的 Ed25519 身份。
其中:
id_ed25519
是私钥。
id_ed25519.pub
是客户端配置自建 RustDesk Server 时使用的服务器公钥。
如果完整轮换这对服务器密钥:
旧客户端保存旧公钥
↓
服务器更换新密钥
↓
客户端无法再按照原配置正常信任服务器
此时必须把新的服务器公钥重新配置到允许继续使用的客户端。
因此:
删除 peer
属于:
清理服务器设备登记记录。
客户端仍知道正确服务器 Key 时,可以重新注册回来。
轮换服务器密钥
属于:
整体重置客户端对服务器身份的信任。
所有仍配置旧公钥的客户端都会受到影响。
两者安全强度完全不同。
如果只是清理历史垃圾记录,没有必要为了这个目的轮换服务器密钥。
十三、不要手工修改 Ed25519 密钥内容
需要特别注意:
不要打开:
id_ed25519
然后随便修改其中几个字符。
Ed25519 私钥与公钥必须是一一对应的完整密钥对。
任意修改其中一部分可能导致:
私钥损坏
公私钥不匹配
RustDesk Server 无法正常使用密钥
如果将来确实需要轮换服务器身份,应当完整生成新的密钥对,而不是修改旧密钥字符串。
十四、建立一个系统级只读管理命令
如果经常需要检查设备情况,每次重新输入 SQLite 查询会比较麻烦。
可以在:
/usr/local/sbin/
建立一个系统管理员使用的自定义命令。
这里比:
/usr/bin
/usr/sbin
更合适,因为 /usr/local/sbin 本来就是供本机管理员安装自定义系统管理工具的标准位置之一。
需要注意的是,脚本本身完全可以使用任意自定义名称。
例如:
/usr/local/sbin/<custom-command>
其功能保持只读即可。
示例:
#!/usr/bin/env bash
set -euo pipefail
DB='/srv/rustdesk/data/db_v2.sqlite3'
if [[ ! -r "$DB" ]]; then
printf 'Error: RustDesk database is not readable: %s\n' "$DB" >&2
exit 1
fi
if ! command -v sqlite3 >/dev/null 2>&1; then
printf 'Error: sqlite3 is not installed.\n' >&2
exit 1
fi
total="$(sqlite3 -readonly "$DB" \
'SELECT COUNT(*) FROM peer;')"
printf 'RustDesk registered peers: %s\n\n' "$total"
sqlite3 -readonly -header -column "$DB" "
SELECT
id AS ID,
datetime(created_at, '+9 hours') AS FIRST_SEEN_LOCAL,
replace(json_extract(info, '\$.ip'), '::ffff:', '') AS IP,
coalesce(note, '') AS NOTE
FROM peer
ORDER BY created_at DESC;
"
授权:
chmod 0755 /usr/local/sbin/<custom-command>
以后执行:
<custom-command>
即可看到:
RustDesk registered peers: N
ID FIRST_SEEN_LOCAL IP NOTE
---------- ------------------- -------------- ------------
123456789 2026-09-18 22:21:23 192.0.2.10
234567890 2026-08-20 07:27:09 198.51.100.8
...
需要强调:
FIRST_SEEN_LOCAL
依旧只是 peer 当前数据库记录的创建时间。
它不是:
LAST_SEEN
因此最好在命令输出中避免直接把它命名为“最后在线时间”。
十五、查看命令和删除命令最好分开
系统管理工具设计上还有一个值得保留的原则:
查看与修改尽量分离。
例如:
设备查看工具
始终只读。
如果以后确实需要开发:
设备删除工具
则另外建立一个独立管理命令。
这样可以避免在日常检查设备时,因为参数错误或误输入而执行数据库删除。
对于这种只有几十 KB、记录数量有限的服务器数据库而言,“命令少一点、权限明确一点、行为可预测一点”通常比做一个复杂的一体化管理脚本更安全。
十六、一个更合理的 RustDesk OSS 长期管理模型
对于个人或小规模自建 RustDesk Server,可以把设备管理分成三层。
第一层:RustDesk Server 原始数据库
保留 RustDesk 自己需要的数据:
ID
UUID
公钥
注册记录
IP
尽量少做人为修改。
第二层:管理员设备映射
记录:
RustDesk ID
设备名称
用途
设备负责人
是否仍在使用
如果设备规模很小,可以利用 note 字段。
如果希望完全不动 RustDesk 数据库,也可以维护独立配置文件。
第三层:定期清理
例如偶尔执行:
查看所有 ID
↓
确认已知设备
↓
删除明显废弃记录
↓
观察是否重新注册
这样即使 OSS 没有完整设备管理控制台,也可以维持一个非常干净的服务器状态。
十七、结论
RustDesk Server OSS 的设备管理其实比表面上简单,但需要理解数据库的真实含义。
几个关键点可以概括为:
- RustDesk OSS 的客户端记录主要保存在
db_v2.sqlite3的peer表中。 peer.id是 RustDesk ID,并具有唯一性。- 数据库默认不会保存客户端 hostname。
created_at是数据库记录建立时间,不是最后在线时间。info中通常可以看到客户端 IP。- RustDesk ID 由客户端持有并向服务器提交。
- 从服务器删除 peer 不会删除客户端的 RustDesk ID。
- 如果被删除的客户端仍然活跃,它还会重新注册回来。
- 重新创建 peer 后,
created_at会变成新的登记时间,因此很容易识别。 - WAL 模式下不要随意删除
-wal或-shm文件。 - 修改数据库前最好停止
hbbs并通过 SQLite.backup制作一致性备份。 - 删除 peer 与轮换服务器 Ed25519 密钥是两个完全不同级别的操作。
- 日常管理最适合建立一个放在
/usr/local/sbin/的只读系统命令。 - 查看与删除功能最好分离,减少误操作风险。
对于规模不大的自建 RustDesk Server,这套方式已经足以解决“服务器到底登记过哪些设备、哪些仍然存在、哪些历史记录可以清理”这一类长期维护问题,而不需要为了简单的设备审计额外引入复杂的管理系统。