- 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
355 lines
14 KiB
Markdown
355 lines
14 KiB
Markdown
# RDtop — RustDesk Headless Display Setup
|
||
|
||
**Автоматический виртуальный дисплей для RustDesk без подключенного HDMI монитора. Работает на любой Linux машине (Intel iGPU, AMD, NVIDIA, CPU-only VPS).**
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Проблема](#проблема)
|
||
2. [Решение](#решение)
|
||
3. [Файлы проекта](#файлы-проекта)
|
||
4. [Установка](#установка)
|
||
5. [Как это работает](#как-это-работает)
|
||
6. [Проверка](#проверка)
|
||
7. [Troubleshooting](#troubleshooting)
|
||
8. [Откат](#откат)
|
||
|
||
---
|
||
|
||
## Проблема
|
||
|
||
Оборудование: **Intel Alder Lake-N (i915)**, Ubuntu 24.04, X11 (GDM/GNOME).
|
||
|
||
### Что происходит
|
||
|
||
1. При подключенном HDMI — Xorg инициализирует `HDMI-1` на отдельный CRTC.
|
||
2. Когда кабель отключают — **CRTC уничтожается**.
|
||
3. У Xorg не остается активного выхода. Может остаться черный экран или fallback 8×8.
|
||
4. RustDesk все еще подключен, но захватывает `Screen 0` — и видит либо черный экран, либо 8×8 пикселей.
|
||
|
||
### Почему стандартные способы не помогли
|
||
|
||
| Способ | Результат |
|
||
|--------|-----------|
|
||
| `Option "VirtualHeads" "1"` (intel driver) | **Работает только с кабелем на момент загрузки.** Без HDMI — VIRTUAL1 не создается. |
|
||
| modesetting driver | Создает VIRTUAL1 через randr, но после reboot пропадает. |
|
||
| `NoOutputInitialSize` | Требует пересборки xorg или патчей. |
|
||
| Dummy plug (аппаратный) | Работает, но нужно покупать. |
|
||
|
||
---
|
||
|
||
## Решение
|
||
|
||
### Хост: Dummy driver + kernel EDID fallback
|
||
|
||
Работает **без кабеля HDMI на момент загрузки** — dummy driver не зависит от физических выходов.
|
||
|
||
```
|
||
+------------------------------------------------------------------------+
|
||
| Xorg Dummy Driver (1920×1080) |
|
||
| +-------------------------+ |
|
||
| | DUMMY0 = primary | ← RustDesk --server захватывает |
|
||
| | 1920×1080 @ 60Hz | |
|
||
| +-------------------------+ |
|
||
| |
|
||
| При подключении HDMI кабеля: |
|
||
| скрипт hdmi-fallback.sh клонирует HDMI1 на DUMMY0 |
|
||
| При отключении: HDMI1 off, DUMMY0 остается primary |
|
||
+------------------------------------------------------------------------+
|
||
```
|
||
|
||
### 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 и авто-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` | Установщик для хоста | (запускается один раз) |
|
||
|
||
---
|
||
|
||
## Установка
|
||
|
||
### Хост (Desktop с GPU)
|
||
|
||
```bash
|
||
bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
|
||
```
|
||
|
||
После установки **перезагрузить компьютер**:
|
||
```bash
|
||
sudo reboot
|
||
```
|
||
|
||
**Вручную:**
|
||
```bash
|
||
# 1. Клонируем
|
||
git clone https://git.softuniq.eu/NW/RDtop.git ~/RDtop && cd ~/RDtop
|
||
|
||
# 2. Dummy driver config (главный способ)
|
||
sudo cp config/x11-host/20-dummy-headless.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. Сохранить 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/
|
||
|
||
# 7. systemd unit
|
||
mkdir -p ~/.config/systemd/user
|
||
cp config/systemd/hdmi-fallback.service ~/.config/systemd/user/
|
||
systemctl --user daemon-reload
|
||
systemctl --user enable --now hdmi-fallback.service
|
||
|
||
# 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
|
||
```
|
||
|
||
---
|
||
|
||
## Как это работает
|
||
|
||
### Хост (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 цифр + пароль
|
||
|
||
```
|
||
+------------------------------------------------------------------------+
|
||
| Xvfb :99 (1920x1080x24) |
|
||
| +-------------------------------------------------------+ |
|
||
| | XFCE Desktop (xfwm4 + xfce4-panel + xfdesktop) | |
|
||
| | +---------------------------------------------+ | |
|
||
| | | [Панель XFCE] [Иконки] [Контекстное меню]| | |
|
||
| | +---------------------------------------------+ | |
|
||
| | ← RustDesk client захватывает экран через X11 | |
|
||
| +-------------------------------------------------------+ |
|
||
| SSH / tmux (персистентность) |
|
||
+------------------------------------------------------------------------+
|
||
```
|
||
|
||
**Персистентность:** `tmux` (не systemd) — `pkill` в ExecStartPre убивал Xvfb и xfce.
|
||
|
||
---
|
||
|
||
## Проверка
|
||
|
||
### Хост (после reboot без HDMI)
|
||
```bash
|
||
# Должно показать 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: +*DUMMY0 1920x... DUMMY0 [PRIMARY]
|
||
|
||
# RustDesk
|
||
ps aux | grep rustdesk
|
||
# /usr/lib/rustdesk/rustdesk --server должен быть запущен
|
||
```
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### DUMMY0 не появляется после reboot
|
||
|
||
```bash
|
||
# Проверить 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 черный экран
|
||
|
||
```bash
|
||
# Проверить primary:
|
||
xrandr --verbose | grep primary
|
||
# Должно быть: DUMMY0 connected primary
|
||
|
||
# Если primary не DUMMY0:
|
||
xrandr --output DUMMY0 --mode "1920x1080" --primary
|
||
xrandr --output HDMI-1 --auto --same-as DUMMY0
|
||
```
|
||
|
||
### VPS: RustDesk не видит DUMMY0
|
||
|
||
```bash
|
||
# Проверить что Xorg запущен:
|
||
ls /tmp/.X11-unix/X0
|
||
# Должен быть сокет
|
||
|
||
# Если нет — запустить вручную:
|
||
/usr/local/bin/start-xorg-dummy.sh
|
||
```
|
||
|
||
---
|
||
|
||
## Откат
|
||
|
||
```bash
|
||
# Хост
|
||
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
|
||
systemctl --user disable --now hdmi-fallback.service
|
||
rm -f ~/.local/bin/hdmi-fallback.sh
|
||
|
||
# Вернуть стандартный 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?
|
||
|
||
`intel` driver + `VirtualHeads=1` работает **только если HDMI подключен при загрузке**. Без кабеля — ядро не создает connector/CRTC для HDMI-A-1, и `VirtualHeads` не помогает. Dummy driver не зависит от физических выходов.
|
||
|
||
### Почему kernel EDID fallback?
|
||
|
||
Параметры `video=HDMI-A-1:1920x1080@60` и `drm.edid_firmware=HDMI-A-1:edid/samsung.bin` заставляют ядро `i915` инициализировать connector с фиктивным EDID. Это "страховка": если dummy driver по какой-то причине не загрузится (например, обновление Xorg удалит его), ядро все равно создаст выход.
|
||
|
||
### Почему клон, а не extended desktop?
|
||
|
||
`--same-as` клонирует framebuffer. При disconnect HDMI clone просто перестает рендериться, но primary (DUMMY0) не затрагивается — Xorg не теряет framebuffer.
|
||
|
||
### Dummy driver на VPS
|
||
|
||
Не требует GPU, не использует DRM. Работает на любом Linux с xorg-server. RustDesk использует software capture через X11 API.
|
||
|
||
---
|
||
|
||
## Авторы
|
||
|
||
- 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
|
||
|
||
## Ссылки
|
||
|
||
- Репозиторий: `https://git.softuniq.eu/NW/RDtop`
|
||
- Установщик: `bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"`
|