Compare commits

11 Commits

Author SHA1 Message Date
Orchestrator
038b9791c5 feat: add Xvfb + RustDesk scripts for VPS GUI fix (#6)\n\n- scripts/vps/start-rustdesk-xvfb.sh: Xvfb + xcompmgr + RustDesk launcher\n- scripts/host/manual-start-rustdesk.sh: manual run script for VPS\n\nWorkaround for RustDesk v1.4.6 SIGSEGV on dummy/Xvfb display\nwhen opening GUI (Flutter requires GL/GLES compositor).\n\nCloses #6 (partial) 2026-05-16 00:46:53 +01:00
Orchestrator
43e7b30f41 feat: add host setup scripts + configs for Intel Alder Lake-N
- scripts/host/setup-host-rustdesk.sh: protect RustDesk ID on host
- scripts/host/README-host.md: instructions for manual apply
- configs/rustdesk2-host.toml.template: config template
- configs/systemd/: systemd units for both VPS and host
2026-05-16 00:16:14 +01:00
Orchestrator
32451eb0f8 feat: add client config script and systemd unit 2026-05-15 23:51:09 +01:00
Orchestrator
3f49556cd7 feat: add RustDesk Server scripts (hbbs+hbbr native, systemd, UFW)
- install-rustdesk-server.sh: download & install hbbs/hbbr from GitHub releases
- rustdesk-hbbs.service: systemd unit for ID-server (ports 21115-21116)
- rustdesk-hbbr.service: systemd unit for relay (port 21117)
- open-rustdesk-ports.sh: idempotent UFW port opening
- README.md: deployment instructions

Generated by orchestrator in APAW worktree, applied to VPS 2.59.219.61
2026-05-15 23:50:39 +01:00
Deploy Bot
554aa6008d feat: working XFCE + RustDesk online on VPS tmux
- Add start-rd-desktop.sh: Xvfb :99 + xfwm4 + xfce4-panel + RustDesk client
- Add rustdesk-vps.service (Type=simple, but systemd kills processes) → use tmux instead
- Update README.md with VPS architecture (Xvfb + xfce4-session + RustDesk)
- Update VPS section: tmux persistent session instead of systemd
- RustDesk ID: 458564614, password: retrowest
- Ports 21115-21119 open in UFW for Docker server (optional)
- XFCE desktop confirmed working (xfdesktop + xfwm4 + panel)

Note: systemd ExecStartPre kills background processes (Xvfb, xfce4).
Solution: tmux detached session 'rd' with start-rd-desktop.sh
2026-05-15 22:02:45 +01:00
Deploy Bot
be123e8057 feat: add persistent RustDesk client service for VPS
- Add rustdesk-vps.service (systemd unit with Xvfb + DBus)
- Add rustdesk-vps-start.sh (Xvfb startup + DBus session for RustDesk)
- Update xorg-dummy.service without -nolisten local (fixes socket access)
- Update rustdesk-dummy.service with /usr/share/ binary path
- Add docker-compose.vps.yml with hbbs/hbbr RustDesk server
- RustDesk client connects to public relay rs-ny.rustdesk.com:21116
- ID: 458564614 (confirmed via --get-id)
- Password: retrowest
2026-05-15 21:30:47 +01:00
Deploy Bot
a6492fc1b9 feat: add Docker-based RustDesk Server (hbbs/hbbr) for VPS
- Add docker-compose.vps.yml with hbbs + hbbr services
- Update rustdesk-dummy.service with corrected binary path (/usr/share/...)
- Update xorg-dummy.service without -nolisten local (fixes socket access)
- Update install.sh for dummy driver + EDID fallback approach
- Update README with dual setup (host + VPS) and server instructions
- VPS: RustDesk Server runs via Docker on ports 21115-21119
- Public key: 5PxKX27ZTJGlu9KjU7YByXQDIw6zS8pKlvqxXsZiw8M=
2026-05-15 20:09:30 +01:00
Deploy Bot
b93147dd67 docs: update README with VPS setup steps including gdm disable 2026-05-15 19:18:34 +01:00
Deploy Bot
90fa901119 fix: VPS xorg-dummy auth + rustdesk-dummy service fixes
- xorg-dummy.service: remove -nolisten local (blocked local UNIX socket)
- xorg-dummy.service: add -auth /root/.Xauthority and xauth generation ExecStartPre
- rustdesk-dummy.service: add XAUTHORITY=/root/.Xauthority env var
- Add config/vps/20-dummy-headless.conf (Xorg dummy config for VPS)
- Disable conflicting gdm.service on VPS
- RustDesk server now starts successfully with DUMMY0 as display

Fixes connection refused on /tmp/.X11-unix/X0 caused by
-nolisten local Xorg flag.
2026-05-15 19:17:13 +01:00
Deploy Bot
00c36b8d7e feat: switch host from intel VirtualHeads to dummy driver for headless boot
- Add 20-dummy-headless.conf (Driver dummy, 1920x1080)
- Remove 20-intel-virtual.conf (intel driver required HDMI at boot)
- Update install.sh: installs dummy driver, saves EDID, updates GRUB
- Update README: document dummy driver + kernel EDID fallback approach
- hdmi-fallback.sh now detects DUMMY0 or VIRTUAL1

Rationale: intel VirtualHeads only worked when HDMI was connected at boot.
Dummy driver creates DUMMY0 unconditionally, working with or without cable.
Kernel EDID fallback (video=... drm.edid_firmware=...) is insurance if dummy fails.
2026-05-15 18:12:05 +01:00
Deploy Bot
a0b2b5ebf7 feat: update configs for host+VPS with Intel VirtualHeads and dummy driver
- Host: 20-intel-virtual.conf with Driver intel + VirtualHeads 1
- VPS: xorg-dummy.service + rustdesk-dummy.service for CPU-only
- Updated README with dual setup (host Intel iGPU + VPS dummy)
- Verified: VIRTUAL1 primary after reboot, HDMI1 clone
- Verified: DUMMY0 primary on VPS without GPU
2026-05-14 09:29:08 +01:00
28 changed files with 1460 additions and 280 deletions

430
README.md
View File

@@ -1,251 +1,300 @@
# RDtop — RustDesk Headless Display Setup
**Автоматический виртуальный дисплей для RustDesk на Intel iGPU без подключённого HDMI монитора.**
**Автоматический виртуальный дисплей для RustDesk без подключенного HDMI монитора. Работает на любой Linux машине (Intel iGPU, AMD, NVIDIA, CPU-only VPS).**
---
## Содержание
1. [Кратко о проблеме](#кратко-о-проблеме)
2. [Архитектура решения](#архитектура-решения)
3. [Что где лежит и куда ставить](#что-где-лежит-и-куда-ставить)
1. [Проблема](#проблема)
2. [Решение](#решение)
3. [Файлы проекта](#файлы-проекта)
4. [Установка](#установка)
5. [Как это работает на самом деле](#как-это-работает-на-самом-деле)
5. [Как это работает](#как-это-работает)
6. [Проверка](#проверка)
7. [Troubleshooting](#troubleshooting)
8. [Откат](#откат)
9. [Технические детали](#технические-детали)
---
## Кратко о проблеме
## Проблема
Оборудование: **Intel Alder Lake-N (i915)**, Ubuntu 24.04 (Noble), X11 (GDM/GNOME).
Оборудование: **Intel Alder Lake-N (i915)**, Ubuntu 24.04, X11 (GDM/GNOME).
### Что происходит
1. При подключённом HDMI — Xorg инициализирует `HDMI-1` на отдельный CRTC.
1. При подключенном HDMI — Xorg инициализирует `HDMI-1` на отдельный CRTC.
2. Когда кабель отключают — **CRTC уничтожается**.
3. У Xorg не остаётся активного выхода. Может остаться чёрный экран или fallback resolution 8×8.
4. RustDesk всё ещё подключён, но захватывает `Screen 0` — и видит либо чёрный экран, либо 8×8 пикселей.
3. У Xorg не остается активного выхода. Может остаться черный экран или fallback 8×8.
4. RustDesk все еще подключен, но захватывает `Screen 0` — и видит либо черный экран, либо 8×8 пикселей.
### Почему стандартные способы не помогли
| Способ | Результат |
|--------|-----------|
| `Option "VirtualHeads" "1"` (intel driver) | **Не работает** на Alder Lake-N: VIRTUAL1 создаётся, но intel driver не инициализирует на Gen11+. |
| modesetting driver | Даёт VIRTUAL1 через randr, но через uAPI DRI он остаётся disconnected. |
| `Option "VirtualHeads" "1"` (intel driver) | **Работает только с кабелем на момент загрузки.** Без HDMI — VIRTUAL1 не создается. |
| modesetting driver | Создает VIRTUAL1 через randr, но после reboot пропадает. |
| `NoOutputInitialSize` | Требует пересборки xorg или патчей. |
| Dummy plug (аппаратный) | Работает, но нужно покупать. Для программного режима — нет простого способа. |
| Dummy plug (аппаратный) | Работает, но нужно покупать. |
---
## Архитектура решения
## Решение
### Хост: Dummy driver + kernel EDID fallback
Работает **без кабеля HDMI на момент загрузки** — dummy driver не зависит от физических выходов.
```
+-----------------------------------------------------------------------------+
| X11 Screen 0: 1920x1080 |
| |
| +------------------------+ +------------------------------------+ |
| | VIRTUAL1 | | HDMI1 | |
| | PRIMARY | | CLONE (--same-as VIRTUAL1) | |
| | 1920×1080 @ 60Hz | | 1920×1080 @ 60Hz | |
| | Full HD | | Full HD (если кабель подключён) | |
| | ВСЕГДА АКТИВЕН | | При отключении → off | |
| +------------------------+ +------------------------------------+ |
| |
| RustDesk: всегда захватывает PRIMARY (VIRTUAL1), вне зависимости |
| от того, подключён HDMI или нет. |
+-----------------------------------------------------------------------------+
+------------------------------------------------------------------------+
| Xorg Dummy Driver (1920×1080) |
| +-------------------------+ |
| | DUMMY0 = primary | ← RustDesk --server захватывает |
| | 1920×1080 @ 60Hz | |
| +-------------------------+ |
| |
| При подключении HDMI кабеля: |
| скрипт hdmi-fallback.sh клонирует HDMI1 на DUMMY0 |
| При отключении: HDMI1 off, DUMMY0 остается primary |
+------------------------------------------------------------------------+
```
**Ключевой принцип:** `VIRTUAL1 = primary`. HDMI1 — это просто клон. Когда HDMI1 отключается, его CRTC удаляется, но primary остаётся — RustDesk всегда видит рабочий стол.
### Kernel EDID fallback (дополнительно)
Параметры ядра:
```
video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin
```
Это заставляет ядро `i915` инициализировать HDMI-A-1 с сохраненным EDID даже без кабеля. Если dummy driver не сработает (редкий случай), ядро все равно создаст виртуальный framebuffer.
### VPS (CPU-only): Dummy driver + Xorg + RustDesk
Тот же dummy driver, но без GPU. Работает на любом VPS.
```
+------------------------------------------------------------------------+
| Xorg Dummy Driver (1920×1080) |
| +-------------------------+ |
| | DUMMY0 = primary | ← RustDesk --server захватывает |
| | 1920×1080 @ 60Hz | |
| +-------------------------+ |
| Нет GPU, нет DRM — работает на любом VPS |
+------------------------------------------------------------------------+
```
---
## Что где лежит и куда ставить
## Файлы проекта
| Файл репозитория | Назначение | Системный путь |
|------------------|------------|----------------|
| `bin/hdmi-fallback.sh` | Мониторинг `HDMI-A-1` в sysfs и автоматическое переключение на fallback | `~/.local/bin/hdmi-fallback.sh` |
| `config/x11/90-fallback.conf` | Xorg конфиг: гарантирует framebuffer 1920×1080 даже при отключённом HDMI | `/etc/X11/xorg.conf.d/90-fallback.conf` |
| `config/systemd/hdmi-fallback.service` | systemd user service: запускает скрипт после входа в графическую сессию | `~/.config/systemd/user/hdmi-fallback.service` |
| `install.sh` | Установщик: делает всё автоматически | (запускается один раз) |
| `bin/hdmi-fallback.sh` | Мониторинг HDMI-A-1 и авто-fallback на DUMMY0/VIRTUAL1 | `~/.local/bin/hdmi-fallback.sh` |
| `bin/vps-start-xorg.sh` | Стартер Xorg с dummy driver на VPS | `/usr/local/bin/start-xorg-dummy.sh` |
| `config/x11-host/20-dummy-headless.conf` | Dummy driver headless config | `/etc/X11/xorg.conf.d/20-dummy-headless.conf` |
| `config/x11-host/20-intel-virtual.conf.bak` | Backup старого Intel config (не используется) | — |
| `config/systemd/hdmi-fallback.service` | systemd user unit (host) | `~/.config/systemd/user/hdmi-fallback.service` |
| `config/vps/xorg-dummy.service` | systemd system unit (VPS Xorg) | `/etc/systemd/system/xorg-dummy.service` |
| `config/vps/rustdesk-dummy.service` | systemd system unit (VPS RustDesk) | `/etc/systemd/system/rustdesk-dummy.service` |
| `install.sh` | Установщик для хоста | (запускается один раз) |
---
## Установка
### Способ 1: Одной командой
### Хост (Desktop с GPU)
```bash
bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
```
После установки **перезагрузить компьютер** или перезапустить GDM:
После установки **перезагрузить компьютер**:
```bash
sudo systemctl restart gdm
sudo reboot
```
### Способ 2: Вручную
**Вручную:**
```bash
# 1. Клонируем репозиторий
git clone https://git.softuniq.eu/NW/RDtop.git ~/RDtop
cd ~/RDtop
# 1. Клонируем
git clone https://git.softuniq.eu/NW/RDtop.git ~/RDtop && cd ~/RDtop
# 2. Устанавливаем dummy драйвер (опционально, запасной)
sudo apt-get update
sudo apt-get install -y xserver-xorg-video-dummy
# 2. Dummy driver config (главный способ)
sudo cp config/x11-host/20-dummy-headless.conf /etc/X11/xorg.conf.d/
# 3. Копируем X11 конфиг
sudo mkdir -p /etc/X11/xorg.conf.d
sudo cp config/x11/90-fallback.conf /etc/X11/xorg.conf.d/
# 3. Удалить старые конфиги если были
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
# 4. Устанавливаем скрипт
mkdir -p ~/.local/bin
# 4. Сохранить EDID для kernel fallback (опционально)
sudo mkdir -p /lib/firmware/edid
sudo cp /sys/class/drm/card0-HDMI-A-1/edid /lib/firmware/edid/samsung.bin
# 5. Обновить GRUB для kernel EDID fallback
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT=".*"|GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin"|' /etc/default/grub
sudo update-grub
# 6. Скрипт мониторинга
chmod +x bin/hdmi-fallback.sh
cp bin/hdmi-fallback.sh ~/.local/bin/
# 5. Устанавливаем systemd unit
# 7. systemd unit
mkdir -p ~/.config/systemd/user
cp config/systemd/hdmi-fallback.service ~/.config/systemd/user/
# 6. Активируем сервис
systemctl --user daemon-reload
systemctl --user enable --now hdmi-fallback.service
# 7. Перезагрузка (обязательно для X11)
sudo systemctl restart gdm
# 8. Перезагрузка
sudo reboot
```
### Вариант Б: CPU-only VPS
```bash
# 1. Установить Xorg + dummy
apt-get update
apt-get install -y xserver-xorg xserver-xorg-video-dummy rustdesk
# 2. Копировать конфиги
git clone https://git.softuniq.eu/NW/RDtop.git /tmp/rdtop
cd /tmp/rdtop
sudo cp config/vps/20-dummy-headless.conf /etc/X11/xorg.conf.d/
sudo cp config/vps/xorg-dummy.service /etc/systemd/system/
sudo cp config/vps/rustdesk-dummy.service /etc/systemd/system/
sudo cp bin/vps-start-xorg.sh /usr/local/bin/start-xorg-dummy.sh
chmod +x /usr/local/bin/start-xorg-dummy.sh
# 3. Остановить конфликтующий gdm
systemctl stop gdm.service 2>/dev/null || true
systemctl disable gdm.service 2>/dev/null || true
# 4. Запустить
systemctl daemon-reload
systemctl enable --now xorg-dummy.service
systemctl enable --now rustdesk-dummy.service
# 5. Проверить
export DISPLAY=:0
xrandr --listmonitors
# Должно показать: DUMMY0 primary 1920x1080
```
---
## Как это работает на самом деле
## Как это работает
### Шаг 1. Xorg fallback конфиг (`90-fallback.conf`)
### Хост (Intel iGPU)
1. **Dummy driver** (`Driver "dummy"`) создает DUMMY0 сразу при старте Xorg, **независимо от HDMI**.
2. **Kernel EDID fallback** (`drm.edid_firmware`) — если dummy каким-то образом не сработает, ядро `i915` инициализирует HDMI-A-1 с сохраненным EDID.
3. **Скрипт** `hdmi-fallback.sh` при старте:
- Всегда активирует DUMMY0 как primary
- Если HDMI connected — делает HDMI1 clone (`--same-as`)
- Если HDMI disconnected — выключает HDMI1, DUMMY0 остается primary
4. **RustDesk** захватывает DUMMY0 (primary).
### VPS (CPU-only)
**Архитектура:** Xvfb :99 + xfce4-session + xfwm4 + xfce4-panel + RustDesk client
1. **Xvfb** — виртуальный framebuffer на `:99` 1920×1080x24, доступен для захвата X11
2. **xfce4-session** — полноценный XFCE рабочий стол (окна, панель, иконки)
3. **RustDesk** — клиент подключён к публичному relay `rs-ny.rustdesk.com:21116`
4. **Вход** — ID из 9 цифр + пароль
```
Section "Screen"
Identifier "AutoScreen"
...
SubSection "Display"
Depth 24
Virtual 1920 1080
EndSubSection
EndSection
+------------------------------------------------------------------------+
| Xvfb :99 (1920x1080x24) |
| +-------------------------------------------------------+ |
| | XFCE Desktop (xfwm4 + xfce4-panel + xfdesktop) | |
| | +---------------------------------------------+ | |
| | | [Панель XFCE] [Иконки] [Контекстное меню]| | |
| | +---------------------------------------------+ | |
| | ← RustDesk client захватывает экран через X11 | |
| +-------------------------------------------------------+ |
| SSH / tmux (персистентность) |
+------------------------------------------------------------------------+
```
Параметр `Virtual 1920 1080` задаёт размер виртуального framebuffer на уровне экрана X11. Даже если физических выхода нет, Xorg создаёт `Screen` фиксированного размера. **Без этого при отключённом HDMI Xorg мог задать fallback resolution 8×8 или не инициализировать экран вовсе.**
### Шаг 2. Modesetting драйвер создаёт VIRTUAL1
Драйвер `modesetting` (встроен в `xorg-server`) на современных Intel GPU создаёт `VIRTUAL1` как software output. Проверка:
```bash
xrandr | grep VIRTUAL
# VIRTUAL1 disconnected (normal left inverted right x axis y axis)
# VIRTUAL2 disconnected (normal left inverted right x axis y axis)
```
Это отдаётся через uAPI randr, но не через DRI.
### Шаг 3. Мы активируем VIRTUAL1
```bash
xrandr --addmode VIRTUAL1 "1920x1080_60.00"
xrandr --output VIRTUAL1 --mode "1920x1080_60.00" --primary
xrandr --output HDMI1 --auto --same-as VIRTUAL1
```
`--same-as` означает клонирование. Обе точки вывода ссылаются на **один и тот же framebuffer**. Когда HDMI1 теряет кабель, его CRTC удаляется, но данные остаются на VIRTUAL1.
### Шаг 4. Скрипт `hdmi-fallback.sh`
```bash
# Цикл каждые 2 секунды проверяет sysfs:
# /sys/class/drm/card1-HDMI-A-1/status
# connected → HDMI1 = clone(VIRTUAL1)
# disconnected → HDMI1 = off, VIRTUAL1 = primary
```
**Почему не через udev?** `drm_connector` в ядре i915 не генерирует reliable hotplug events через sysfs. Поэтому используется polling каждые 2 сек. Это безопасно — sysfs read очень лёгкий.
### Шаг 5. Dummy driver — запасной вариант
Если VIRTUAL1 по какой-то причине не создаётся (например, если modesetting не загружается), `dummy_drv.so` позволяет создать виртуальный выход через `xf86-video-dummy`:
```bash
# Установка:
sudo apt-get install xserver-xorg-video-dummy
# Конфигурация:
# Driver "dummy", Monitor, Screen → DUMMY-1
```
Однако на Intel i915 Alder Lake-N modesetting + VIRTUAL1 работает стабильнее.
**Персистентность:** `tmux` (не systemd) — `pkill` в ExecStartPre убивал Xvfb и xfce.
---
## Проверка
### Хост (после reboot без HDMI)
```bash
# 1. После перезагрузки — проверяем primary output
# Должно показать DUMMY0 как primary
xrandr --listmonitors
# Ожидаемый вывод:
# 0: +*DUMMY0 1920/508x1080/286+0+0 DUMMY0 [PRIMARY]
# Если HDMI подключен:
# 1: +HDMI-1 1920/508x1080/286+0+0 HDMI-1 [CLONE]
```
### Сервис
```bash
systemctl --user status hdmi-fallback.service
```
### Логи
```bash
cat ~/.local/logs/drm-hotplug.log
```
### VPS
```bash
export DISPLAY=:0
xrandr --listmonitors
# Должно быть:
# 0: +*VIRTUAL1 1920/508x1080/286+0+0 VIRTUAL1 [PRIMARY]
# 1: +HDMI1 1920/700x1080/390+0+0 HDMI1 [CLONE]
# 0: +*DUMMY0 1920x... DUMMY0 [PRIMARY]
# 2. Проверяем сервис
systemctl --user status hdmi-fallback.service
# ● hdmi-fallback.service - HDMI to VIRTUAL1 Fallback Monitor
# Active: active (running)
# 3. Смотрим лог
journalctl --user -u hdmi-fallback.service -f
# 4. Тест: вытаскиваем HDMI
# Через 2-3 секунды xrandr должен показывать только VIRTUAL1,
# при этом RustDesk продолжает работать.
# RustDesk
ps aux | grep rustdesk
# /usr/lib/rustdesk/rustdesk --server должен быть запущен
```
---
## Troubleshooting
### VIRTUAL1 не появляется в xrandr
### DUMMY0 не появляется после reboot
```bash
# Добавляем modeline вручную
xrandr --addmode VIRTUAL1 "1920x1080_60.00"
# Если ошибка — создаём моделайн:
xrandr --newmode "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120
xrandr --addmode VIRTUAL1 "1920x1080_60.00"
# Проверить dummy driver:
grep "Loading.*dummy" /var/log/Xorg.0.log
# Должно быть: (II) Loading /usr/lib/xorg/modules/drivers/dummy_drv.so
# Проверить конфиг:
ls /etc/X11/xorg.conf.d/20-dummy-headless.conf
# Должен существовать
# Если intel driver загружается вместо dummy:
# Убедиться что нет других конфликтующих xorg.conf:
ls /etc/X11/xorg.conf 2>/dev/null || echo "OK: no xorg.conf"
```
### RustDesk всё ещё чёрный экран
### RustDesk черный экран
Проверка primary:
```bash
# Проверить primary:
xrandr --verbose | grep primary
# Должно быть: VIRTUAL1 connected primary 1920x1080+0+0
# Должно быть: DUMMY0 connected primary
# Если primary не DUMMY0:
xrandr --output DUMMY0 --mode "1920x1080" --primary
xrandr --output HDMI-1 --auto --same-as DUMMY0
```
Если primary = HDMI1 — переключаем:
```bash
xrandr --output VIRTUAL1 --mode "1920x1080_60.00" --primary
xrandr --output HDMI1 --auto --same-as VIRTUAL1
```
### Сервис не запускается
### VPS: RustDesk не видит DUMMY0
```bash
# Проверь лог скрипта
cat ~/.local/logs/drm-hotplug.log
# Проверить что Xorg запущен:
ls /tmp/.X11-unix/X0
# Должен быть сокет
# Проверь конфиг юнита
systemctl --user cat hdmi-fallback.service
# Если нет — запустить вручную:
/usr/local/bin/start-xorg-dummy.sh
```
---
@@ -253,86 +302,51 @@ systemctl --user cat hdmi-fallback.service
## Откат
```bash
# 1. Удалить конфиги X11
# Хост
sudo rm -f /etc/X11/xorg.conf.d/20-dummy-headless.conf
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
# 2. Остановить и удалить сервис
systemctl --user disable --now hdmi-fallback.service
rm -f ~/.config/systemd/user/hdmi-fallback.service
# 3. Удалить скрипт
rm -f ~/.local/bin/hdmi-fallback.sh
# 4. Перезагрузка
sudo reboot
# Вернуть стандартный GRUB:
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT=".*"|GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"|' /etc/default/grub
sudo update-grub
# VPS
systemctl disable --now xorg-dummy.service
systemctl disable --now rustdesk-dummy.service
sudo rm -f /etc/systemd/system/xorg-dummy.service
sudo rm -f /etc/systemd/system/rustdesk-dummy.service
```
---
## Технические детали
### Железо
### Почему dummy driver, а не intel с VirtualHeads?
```
lspci | grep VGA
# 00:02.0 VGA compatible controller: Intel Corporation Alder Lake-N [UHD Graphics]
`intel` driver + `VirtualHeads=1` работает **только если HDMI подключен при загрузке**. Без кабеля — ядро не создает connector/CRTC для HDMI-A-1, и `VirtualHeads` не помогает. Dummy driver не зависит от физических выходов.
# Ядро: i915
cat /sys/class/drm/card1-HDMI-A-1/status
# connected (или disconnected)
### Почему kernel EDID fallback?
# Только 1 CRTC:
cat /sys/kernel/debug/dri/*/i915_display_info | grep "CRTC"
# [CRTC:80:pipe A] — один pipe
```
Параметры `video=HDMI-A-1:1920x1080@60` и `drm.edid_firmware=HDMI-A-1:edid/samsung.bin` заставляют ядро `i915` инициализировать connector с фиктивным EDID. Это "страховка": если dummy driver по какой-то причине не загрузится (например, обновление Xorg удалит его), ядро все равно создаст выход.
### Почему не intel драйвер?
### Почему клон, а не extended desktop?
Драйвер `xf86-video-intel` — legacy. Официально **не рекомендуется** для Gen 9 и новее (Broadwell+). На Alder Lake-N он вообще не инициализируется корректно. Ubuntu по умолчанию использует `modesetting` — и это правильно.
`--same-as` клонирует framebuffer. При disconnect HDMI clone просто перестает рендериться, но primary (DUMMY0) не затрагивается — Xorg не теряет framebuffer.
### Почему именно клон, а не side-by-side?
### Dummy driver на VPS
```
HDMI1 right-of VIRTUAL1 → CRTC для HDMI1 используется отдельно
При отключении HDMI1 → CRTC освобождается
→ если VIRTUAL1 не primary, Screen может схлопнуться
HDMI1 same-as VIRTUAL1 → Один framebuffer, два выхода
При отключении HDMI1 → Просто один output становится неактивным
→ Primary (VIRTUAL1) никогда не пострадает
```
### Dummy драйвер как fallback
На некоторых системах modesetting может не создать VIRTUAL1 (например, если DRI отключен). `dummy_drv.so` решает это:
```
Driver "dummy"
→ Создаёт DUMMY-1 с фиксированным разрешением
→ Всегда работает, независимо от GPU
→ Пакет: xserver-xorg-video-dummy
```
В нашем случае i915 Alder Lake-N — modesetting даёт VIRTUAL1, dummy не нужен, но мы его установили как запасной вариант.
### Права доступа
```
/etc/X11/xorg.conf.d/90-fallback.conf — root:root 644
~/.local/bin/hdmi-fallback.sh — swp:swp 755
~/.config/systemd/user/... — swp:swp 644
```
Не требует GPU, не использует DRM. Работает на любом Linux с xorg-server. RustDesk использует software capture через X11 API.
---
## Авторы и цели
## Авторы
- **Задача:** Запуск RustDesk на headless машине (нет физического монитора) с Intel iGPU.
- **Ключевой инсайт:** `VIRTUAL1` создаётся modesetting driver'ом на Intel Alder Lake-N; нужно только сделать его primary и отслеживать HDMI.
- **Результат:** При отключённом HDMI кабеле RustDesk продолжает работать через `VIRTUAL1` (1920×1080, fullscreen).
- **Создано:** через AI agent pipeline (Kilo Code Orchestrator) для SWP.
---
- Deploy via AI Agent (Kilo Code Orchestrator)
- Target hardware: Intel Alder Lake-N (i915), Ubuntu 24.04
- VPS: CPU-only, Ubuntu 24.04, Hetzner/DigitalOcean/Linode
## Ссылки

31
bin/vps-start-xorg.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Start Xorg with dummy driver for headless VPS
export DISPLAY=:0
export XAUTHORITY=${XAUTHORITY:-/run/user/0/.Xauthority}
# Generate MIT magic cookie if needed
if [ ! -s "$XAUTHORITY" ]; then
mkdir -p "$(dirname "$XAUTHORITY")"
touch "$XAUTHORITY"
xauth -f "$XAUTHORITY" generate :0 MIT-MAGIC-COOKIE-1 2>/dev/null || \
xauth -f "$XAUTHORITY" add :0 MIT-MAGIC-COOKIE-1 "$(xxd -l 16 -p /dev/urandom)" 2>/dev/null || true
fi
# Ensure socket directory
mkdir -p /tmp/.X11-unix
# Check if already running
if pgrep -x "Xorg" >/dev/null 2>&1; then
echo "Xorg already running"
exit 0
fi
exec /usr/lib/xorg/Xorg :0 \
-nolisten tcp \
-nolisten local \
-config /etc/X11/xorg.conf.d/99-dummy.conf \
+extension GLX \
+extension RANDR \
+extension RENDER \
-novtswitch \
-keeptty

View File

@@ -0,0 +1,23 @@
Section "Device"
Identifier "DummyHeadless"
Driver "dummy"
Option "ConstantDPI" "true"
VideoRam 256000
EndSection
Section "Monitor"
Identifier "DummyMonitor"
HorizSync 28-80
VertRefresh 48-75
EndSection
Section "Screen"
Identifier "DummyScreen"
Device "DummyHeadless"
Monitor "DummyMonitor"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1920x1080" "1280x720"
EndSubSection
EndSection

View File

@@ -0,0 +1,17 @@
[Unit]
Description=RustDesk with Dummy Display
After=xorg-dummy.service
Requires=xorg-dummy.service
[Service]
Type=simple
User=root
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/root/.Xauthority"
ExecStartPre=/bin/sleep 3
ExecStart=/usr/share/rustdesk/rustdesk --server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,34 @@
#!/bin/bash
# RustDesk VPS Headless Client Startup Script
# Starts Xvfb :99, waits for display, then launches RustDesk client via DBus
set -e
# Kill old Xvfb
pkill -9 Xvfb 2>/dev/null || true
sleep 1
# Start Xvfb
/usr/bin/Xvfb :99 -screen 0 1920x1080x24 -ac -nolisten tcp -nolisten unix &
XVFB_PID=$!
sleep 2
# Wait for display to be ready
for i in $(seq 1 30); do
if xdpyinfo -display :99 > /dev/null 2>&1; then
break
fi
sleep 1
done
# Verify display is up
if ! xdpyinfo -display :99 > /dev/null 2>&1; then
echo "ERROR: Xvfb failed to start on :99"
kill $XVFB_PID 2>/dev/null || true
exit 1
fi
export DISPLAY=:99
# Start RustDesk client inside DBus session (required for headless GUI apps)
exec /usr/bin/dbus-run-session -- /usr/share/rustdesk/rustdesk --server

View File

@@ -0,0 +1,19 @@
[Unit]
Description=RustDesk + XFCE Desktop on VPS
After=network-online.target
[Service]
Type=forking
User=root
Environment="DISPLAY=:99"
Environment="XAUTHORITY=/root/.Xauthority"
# Start Xvfb, wait for display, then launch XFCE + RustDesk
ExecStartPre=/bin/bash -c 'pkill -9 -f rustdesk 2>/dev/null || true; pkill -9 -f xfce4-session 2>/dev/null || true; sleep 1; pkill -9 Xvfb 2>/dev/null || true; rm -f /tmp/.X11-unix/X99; sleep 1'
ExecStart=/bin/bash -c 'Xvfb :99 -screen 0 1920x1080x24 -ac +extension RANDR > /dev/null 2>&1 &; sleep 3; for i in $(seq 1 30); do export DISPLAY=:99; export XAUTHORITY=/root/.Xauthority; xrandr --listmonitors > /dev/null 2>&1 && break; sleep 1; done; nohup dbus-run-session -- xfce4-session > /tmp/xfce-session.log 2>&1 &; sleep 5; nohup dbus-run-session -- /usr/share/rustdesk/rustdesk --server > /tmp/rustdesk-session.log 2>&1 &; sleep 2; exit 0'
ExecStop=/bin/bash -c 'pkill -9 -f rustdesk; pkill -9 -f xfce4-session; pkill -9 Xvfb; rm -rf /tmp/RustDesk'
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,51 @@
#!/bin/bash
# RustDesk + XFCE Desktop Auto-Start for VPS
# Systemd runs this script; script waits for Xvfb to exit
set -e
# Kill old
pkill -9 -f rustdesk 2>/dev/null || true
pkill -9 -f xfce4-session 2>/dev/null || true
pkill -9 Xvfb 2>/dev/null || true
rm -f /tmp/.X11-unix/X99 /tmp/.X*-lock
sleep 2
# Start Xvfb :99
Xvfb :99 -screen 0 1920x1080x24 -ac +extension RANDR +extension RENDER +extension GLX > /dev/null 2>&1 &
XVFB_PID=$!
sleep 3
# Wait for display ready
export DISPLAY=:99
export XAUTHORITY=/root/.Xauthority
for i in $(seq 1 30); do
xrandr --listmonitors > /dev/null 2>&1 && break
if ! kill -0 $XVFB_PID 2>/dev/null; then
echo "ERROR: Xvfb died"
exit 1
fi
sleep 1
done
xrandr --listmonitors > /dev/null 2>&1 || { echo "ERROR: Display :99 not ready"; exit 1; }
# Start XFCE desktop in background
nohup dbus-run-session -- xfce4-session > /tmp/xfce-session.log 2>&1 &
echo "XFCE PID: $!"
sleep 5
# Wait for window manager
for i in $(seq 1 20); do
ps aux | grep xfwm4 | grep -v grep > /dev/null 2>&1 && break
sleep 1
done
# Start RustDesk client in background
nohup dbus-run-session -- /usr/share/rustdesk/rustdesk --server > /tmp/rustdesk-session.log 2>&1 &
echo "RustDesk PID: $!"
sleep 3
# Wait for Xvfb (main process) - systemd tracks this script
# If Xvfb dies, systemd will restart the service
wait $XVFB_PID

View File

@@ -0,0 +1,51 @@
#!/bin/bash
# RustDesk + XFCE Desktop Auto-Start for VPS
# Systemd runs this script; script waits for Xvfb to exit
set -e
# Kill old
pkill -9 -f rustdesk 2>/dev/null || true
pkill -9 -f xfce4-session 2>/dev/null || true
pkill -9 Xvfb 2>/dev/null || true
rm -f /tmp/.X11-unix/X99 /tmp/.X*-lock
sleep 2
# Start Xvfb :99
Xvfb :99 -screen 0 1920x1080x24 -ac +extension RANDR +extension RENDER +extension GLX > /dev/null 2>&1 &
XVFB_PID=$!
sleep 3
# Wait for display ready
export DISPLAY=:99
export XAUTHORITY=/root/.Xauthority
for i in $(seq 1 30); do
xrandr --listmonitors > /dev/null 2>&1 && break
if ! kill -0 $XVFB_PID 2>/dev/null; then
echo "ERROR: Xvfb died"
exit 1
fi
sleep 1
done
xrandr --listmonitors > /dev/null 2>&1 || { echo "ERROR: Display :99 not ready"; exit 1; }
# Start XFCE desktop in background
nohup dbus-run-session -- xfce4-session > /tmp/xfce-session.log 2>&1 &
echo "XFCE PID: $!"
sleep 5
# Wait for window manager
for i in $(seq 1 20); do
ps aux | grep xfwm4 | grep -v grep > /dev/null 2>&1 && break
sleep 1
done
# Start RustDesk client in background
nohup dbus-run-session -- /usr/share/rustdesk/rustdesk --server > /tmp/rustdesk-session.log 2>&1 &
echo "RustDesk PID: $!"
sleep 3
# Wait for Xvfb (main process) - systemd tracks this script
# If Xvfb dies, systemd will restart the service
wait $XVFB_PID

View File

@@ -0,0 +1,16 @@
[Unit]
Description=Xorg Dummy Display
After=network.target
[Service]
Type=simple
User=root
Environment="DISPLAY=:0"
ExecStartPre=/bin/bash -c 'test -f /root/.Xauthority || (xauth -f /root/.Xauthority add :0 . $(mcookie) \u0026\u0026 chmod 600 /root/.Xauthority)'
ExecStartPre=/bin/mkdir -p /tmp/.X11-unix
ExecStart=/usr/lib/xorg/Xorg :0 -auth /root/.Xauthority -nolisten tcp -config /etc/X11/xorg.conf.d/20-dummy-headless.conf +extension GLX +extension RANDR +extension RENDER -novtswitch -keeptty
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
Section "Device"
Identifier "DummyHeadless"
Driver "dummy"
Option "ConstantDPI" "true"
VideoRam 256000
EndSection
Section "Monitor"
Identifier "DummyMonitor"
HorizSync 28-80
VertRefresh 48-75
EndSection
Section "Screen"
Identifier "DummyScreen"
Device "DummyHeadless"
Monitor "DummyMonitor"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1920x1080" "1280x720"
EndSubSection
EndSection

View File

@@ -0,0 +1,9 @@
# BACKUP of original Intel VirtualHeads config
# Disabled in favour of dummy headless config (20-dummy-headless.conf)
# which works without HDMI connected at boot.
#
# Section "Device"
# Identifier "Intel Graphics"
# Driver "intel"
# Option "VirtualHeads" "1"
# EndSection

View File

@@ -0,0 +1,9 @@
# RustDesk2.toml для хоста Intel Alder Lake-N
# Публичный сервер для стабильного ID
rendezvous_server = 'rs-ny.rustdesk.com:21116'
nat_type = 1
serial = 0
unlock_pin = ''
[options]
local-ip-addr = '<IP хоста>'

View File

@@ -0,0 +1,17 @@
[Unit]
Description=RustDesk Client (local hbbs)
After=xorg-dummy.service network.target
[Service]
Type=simple
User=root
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/root/.Xauthority"
ExecStartPre=/root/write-rustdesk-config.sh
ExecStartPre=/bin/sleep 2
ExecStart=/usr/share/rustdesk/rustdesk --server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
[Unit]
Description=RustDesk Server (hbbr — relay)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/hbbr
WorkingDirectory=/var/lib/rustdesk-server
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/rustdesk-server
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rustdesk-hbbr
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
[Unit]
Description=RustDesk Server (hbbs — ID / NAT relay)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/hbbs
WorkingDirectory=/var/lib/rustdesk-server
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/rustdesk-server
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rustdesk-hbbs
[Install]
WantedBy=multi-user.target

22
docker-compose.vps.yml Normal file
View File

@@ -0,0 +1,22 @@
version: '3.8'
services:
hbbs:
image: rustdesk/rustdesk-server:latest
container_name: rustdesk-hbbs
command: hbbs -r rustdesk-hbbr:21117
volumes:
- ./data:/root
network_mode: host
restart: unless-stopped
depends_on:
- hbbr
hbbr:
image: rustdesk/rustdesk-server:latest
container_name: rustdesk-hbbr
command: hbbr
volumes:
- ./data:/root
network_mode: host
restart: unless-stopped

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# RDtop Installer — RustDesk headless display setup
# RDtop Installer — RustDesk headless display setup (dummy driver)
# Usage: bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
set -euo pipefail
@@ -8,9 +8,10 @@ echo "Installing RustDesk headless display fallback..."
# Config paths
REPO_URL="https://git.softuniq.eu/NW/RDtop"
X11_CONF="/etc/X11/xorg.conf.d/90-fallback.conf"
X11_CONF="/etc/X11/xorg.conf.d/20-dummy-headless.conf"
BIN_DST="$HOME/.local/bin/hdmi-fallback.sh"
SYSTEMD_DST="$HOME/.config/systemd/user/hdmi-fallback.service"
EDID_DST="/lib/firmware/edid/samsung.bin"
LOG="$HOME/.local/logs/rdtop-install.log"
mkdir -p "$(dirname "$LOG")"
@@ -24,130 +25,203 @@ if [ "$EUID" -eq 0 ]; then
exit 1
fi
if ! xset q >/dev/null 2>&1; then
log "WARNING: No X11 display found. You need to run this from a graphical session."
# --- Check sudo works ---
if ! sudo -n true 2>/dev/null; then
echo "This script requires sudo privileges. Please enter your password when prompted."
fi
# --- Check if dummy driver exists or can be installed ---
# --- Install dummy driver ---
if [ -f /usr/lib/xorg/modules/drivers/dummy_drv.so ]; then
log "Dummy driver already present"
else
log "Dummy driver not found. Will try to install xserver-xorg-video-dummy..."
log "Installing xserver-xorg-video-dummy..."
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq xserver-xorg-video-dummy || log "WARNING: apt failed, dummy not installed (VIRTUAL1 should still work)"
sudo apt-get update -qq && sudo apt-get install -y -qq xserver-xorg-video-dummy || true
else
log "WARNING: apt-get not found, skipping dummy driver install"
log "WARNING: apt-get not found, cannot install dummy driver"
exit 1
fi
fi
# --- Create X11 fallback config ---
log "Creating X11 fallback config at $X11_CONF"
echo "retrowest" | sudo -S bash -c 'cat > /etc/X11/xorg.conf.d/90-fallback.conf' <<'EOF'
# Fallback Xorg config: guarantee a framebuffer even without HDMI
# --- Remove old conflicting configs ---
log "Removing old Intel X11 configs"
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
# --- Copy EDID firmware ---
log "Saving EDID firmware"
if [ -f /sys/class/drm/card0-HDMI-A-1/edid ]; then
sudo mkdir -p /lib/firmware/edid
sudo cp /sys/class/drm/card0-HDMI-A-1/edid "$EDID_DST"
sudo chmod 644 "$EDID_DST"
log "EDID saved to $EDID_DST"
else
log "WARNING: No EDID found at /sys/class/drm/card0-HDMI-A-1/edid"
fi
# --- Create dummy headless X11 config ---
log "Creating dummy headless X11 config at $X11_CONF"
cat <<'XORG' | sudo tee "$X11_CONF" >/dev/null
Section "Device"
Identifier "AutoDevice"
Driver "modesetting"
Identifier "DummyHeadless"
Driver "dummy"
Option "ConstantDPI" "true"
VideoRam 256000
EndSection
Section "Monitor"
Identifier "AutoMonitor"
Option "DPMS" "false"
Identifier "DummyMonitor"
HorizSync 28-80
VertRefresh 48-75
EndSection
Section "Screen"
Identifier "AutoScreen"
Device "AutoDevice"
Monitor "AutoMonitor"
Identifier "DummyScreen"
Device "DummyHeadless"
Monitor "DummyMonitor"
DefaultDepth 24
SubSection "Display"
Depth 24
Virtual 1920 1080
Modes "1920x1080"
Modes "1920x1080" "1280x720"
EndSubSection
EndSection
EOF
sudo chmod 644 /etc/X11/xorg.conf.d/90-fallback.conf
XORG
sudo chmod 644 "$X11_CONF"
log "X11 config installed"
# --- Update GRUB for kernel EDID fallback ---
log "Updating GRUB cmdline for EDID fallback"
if [ -f /etc/default/grub ]; then
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT=".*"|GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin"|' /etc/default/grub
sudo update-grub >/dev/null 2>&1 || log "WARNING: update-grub failed"
log "GRUB updated"
else
log "WARNING: /etc/default/grub not found"
fi
# --- Install fallback script ---
log "Installing hdmi-fallback.sh to ~/.local/bin/"
mkdir -p "$HOME/.local/bin"
cat > "$BIN_DST" <<'SCRIPT'
#!/bin/bash
# Universal HDMI Fallback: DUMMY0 (dummy driver) or VIRTUAL1 (intel)
# Works both with HDMI connected and disconnected at boot
LOG="$HOME/.local/logs/drm-hotplug.log"
mkdir -p "$(dirname "$LOG")"
mkdir -p "$HOME/.local/logs"
export DISPLAY=:0
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG" >&1
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG" >/dev/null
}
log "=== Starting VIRTUAL1-primary fallback monitor ==="
MODE="1920x1080_60.00"
if ! xrandr 2>/dev/null | grep -q "$MODE"; then
xrandr --newmode "$MODE" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 2>/dev/null || true
fi
xrandr --addmode VIRTUAL1 "$MODE" 2>/dev/null || true
setup_virtual_primary() {
log "Setting up VIRTUAL1 as primary..."
xrandr --output VIRTUAL1 --mode "$MODE" --primary 2>&1 && log "VIRTUAL1 primary OK" || log "VIRTUAL1 primary failed"
# Find available primary output (DUMMY0 or VIRTUAL1)
find_primary_output() {
if xrandr 2>/dev/null | grep -q "^DUMMY0"; then
echo "DUMMY0"
elif xrandr 2>/dev/null | grep -q "^VIRTUAL1"; then
echo "VIRTUAL1"
else
echo ""
fi
}
enable_hdmi_clone() {
log "Enabling HDMI1 as clone of VIRTUAL1"
xrandr --output HDMI1 --auto --same-as VIRTUAL1 2>&1 && log "HDMI1 cloned OK" || log "HDMI1 clone failed"
# Find HDMI output name (HDMI-1, HDMI1, etc)
find_hdmi_output() {
xrandr 2>/dev/null | grep -E "^HDMI-[0-9] connected" | awk '{print $1}' | head -1
}
disable_hdmi() {
log "HDMI1 disconnected, keeping VIRTUAL1 primary"
xrandr --output HDMI1 --off 2>&1 && log "HDMI1 off OK" || true
# Set up mode for virtual output
setup_mode() {
local OUTPUT="$1"
local MODE_NAME="1920x1080"
if [ "$OUTPUT" = "VIRTUAL1" ]; then
local MODELINE="1920x1080_60.00"
if ! xrandr 2>/dev/null | grep -q "$MODELINE"; then
xrandr --newmode "$MODELINE" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 2>/dev/null || true
fi
xrandr --addmode VIRTUAL1 "$MODELINE" 2>/dev/null || true
MODE_NAME="$MODELINE"
fi
echo "$MODE_NAME"
}
log "Start monitor (user=$USER, display=$DISPLAY)"
CONNECTOR=""
for p in /sys/class/drm/card1-HDMI-A-1 /sys/class/drm/card0-HDMI-A-1; do
[ -e "$p/status" ] && { CONNECTOR="$p"; break; }
done
[ -z "$CONNECTOR" ] && CONNECTOR=$(find /sys/class/drm -maxdepth 1 -name "*HDMI-A-1" -type d | head -1)
if [ -z "$CONNECTOR" ] || [ ! -e "$CONNECTOR/status" ]; then
log "ERROR: No HDMI-A-1 found"
setup_virtual_primary
log "WARNING: No HDMI-A-1 connector found, running headless only"
fi
# Initial state
LAST_STATE=$(cat "$CONNECTOR/status" 2>/dev/null || echo unknown)
log "Initial HDMI: $LAST_STATE"
# Detect outputs
PRIMARY=$(find_primary_output)
HDMI=$(find_hdmi_output)
if [ -z "$PRIMARY" ]; then
log "ERROR: No virtual output found (DUMMY0 or VIRTUAL1)"
exit 1
fi
log "Monitoring: $CONNECTOR"
log "Primary: $PRIMARY, HDMI: ${HDMI:-none}"
setup_virtual_primary
HDMI_STATE=$(cat "$CONNECTOR/status" 2>/dev/null || echo "unknown")
log "Initial HDMI state: $HDMI_STATE"
MODE=$(setup_mode "$PRIMARY")
if [ "$HDMI_STATE" = "connected" ]; then
enable_hdmi_clone
# Initial setup
if [ "$LAST_STATE" = "connected" ] && [ -n "$HDMI" ]; then
log "HDMI connected, setting clone: $PRIMARY primary + $HDMI same-as"
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
xrandr --output "$HDMI" --auto --same-as "$PRIMARY" 2>/dev/null || true
else
disable_hdmi
log "HDMI disconnected or not found, keeping $PRIMARY primary"
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
[ -n "$HDMI" ] && xrandr --output "$HDMI" --off 2>/dev/null || true
fi
log "Initial setup complete. Monitors:"
xrandr --listmonitors 2>&1 | while read line; do log " $line"; done
LAST_STATE="$HDMI_STATE"
log "Setup complete. Monitors:"
xrandr --listmonitors 2>/dev/null | while read -r line; do log " $line"; done
# Main loop
while true; do
CURRENT=$(cat "$CONNECTOR/status" 2>/dev/null || echo "unknown")
CURRENT=$(cat "$CONNECTOR/status" 2>/dev/null || echo unknown)
if [ "$CURRENT" != "$LAST_STATE" ]; then
log "HDMI changed: $LAST_STATE -> $CURRENT"
setup_virtual_primary
if [ "$CURRENT" = "connected" ]; then
enable_hdmi_clone
else
disable_hdmi
# Re-detect
PRIMARY=$(find_primary_output)
HDMI=$(find_hdmi_output)
MODE=$(setup_mode "$PRIMARY")
if [ -z "$PRIMARY" ]; then
log "ERROR: No virtual output detected after HDMI change"
sleep 5
continue
fi
log "Monitor layout after change:"
xrandr --listmonitors 2>&1 | while read line; do log " $line"; done
# Always make PRIMARY primary
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
if [ "$CURRENT" = "connected" ] && [ -n "$HDMI" ]; then
log "HDMI connected, cloning $HDMI to $PRIMARY"
xrandr --output "$HDMI" --auto --same-as "$PRIMARY" 2>/dev/null || true
else
log "HDMI disconnected, keeping $PRIMARY primary"
[ -n "$HDMI" ] && xrandr --output "$HDMI" --off 2>/dev/null || true
fi
LAST_STATE="$CURRENT"
fi
sleep 2
done
SCRIPT
@@ -160,7 +234,7 @@ log "Installing systemd user unit"
mkdir -p "$HOME/.config/systemd/user"
cat > "$SYSTEMD_DST" <<'UNIT'
[Unit]
Description=HDMI to VIRTUAL1 Fallback Monitor
Description=HDMI to Virtual Fallback Monitor
After=graphical-session.target
[Service]
@@ -175,8 +249,8 @@ WantedBy=graphical-session.target
UNIT
systemctl --user daemon-reload
systemctl --user enable --now hdmi-fallback.service
log "Systemd unit enabled and started"
systemctl --user enable --now hdmi-fallback.service 2>/dev/null || true
log "Systemd unit enabled"
# --- Summary ---
echo ""
@@ -185,15 +259,16 @@ echo "RDtop installation complete!"
echo "============================================"
echo ""
echo "Next steps:"
echo "1. Reboot or re-login to apply X11 config"
echo " sudo systemctl restart gdm"
echo "1. REBOOT now for changes to take effect:"
echo " sudo reboot"
echo ""
echo "2. After reboot, verify VIRTUAL1 is primary:"
echo "2. After reboot without HDMI, check:"
echo " xrandr --listmonitors"
echo ""
echo "3. Check service status:"
echo "3. Service status:"
echo " systemctl --user status hdmi-fallback.service"
echo ""
echo "4. To verify: unplug HDMI cable, VIRTUAL1 will stay active"
echo "4. View logs:"
echo " cat ~/.local/logs/drm-hotplug.log"
echo ""
log "Install complete"

56
scripts/README.md Normal file
View File

@@ -0,0 +1,56 @@
# RustDesk Server — Нативная установка на VPS
## Файлы
| Файл | Назначение |
|------|------------|
| `install-rustdesk-server.sh` | Скачивает и устанавливает hbbs + hbbr |
| `rustdesk-hbbs.service` | systemd unit для ID-сервера |
| `rustdesk-hbbr.service` | systemd unit для relay |
| `open-rustdesk-ports.sh` | Открывает порты в UFW |
## Быстрый старт
```bash
# 1. Скопируй файлы на VPS
scp install-rustdesk-server.sh \
rustdesk-hbbs.service \
rustdesk-hbbr.service \
open-rustdesk-ports.sh \
root@2.59.219.61:/root/
# 2. Зайди на VPS
ssh root@2.59.219.61
# 3. Запусти
chmod +x install-rustdesk-server.sh open-rustdesk-ports.sh
./open-rustdesk-ports.sh
./install-rustdesk-server.sh
```
## Порты
| Порт | Протокол | Назначение |
|------|----------|------------|
| 21115 | TCP | NAT type detection |
| 21116 | TCP + UDP | ID registration / hole punching |
| 21117 | TCP | Relay |
| 21118 | TCP | Web console (опционально) |
| 21119 | TCP | Web console HTTPS (опционально) |
## Проверка
```bash
systemctl status rustdesk-hbbs rustdesk-hbbr
ss -tlnp | grep -E '21115|21116|21117'
journalctl -u rustdesk-hbbs -f
cat /var/lib/rustdesk-server/id_ed25519.pub
```
## Настройка клиента
1. Открой RustDesk на VPS
2. Нажми «Настройки сети» → «ID-сервер»
3. Вставь IP VPS и порт: `2.59.219.61:21116`
4. Вставь ключ из `/var/lib/rustdesk-server/id_ed25519.pub`
5. Готово — ID теперь локальный и постоянный

146
scripts/host/README-host.md Normal file
View File

@@ -0,0 +1,146 @@
# RustDesk Host Setup — README
**Цель:** зафиксировать RustDesk ID на хосте Intel Alder Lake-N (Debian 12, GNOME headless, dummy-драйвер) и защитить конфиг от перезаписи.
---
## Содержимое
| Файл | Назначение |
|------|-----------|
| `setup-host-rustdesk.sh` | Основной скрипт: проверяет dummy Xorg, создаёт/защищает `RustDesk.toml`, создаёт `write-rustdesk-config.sh`, настраивает systemd-сервис |
| `write-rustdesk-config.sh` | Экстренный скрипт: снимает `chattr +i`, перезаписывает конфиг, ставит защиту обратно |
| `setup-host-rustdesk.log` | Лог, создаётся автоматически при запуске основного скрипта |
---
## Как применить
### Шаг 1: Скопировать файлы на хост
**Вариант A: USB / вручную**
```bash
# На VPS (где вы сейчас)
# Файлы уже находятся в папке scripts/host/. Скопируйте их на USB и перенесите на хост.
# Или если есть любой файловый доступ — просто перенесите.
```
**Вариант B: scp (если есть любой сетевой доступ к хосту)**
```bash
# С хоста или откуда есть доступ:
scp setup-host-rustdesk.sh user@host-ip:/tmp/
ssh user@host-ip "sudo bash /tmp/setup-host-rustdesk.sh"
```
**Вариант C: curl через временный HTTP-сервер**
```bash
# На промежуточной машине:
python3 -m http.server 8080 --directory /path/to/scripts/host/
# На хосте:
curl -O http://PROMO-IP:8080/setup-host-rustdesk.sh
sudo bash setup-host-rustdesk.sh
```
### Шаг 2: Запустить основной скрипт
```bash
sudo bash setup-host-rustdesk.sh
```
Скрипт должен выполниться полностью без ошибок.
---
## Что проверить после применения
### 1. RustDesk ID
```bash
rustdesk --get-id
```
Ожидаемый результат: 9-значный номер (например, `458564614` на VPS). На хосте будет **другой** ID — это нормально. Главное, чтобы он **не менялся после перезагрузки**.
### 2. Конфиг защищён
```bash
lsattr /home/ИМЯПОЛЬЗОВАТЕЛЯ/.config/rustdesk/RustDesk.toml
```
Ожидаемый результат: флаг `i` (immutable), например:
```
---- i --------- /home/user/.config/rustdesk/RustDesk.toml
```
### 3. Сервис активен
```bash
systemctl is-active rustdesk-client-local
```
Ожидаемый результат: `active`
### 4. Dummy монитор работает
```bash
DISPLAY=:1 xrandr --listmonitors
```
Ожидаемый результат: строка с `DUMMY0`
---
## Что делать, если ID всё равно меняется
### Вариант 1: RustDesk не успел сгенерировать ID перед chattr
Если ID меняется, скорее всего RustDesk создал новый файл конфигурации в другом месте или перезаписал его через другой процесс.
**Проверьте:**
```bash
find / -name "RustDesk.toml" 2>/dev/null
```
Если найдено несколько — значит RustDesk использует не тот.
Правильный: `/home/ИМЯПОЛЬЗОВАТЕЛЯ/.config/rustdesk/RustDesk.toml`
### Вариант 2: RustDesk запущен без DISPLAY
Для генерации ID RustDesk **должен** запуститься с GUI (даже dummy). Убедитесь, что `xorg-dummy.service` запущен **до** rustdesk-client-local:
```bash
systemctl status xorg-dummy
systemctl status rustdesk-client-local
```
Если `xorg-dummy` неактивен — RustDesk может работать в CLI-режиме и генерировать новый ID каждый раз.
### Вариант 3: Файл отсутствовал при первом запуске скрипта
Если вы увидели в логе:
```
WARN: RustDesk.toml ОТСУТСТВУЕТ, и rustdesk --get-id не вернул ID.
```
Нужно:
1. Запустить RustDesk вручную с GUI один раз (чтобы он создал конфиг)
2. Затем применить защиту:
```bash
sudo chattr -i /home/user/.config/rustdesk/RustDesk.toml # если уже ставили
sudo chattr +i /home/user/.config/rustdesk/RustDesk.toml # защитить
```
---
## Предупреждения
⚠️ **chattr +i** блокирует **любую** запись в файл, в том числе от RustDesk. Это цель — зафиксировать ID. Но если нужно поменять сервер или другие настройки — используйте `write-rustdesk-config.sh`.
⚠️ Скрипт полагается на то, что:
- `rustdesk` установлен и доступен в `/usr/bin/rustdesk`
- Пользователь, под которым работает GNOME, называется `user` (или задайте `USER=... USER_HOME=...` перед запуском)
- Dummy-драйвер уже сконфигурирован (из предыдущих сессий)
⚠️ **Экстренный write-скрипт**:
```bash
sudo bash write-rustdesk-config.sh myusername
```
Он временно снимает `chattr +i`, перезаписывает файл и ставит защиту обратно.
---
## Логи
Все действия основного скрипта пишутся в:
```
./setup-host-rustdesk.log
```
При возникновении проблем — приложите этот файл.

View File

@@ -0,0 +1,43 @@
#!/bin/bash
# RustDesk Client Launcher for VPS (Manual Run)
# Run this script when you want to connect to host from VPS
# 1. Ensure no conflicting X servers
pkill -f Xvfb
pkill -f Xorg
# 2. Start Xvfb on :0
rm -f /tmp/.X11-unix/X0 /tmp/.X0-lock 2>/dev/null
/usr/bin/Xvfb :0 -screen 0 1920x1080x24 +extension GLX +extension RANDR +extension RENDER -ac &
echo "Xvfb started on :0"
sleep 2
# 3. Configure RustDesk
cat > ~/.config/rustdesk/RustDesk2.toml <<'EOF'
rendezvous_server = 'localhost:21116'
custom-rendezvous-server = 'localhost:21116'
key = 'JDSXd9sxN6Y7mIzu3krSpPTTc4yjChfqpzgIH5fl9Iw='
nat_type = 1
serial = 0
unlock_pin = ''
[options]
local-ip-addr = '2.59.219.61'
EOF
# 4. Start RustDesk client
export DISPLAY=:0
export XAUTHORITY=/root/.Xauthority
export LIBVA_DRIVER_NAME=none
export VDPAU_DRIVER=none
# Add xauth for display :0
xauth -f "$XAUTHORITY" add :0 . $(mcookie) 2>/dev/null || true
echo "Starting RustDesk..."
nohup /usr/share/rustdesk/rustdesk &
echo "RustDesk started. Check ID: DISPLAY=:0 /usr/share/rustdesk/rustdesk --get-id"
sleep 2
echo "Current ID:"
DISPLAY=:0 /usr/share/rustdesk/rustdesk --get-id

View File

@@ -0,0 +1,207 @@
#!/usr/bin/env bash
set -euo pipefail
#######################################
# setup-host-rustdesk.sh
# Защита RustDesk ID на хосте Intel Alder Lake-N
# Запускать от root
#######################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG="$SCRIPT_DIR/setup-host-rustdesk.log"
USER_HOME="${USER_HOME:-/home/user}"
USER="${USER:-user}"
RUSTDESK_DIR="$USER_HOME/.config/rustdesk"
CONFIG_FILE="$RUSTDESK_DIR/RustDesk.toml"
log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG"; }
warn() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $1" | tee -a "$LOG" >&2; }
fail() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] FATAL: $1" | tee -a "$LOG" >&2; exit 1; }
[[ $EUID -ne 0 ]] && fail "Этот скрипт нужно запускать от root: sudo bash setup-host-rustdesk.sh"
log "========== Начало настройки RustDesk на хосте =========="
log "USER_HOME=$USER_HOME | USER=$USER"
# 1. Проверить dummy Xorg (GNOME с dummy-драйвером должен показывать монитор)
log "--- Шаг 1: Проверка dummy Xorg ---"
# systemd-сервис xorg-dummy уже должен быть создан пользователем или предыдущими сессиями
if systemctl is-active --quiet xorg-dummy 2>/dev/null; then
log "xorg-dummy.service: ACTIVE"
elif systemctl is-active --quiet xorg-dummy@"$USER" 2>/dev/null; then
log "xorg-dummy@$USER.service: ACTIVE"
else
warn "xorg-dummy.service не найден в активных юнитах. Это может быть нормально, если драйвер загружен иначе."
fi
# Проверяем через xrandr у активного пользователя (display :1 для dummy)
if DISPLAY=:1 xrandr --listmonitors 2>/dev/null | grep -qi "DUMMY\|dummy"; then
log "xrandr: dummy монитор обнаружен (DISPLAY=:1)"
else
warn "xrandr не нашел DUMMY монитор на DISPLAY=:1. Попробуйте DISPLAY=:0"
fi
log "--- Шаг 2: Статус локального клиента RustDesk --"
if systemctl is-active --quiet rustdesk-client-local 2>/dev/null; then
log "rustdesk-client-local.service: ACTIVE"
else
warn "rustdesk-client-local.service: NOT ACTIVE — будет создан/включён ниже"
fi
# 2. Проверить/создать RustDesk.toml и защитить его
log "--- Шаг 3: Проверка/создание RustDesk.toml ---"
mkdir -p "$RUSTDESK_DIR"
if [[ -f "$CONFIG_FILE" ]]; then
log "RustDesk.toml найден: $CONFIG_FILE"
# Показать текущий ID
CURRENT_ID=$(rustdesk --get-id 2>/dev/null || echo "N/A")
log "Текущий RustDesk ID: $CURRENT_ID"
# Убедиться что в конфиге нужный сервер
if grep -q 'custom-rendezvous-server' "$CONFIG_FILE"; then
log "custom-rendezvous-server уже задан в конфиге"
else
log "custom-rendezvous-server НЕ задан — добавляем публичный сервер"
cat >> "$CONFIG_FILE" <<'TOML'
[options]
custom-rendezvous-server = "rs-ny.rustdesk.com:21116"
TOML
fi
else
CURRENT_ID=$(rustdesk --get-id 2>/dev/null || echo "")
if [[ -z "$CURRENT_ID" ]]; then
warn "RustDesk.toml ОТСУТСТВУЕТ, и rustdesk --get-id не вернул ID."
warn "Возможно, RustDesk ещё ни разу не запускался с GUI (нужен xorg-dummy)."
warn "Если ID ещё не сгенерирован — создайте конфиг вручную:"
warn " mkdir -p $RUSTDESK_DIR && cat > $CONFIG_FILE <<'TOML'"
warn " [options]"
warn " custom-rendezvous-server = \"rs-ny.rustdesk.com:21116\""
warn " TOML"
warn "Затем перезапустите rustdesk-client-local и повторите скрипт."
else
log "ID получен ($CURRENT_ID), но RustDesk.toml отсутствует — создаём из текущих настроек"
cat > "$CONFIG_FILE" <<TOML
[options]
custom-rendezvous-server = "rs-ny.rustdesk.com:21116"
TOML
fi
fi
# Правильные права
chown -R "$USER:$USER" "$RUSTDESK_DIR"
chmod 600 "$CONFIG_FILE"
# 3. Защита chattr +i (только root)
log "--- Шаг 4: Применение chattr +i к RustDesk.toml ---"
if [[ -f "$CONFIG_FILE" ]]; then
chattr +i "$CONFIG_FILE"
log "chattr +i применён к $CONFIG_FILE"
lsattr "$CONFIG_FILE" | tee -a "$LOG"
else
warn "Файл $CONFIG_FILE не существует — chattr +i НЕ применён!"
warn "Сначала убедитесь, что RustDesk сгенерировал конфигурацию."
fi
# 4. Создать/обновить write-rustdesk-config.sh (для экстренной перезаписи)
WRITE_SCRIPT="$SCRIPT_DIR/write-rustdesk-config.sh"
log "--- Шаг 5: Создание write-rustdesk-config.sh ---"
cat > "$WRITE_SCRIPT" <<'SCRIPT'
#!/usr/bin/env bash
# Этот скрипт временно снимает защиту, перезаписывает конфиг и ставит защиту обратно.
# Запускать от root только если понимаете зачем.
set -euo pipefail
USER="${1:-user}"
USER_HOME="/home/$USER"
CONFIG_FILE="$USER_HOME/.config/rustdesk/RustDesk.toml"
[[ $EUID -ne 0 ]] && { echo "Run as root"; exit 1; }
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Файл $CONFIG_FILE не найден. Нечего перезаписывать."
exit 1
fi
echo "Снимаем immutable..."
chattr -i "$CONFIG_FILE"
echo "Перезаписываем RustDesk.toml..."
cat > "$CONFIG_FILE" <<'TOML'
[options]
custom-rendezvous-server = "rs-ny.rustdesk.com:21116"
TOML
chown "$USER:$USER" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
echo "Ставим immutable обратно..."
chattr +i "$CONFIG_FILE"
lsattr "$CONFIG_FILE"
echo "Готово."
SCRIPT
chmod +x "$WRITE_SCRIPT"
chown root:root "$WRITE_SCRIPT"
log "write-rustdesk-config.sh создан: $WRITE_SCRIPT"
# 5. Создать/обновить systemd-сервис rustdesk-client-local
SERVICE_FILE="/etc/systemd/system/rustdesk-client-local.service"
log "--- Шаг 6: Создание systemd unit rustdesk-client-local.service ---"
if [[ -f "$SERVICE_FILE" ]]; then
cp "$SERVICE_FILE" "$SERVICE_FILE.bak.$(date +%s)"
log "Backup создан: $SERVICE_FILE.bak.*"
fi
cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=RustDesk Client Local (headless host)
After=network.target xorg-dummy.service
Wants=xorg-dummy.service
[Service]
Type=simple
User=$USER
Environment=DISPLAY=:1
Environment=XAUTHORITY=$USER_HOME/.Xauthority
WorkingDirectory=$USER_HOME
ExecStart=/usr/bin/rustdesk --tray
Restart=always
RestartSec=5
StartLimitInterval=60
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now rustdesk-client-local.service || true
log "rustdesk-client-local.service: enabled & restarted"
# 6. Текущий ID
log "--- Шаг 7: Проверка RustDesk ID хоста ---"
ID=$(rustdesk --get-id 2>/dev/null || echo "")
if [[ -n "$ID" ]]; then
log "ТЕКУЩИЙ RUSTDESK ID: $ID"
else
warn "Не удалось получить ID (rustdesk --get-id вернул пустоту)"
warn "Убедитесь, что rustdesk установлен и systemd-сервис запущён."
fi
# Summary
log "========== Результат =========="
log "RustDesk ID: ${ID:-пусто}"
log "Конфиг защищён chattr: $(lsattr "$CONFIG_FILE" 2>/dev/null || echo 'не удалось проверить')"
log "systemd unit: $(systemctl is-active rustdesk-client-local 2>/dev/null || echo 'неактивен')"
log "Записи в systemd journal: journalctl -u rustdesk-client-local -f"
log "Лог скрипта: $LOG"
log "Готово."

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -euo pipefail
# RustDesk Server Native Installer (hbbs + hbbr)
# Debian 12 / x86_64 / static musl binaries from GitHub releases
# Idempotent
VERSION="1.1.15"
ARCH="amd64"
ZIP_URL="https://github.com/rustdesk/rustdesk-server/releases/download/${VERSION}/rustdesk-server-linux-${ARCH}.zip"
INSTALL_DIR="/usr/local/bin"
DATA_DIR="/var/lib/rustdesk-server"
CONFIG_DIR="/etc/rustdesk-server"
SERVICE_DIR="/etc/systemd/system"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
log() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $*"
}
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
log "Installing: $1"
apt-get update -qq
apt-get install -y -qq "$1"
fi
}
log "=== RustDesk Server Install ==="
log "Version: $VERSION"
log "Arch: $ARCH"
need_cmd curl
need_cmd unzip
log "Downloading release $VERSION..."
curl -fsSL -o "$TMP_DIR/rustdesk-server.zip" "$ZIP_URL"
log "Extracting..."
unzip -q -o "$TMP_DIR/rustdesk-server.zip" -d "$TMP_DIR"
log "Installing binaries to $INSTALL_DIR..."
install -m 755 "$TMP_DIR/$ARCH/hbbs" "$INSTALL_DIR/hbbs"
install -m 755 "$TMP_DIR/$ARCH/hbbr" "$INSTALL_DIR/hbbr"
install -m 755 "$TMP_DIR/$ARCH/rustdesk-utils" "$INSTALL_DIR/rustdesk-utils"
log "Creating directories..."
mkdir -p "$DATA_DIR" "$CONFIG_DIR"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for service in rustdesk-hbbs.service rustdesk-hbbr.service; do
src="$SCRIPT_DIR/$service"
dst="$SERVICE_DIR/$service"
if [ -f "$src" ]; then
cp -f "$src" "$dst"
chmod 644 "$dst"
log "Installed: $dst"
else
log "WARNING: $src not found — ensure systemd unit files are in the same directory."
fi
done
log "Reloading systemd..."
systemctl daemon-reload
log "Enabling services..."
systemctl enable rustdesk-hbbs.service || true
systemctl enable rustdesk-hbbr.service || true
log "Starting services..."
systemctl restart rustdesk-hbbs.service || true
systemctl restart rustdesk-hbbr.service || true
sleep 2
log "=== Status ==="
systemctl status rustdesk-hbbs.service --no-pager || true
systemctl status rustdesk-hbbr.service --no-pager || true
PUB_KEY="$DATA_DIR/id_ed25519.pub"
if [ -f "$PUB_KEY" ]; then
log "=== Server Public Key ==="
cat "$PUB_KEY"
else
log "Public key not found yet. It will be generated on first successful hbbs start."
fi
log "=== Listening Ports ==="
ss -tlnp | grep -E '21115|21116|21117' || true
log "=== Done ==="

39
scripts/open-rustdesk-ports.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
log() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $*"
}
if ! command -v ufw >/dev/null 2>&1; then
log "UFW not installed. Installing..."
apt-get update -qq
apt-get install -y -qq ufw
fi
UFW_STATUS=$(ufw status numbered 2>/dev/null || true)
open_port() {
local port=$1
local proto=${2:-tcp}
local rule="$port/$proto"
if echo "$UFW_STATUS" | grep -q "$rule"; then
log "Already open: $rule"
else
log "Opening: $rule"
ufw allow "$rule"
fi
}
log "=== Opening RustDesk Server ports ==="
open_port 21115 tcp
open_port 21116 tcp
open_port 21116 udp
open_port 21117 tcp
open_port 21118 tcp
open_port 21119 tcp
log "=== UFW status ==="
ufw status verbose
log "=== Done ==="

View File

@@ -0,0 +1,17 @@
[Unit]
Description=RustDesk Client (local hbbs)
After=xorg-dummy.service network.target
[Service]
Type=simple
User=root
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/root/.Xauthority"
ExecStartPre=/root/write-rustdesk-config.sh
ExecStartPre=/bin/sleep 2
ExecStart=/usr/share/rustdesk/rustdesk --server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
[Unit]
Description=RustDesk Server (hbbr — relay)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/hbbr
WorkingDirectory=/var/lib/rustdesk-server
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/rustdesk-server
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rustdesk-hbbr
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
[Unit]
Description=RustDesk Server (hbbs — ID / NAT relay)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/hbbs
WorkingDirectory=/var/lib/rustdesk-server
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/rustdesk-server
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rustdesk-hbbs
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,56 @@
#!/bin/bash
# RustDesk VPS - запуск с Xvfb + xcompmgr для корректного GUI рендеринга
export DISPLAY=:0
export XAUTHORITY=/root/.Xauthority
export LIBGL_ALWAYS_SOFTWARE=1
export LIBVA_DRIVER_NAME=none
export VDPAU_DRIVER=none
# 1. Очищаем старые сессии
pkill -f Xvfb
pkill -f rustdesk
sleep 1
rm -f /tmp/.X11-unix/X0 /tmp/.X0-lock 2>/dev/null
# 2. Запускаем Xvfb с COMPOSITE extension
echo "Starting Xvfb..."
/usr/bin/Xvfb :0 -screen 0 1920x1080x24 +extension GLX +extension RANDR +extension RENDER +extension COMPOSITE -ac &
echo "Xvfb PID: $!"
sleep 2
# 3. Проверяем запуск Xvfb
if ! pgrep -f "Xvfb :0" >/dev/null; then
echo "ERROR: Xvfb failed to start"
exit 1
fi
echo "Xvfb OK"
# 4. Запускаем композитор
echo "Starting xcompmgr..."
if command -v xcompmgr >/dev/null 2>&1; then
/usr/bin/xcompmgr -d :0 &
else
echo "WARNING: xcompmgr not found. Install with: apt-get install -y xcompmgr"
fi
# 5. Настраиваем xauth
xauth -f "$XAUTHORITY" add :0 . $(mcookie) 2>/dev/null || true
chmod 600 "$XAUTHORITY"
# 6. Пишем конфиг
/root/write-rustdesk-config.sh
# 7. Запускаем RustDesk
echo "Starting RustDesk..."
nohup /usr/share/rustdesk/rustdesk &
sleep 5
echo "=== Status ==="
echo "Xvfb: $(pgrep -a Xvfb | grep :0 || echo 'NOT RUNNING')"
echo "RustDesk:"
pgrep -a rustdesk || echo 'NOT RUNNING'
echo ""
echo "RustDesk ID: $(/usr/share/rustdesk/rustdesk --get-id 2>/dev/null || echo 'N/A')"
echo ""
echo "To connect to host: rustdesk <host-id>"

View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
CONFIG="/root/.config/rustdesk/RustDesk2.toml"
printf "%s\n" \
"rendezvous_server = 'localhost:21116'" \
"custom-rendezvous-server = 'localhost:21116'" \
"key = 'JDSXd9sxN6Y7mIzu3krSpPTTc4yjChfqpzgIH5fl9Iw='" \
"nat_type = 1" \
"serial = 0" \
"unlock_pin = ''" \
"" \
"[options]" \
"local-ip-addr = '2.59.219.61'" \
> "$CONFIG"
chmod 600 "$CONFIG"
echo "Config written to $CONFIG"