--- date: 2026-04-28 type: migration tags: [rustdesk, lxc116, npm, lejianwen, must-login, isolation] --- # RustDesk migration: OSS hbbs/hbbr → lejianwen-pro ## Цель Закрыть дыру: техник Клиента-А не должен подключаться к машинам Клиента-Б, даже зная ID и пароль. Это задача `MUST_LOGIN=Y` на уровне `hbbs`. ## Что было - LXC 116 `rustdeskserver` — Debian 12, 10.0.0.244 / 100.70.191.161 (NetBird) - Установлено через ProxmoxVE community-script (нативно, не Docker): - `hbbs` 1.1.14 — **OSS** Purslane (поддерживает только `--key`, `-r`, `--rendezvous-servers`, `ALWAYS_USE_RELAY`) - `hbbr` 1.1.14 — OSS - `rustdesk-api` 2.7 — **lejianwen** (это уже не OSS, но без настроек: lang=zh-CN, jwt пустой, домен 192.168.1.66 мусорный) - Конфиг `/var/lib/rustdesk-api/conf/config.yaml` — почти дефолтный, никто не правил - БД `rustdeskapi.db` 352K с 1 admin user + 13 peers (внутренние LAN) - NPM Proxy Host 14 для `remot.dttb.ru` смотрел на **мёртвый 10.0.0.43:21114**, streams 30-35 на **мёртвый 10.0.0.44**, SSL force/HTTP/2/block_exploits всё off - `https://remot.dttb.ru/_admin/` снаружи: **502 Bad Gateway** (никто никогда не пользовался web-админкой через домен) - Peers всё равно работали: внутренние через LAN 10.0.0.244, внешние видимо через NetBird mesh / DNAT на роутере минуя NPM ## Найденная развилка OSS `hbbs` не понимает `MUST_LOGIN`. Этот патч есть только в `lejianwen/rustdesk-server-pro` (тот же бинарь, что в docker-образе `lejianwen/rustdesk-server-s6`). Без него изоляция работает только на уровне `rustdesk-api` (адресные книги по группам), но любой клиент с правильным ключом всё равно может ходить по ID минуя API. ## Варианты | Вариант | Действие | Плюс | Минус | |---|---|---|---| | A1 | Подменить только бинари hbbs/hbbr на pro, остальное оставить | Минимум потрясений, ключ/БД/api/NPM streams не трогаем | На LXC два разных способа управления (apt + override). apt-mark hold нужен | | A2 | Оставить OSS, изоляция только через api | Ноль рисков ломки | Главное требование (изоляция по ID) не закрыто | | A3 | Полная миграция в Docker s6 (как в исходном промте Олега) | Единый стек, проще будущие upgrades | Требует Docker, расширения rootfs LXC, миграции БД, больше точек отказа сейчас | **Выбран: A1.** A3 — отдельной задачей через 2-4 недели когда дойдём до MySQL/HA/MCP-обёртки. ## Что сделано ### LXC 116 1. **Бэкап** `/root/rustdesk-backup-20260428-1134/` (ключи, db_v2.sqlite3, rustdeskapi.db, config.yaml.orig, systemd units, JWT, admin pw) 2. **Pro-бинари** извлечены из `lejianwen/rustdesk-server-s6:latest` через docker на Proxmox-host (на самом 10.0.0.250 docker есть, в LXC 116 — нет): ``` docker create --name rd-extract lejianwen/rustdesk-server-s6:latest docker cp rd-extract:/usr/bin/hbbs /tmp/... docker cp rd-extract:/usr/bin/hbbr /tmp/... pct push 116 ... ``` 3. **Подмена** через `mv`: - `/usr/bin/hbbs.oss-1.1.14` ← старый - `/usr/bin/hbbs` ← новый pro (поддерживает `--must-login Y`) 4. **`apt-mark hold rustdesk-server-hbbs rustdesk-server-hbbr`** — apt не перезапишет 5. **Systemd overrides:** - `/etc/systemd/system/rustdesk-hbbs.service.d/override.conf`: ``` ExecStart= ExecStart=/usr/bin/hbbs -k _ -r remot.dttb.ru:21117 --must-login Y ``` - `/etc/systemd/system/rustdesk-hbbr.service.d/override.conf`: ``` ExecStart= ExecStart=/usr/bin/hbbr -k _ ``` 6. **`config.yaml`** правки (точечно, без пересборки): - `lang: ru` - `admin.title: "Remote Support Portal"` - `app.show-swagger: 1`, `ban-threshold: 5` - `rustdesk.id-server / relay-server: remot.dttb.ru` - `rustdesk.api-server: https://remot.dttb.ru` - `rustdesk.key: R0lA4r77hAGw6YRL1qG3JioVqQ0Q0fJfzkwlAGqR6jU=` (значение `id_ed25519.pub`) - `rustdesk.key-file: /var/lib/rustdesk-server/id_ed25519.pub` - `jwt.key: ` 7. **Admin pw сменён** через CLI: `rustdesk-api reset-admin-pwd ` (cd в `/var/lib/rustdesk-api/`) ### NPM (LXC 103 через API) 1. Proxy Host 14 (id 14): `forward_host: 10.0.0.244`, `ssl_forced/http2/block_exploits=true`, `caching_enabled=false`, `allow_websocket_upgrade=true` 2. **Custom Nginx Configuration** (advanced_config) добавлен с `location /ws/id` (→21118) и `/ws/relay` (→21119) — для будущей All-in-HTTPS архитектуры (клиенты за корпоративными firewall'ами через 443) 3. Stream 30 (TCP 21114) **удалён** — API не должен жить голым TCP 4. Streams 31-35 пересозданы (id 38-43) на 10.0.0.244 (PUT не работает на streams API — `additional properties` ошибка; делать DELETE+POST с минимальным body без `certificate_id`) 5. Добавлен Stream id 43: 21116/TCP (id-server) дополнительно к Stream id 39: 21116/UDP ## Подтверждения - `sha256sum id_ed25519.pub` идентичен до/после миграции (`abfbebe9…a38c6b`) ✅ - `sha256sum id_ed25519` (приватный) идентичен ✅ - `MUST_LOGIN=Y` явно в `/var/log/rustdesk-server/hbbs.log` после рестарта - `Key: R0lA4r77hAGw6YRL1qG3JioVqQ0Q0fJfzkwlAGqR6jU=` тот же - `Private key comes from id_ed25519` — pro-hbbs нашёл существующий ключ, не перегенерировал - Все 7 портов 21114-21119 слушают - `https://remot.dttb.ru/_admin/` снаружи: **HTTP/2 200 OK** (раньше 502) - 13 существующих peers продолжают подключаться (БД та же) ## Грабли 1. **community-script ставит OSS hbbs**, не pro. Бинарь от Purslane Ltd. Версия 1.1.14 у обоих идентична, но pro имеет дополнительный CLI-флаг `--must-login Y` и читает `MUST_LOGIN` env. Проверка через `grep -ao MUST_LOGIN /usr/bin/hbbs` (`strings` в LXC нет). 2. **Admin API не пускает через curl** даже с правильным admin-токеном из `/api/admin/login` — требует web-сессию. Юзеров и группы создавать через UI. 3. **NPM PUT на streams возвращает 400 `additional properties`** — даже если отправить только разрешённые поля. Решение: DELETE + POST с минимальным body (5 полей: incoming_port, forwarding_host, forwarding_port, tcp_forwarding, udp_forwarding). Без `certificate_id`. 4. **`ENCRYPTED_ONLY` env-переменная** не нашлась как строка в pro-бинаре hbbs. Возможно управляется через config или этого патча нет в публичном `lejianwen/rustdesk-server-s6`. MUST_LOGIN сам по себе закрывает изоляцию — шифрование RustDesk и так включено для key-серверов. 5. **NPM streams для портов 21115-21119 по факту не использовались** — peers подключались внутри LAN (10.0.0.244 напрямую) или через NetBird (100.70.191.161). Поэтому "поломанная" конфигурация NPM не мешала. Внешний DNAT на роутере dttb.ru вероятно идёт прямо на rustdesk-сервер минуя NPM (надо ещё разок проверить — или подтвердить что внешние клиенты ходят через NetBird). ## Rollback `bash /root/rustdesk-rollback.sh` — за 30 секунд: - mv бинари обратно (`/usr/bin/hbbs.oss-1.1.14` → `/usr/bin/hbbs`) - удаляет systemd overrides - восстанавливает `config.yaml.orig` - снимает apt-mark hold - рестартует сервисы NPM-настройки **скриптом не откатываются** — ручной откат через UI или из `/data/database.sqlite` LXC 103. ## TODO (отдельные задачи) - Создать клиентские группы и юзеров через web (Олег, в /_admin/) - Протестировать изоляцию на тестовой клиентской машине - Раскатать новый `RustDesk2.toml` на ~30-50 машин через GPO/скрипт (старые конфиги тоже работают — ключ тот же) - Бэкап БД API + ключей по cron на ArtLeon через NetBird/rsync - Через 2-4 недели — миграция в Docker rustdesk-server-s6 (Вариант A3), MySQL/HA, MCP-обёртка ## Связанные файлы На LXC 116: - `/root/rustdesk-backup-20260428-1134/` — бэкап и креды - `/root/rustdesk-rollback.sh` — откат (chmod 700) - `/root/RustDesk2.toml` — шаблон клиента - `/root/rustdesk-migration-report.md` — расширенный отчёт В kb: - [[projects/dttb/rustdesk]] — справочник по серверу - [[projects/dttb/credentials]] — креды (admin pw, JWT_KEY) - [[projects/dttb/proxmox-inventory]] — LXC 116 запись обновлена