自建 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 的设备管理其实比表面上简单,但需要理解数据库的真实含义。

几个关键点可以概括为:

  1. RustDesk OSS 的客户端记录主要保存在 db_v2.sqlite3peer 表中。
  2. peer.id 是 RustDesk ID,并具有唯一性。
  3. 数据库默认不会保存客户端 hostname。
  4. created_at 是数据库记录建立时间,不是最后在线时间。
  5. info 中通常可以看到客户端 IP。
  6. RustDesk ID 由客户端持有并向服务器提交。
  7. 从服务器删除 peer 不会删除客户端的 RustDesk ID。
  8. 如果被删除的客户端仍然活跃,它还会重新注册回来。
  9. 重新创建 peer 后,created_at 会变成新的登记时间,因此很容易识别。
  10. WAL 模式下不要随意删除 -wal-shm 文件。
  11. 修改数据库前最好停止 hbbs 并通过 SQLite .backup 制作一致性备份。
  12. 删除 peer 与轮换服务器 Ed25519 密钥是两个完全不同级别的操作。
  13. 日常管理最适合建立一个放在 /usr/local/sbin/ 的只读系统命令。
  14. 查看与删除功能最好分离,减少误操作风险。

对于规模不大的自建 RustDesk Server,这套方式已经足以解决“服务器到底登记过哪些设备、哪些仍然存在、哪些历史记录可以清理”这一类长期维护问题,而不需要为了简单的设备审计额外引入复杂的管理系统。

Leave a Reply

Your email address will not be published. Required fields are marked *