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
This commit is contained in:
Deploy Bot
2026-05-14 09:29:08 +01:00
parent 4d4b04d7bb
commit a0b2b5ebf7
5 changed files with 231 additions and 201 deletions

353
README.md
View File

@@ -1,251 +1,246 @@
# RDtop — RustDesk Headless Display Setup
**Автоматический виртуальный дисплей для RustDesk на Intel iGPU без подключённого HDMI монитора.**
**Автоматический виртуальный дисплей для RustDesk без подключенного HDMI монитора. Работает на Intel iGPU и CPU-only VPS.**
---
## Содержание
1. [Кратко о проблеме](#кратко-о-проблеме)
2. [Архитектура решения](#архитектура-решения)
3. [Что где лежит и куда ставить](#что-где-лежит-и-куда-ставить)
1. [Проблема](#проблема)
2. [Решение](#решение)
3. [Файлы проекта](#файлы-проекта)
4. [Установка](#установка)
5. [Как это работает на самом деле](#как-это-работает-на-самом-деле)
- [Вариант А: Десктоп с Intel iGPU](#вариант-а-десктоп-с-intel-igpu)
- [Вариант Б: CPU-only VPS](#вариант-б-cpu-only-vps)
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) | **Работает!** Но требует `Driver "intel"` + `VirtualHeads` в `xorg.conf`. |
| modesetting driver | Создает VIRTUAL1 через randr, но после reboot пропадает без `intel` config. |
| `NoOutputInitialSize` | Требует пересборки xorg или патчей. |
| Dummy plug (аппаратный) | Работает, но нужно покупать. Для программного режима — нет простого способа. |
| Dummy plug (аппаратный) | Работает, но нужно покупать. |
---
## Архитектура решения
## Решение
### Хост (Intel iGPU): VIRTUAL1 = primary, HDMI1 = clone
```
+-----------------------------------------------------------------------------+
| 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 или нет. |
+-----------------------------------------------------------------------------+
+------------------------------------------------------------------------+
| X11 Screen 0: 1920x1080 |
| |
| +-------------------------+ +------------------------------------+ |
| | VIRTUAL1 | | HDMI1 | |
| | [PRIMARY] 1920×1080 | | [CLONE --same-as VIRTUAL1] | |
| | Всегда активен | | При отключении → off | |
| +-------------------------+ +------------------------------------+ |
| |
| RustDesk: всегда захватывает PRIMARY (VIRTUAL1) |
+------------------------------------------------------------------------+
```
**Ключевой принцип:** `VIRTUAL1 = primary`. HDMI1 — это просто клон. Когда HDMI1 отключается, его CRTC удаляется, но primary остаётся — RustDesk всегда видит рабочий стол.
### VPS (CPU-only): Dummy driver + Xorg + RustDesk
```
+------------------------------------------------------------------------+
| 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 на VIRTUAL1 | `~/.local/bin/hdmi-fallback.sh` |
| `bin/vps-start-xorg.sh` | Стартер Xorg с dummy driver на VPS | `/usr/local/bin/start-xorg-dummy.sh` |
| `config/x11/90-fallback.conf` | Xorg fallback: Virtual 1920 1080 для modesetting | `/etc/X11/xorg.conf.d/90-fallback.conf` |
| `config/x11-host/20-intel-virtual.conf` | Intel driver + VirtualHeads=1 | `/etc/X11/xorg.conf.d/20-intel-virtual.conf` |
| `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: Одной командой
### Вариант А: Десктоп с Intel iGPU
```bash
bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
```
После установки **перезагрузить компьютер** или перезапустить GDM:
```bash
sudo systemctl restart gdm
```
### Способ 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. Intel driver + VirtualHeads
sudo cp config/x11-host/20-intel-virtual.conf /etc/X11/xorg.conf.d/
# 3. Копируем X11 конфиг
sudo mkdir -p /etc/X11/xorg.conf.d
# 3. Xorg fallback (резерв)
sudo cp config/x11/90-fallback.conf /etc/X11/xorg.conf.d/
# 4. Устанавливаем скрипт
mkdir -p ~/.local/bin
# 4. Скрипт мониторинга
chmod +x bin/hdmi-fallback.sh
cp bin/hdmi-fallback.sh ~/.local/bin/
# 5. Устанавливаем systemd unit
# 5. 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)
# 6. Перезагрузка
sudo systemctl restart gdm
```
### Вариант Б: 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/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. Запустить
systemctl daemon-reload
systemctl enable --now xorg-dummy.service
systemctl enable --now rustdesk-dummy.service
# 4. Проверить
export DISPLAY=:0
xrandr --listmonitors
# Должно показать: DUMMY0 primary 1920x1080
```
---
## Как это работает на самом деле
## Как это работает
### Шаг 1. Xorg fallback конфиг (`90-fallback.conf`)
### Хост (Intel iGPU)
```
Section "Screen"
Identifier "AutoScreen"
...
SubSection "Display"
Depth 24
Virtual 1920 1080
EndSubSection
EndSection
```
1. **Xorg конфиг** `20-intel-virtual.conf` указывает `Driver "intel"` + `Option "VirtualHeads" "1"`.
2. **Intel driver** создает VIRTUAL1 как software output (проверено на Alder Lake-N).
3. **Скрипт** `hdmi-fallback.sh` при старте:
- Всегда активирует VIRTUAL1 как primary
- Если HDMI connected — делает HDMI1 clone (`--same-as`)
- Если HDMI disconnected — выключает HDMI1, VIRTUAL1 остается primary
4. **RustDesk** захватывает VIRTUAL1 (primary), а не HDMI1.
Параметр `Virtual 1920 1080` задаёт размер виртуального framebuffer на уровне экрана X11. Даже если физических выхода нет, Xorg создаёт `Screen` фиксированного размера. **Без этого при отключённом HDMI Xorg мог задать fallback resolution 8×8 или не инициализировать экран вовсе.**
### VPS (CPU-only)
### Шаг 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 работает стабильнее.
1. **Dummy driver** (`Driver "dummy"`) не требует GPU, работает на чистом CPU.
2. **Xorg** запускается без DRM, создает DUMMY0 1920×1080.
3. **RustDesk `--server`** захватывает DUMMY0 через X11 screen capture.
4. **Нет GUI оболочки** по умолчанию — если нужен рабочий стол, установить `xfce4` + `tightvncserver`.
---
## Проверка
### Хост
```bash
# 1. После перезагрузки — проверяем primary output
# После перезагрузки
xrandr --listmonitors
# Должно быть:
# 0: +*VIRTUAL1 1920/508x1080/286+0+0 VIRTUAL1 [PRIMARY]
# 1: +HDMI1 1920/700x1080/390+0+0 HDMI1 [CLONE]
# 0: +*VIRTUAL1 1920x... VIRTUAL1 [PRIMARY]
# 1: +HDMI1 1920x... HDMI1 [CLONE]
# 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
### VPS
```bash
export DISPLAY=:0
xrandr --listmonitors
# Должно быть:
# 0: +*DUMMY0 1920x... DUMMY0 [PRIMARY]
# 4. Тест: вытаскиваем HDMI
# Через 2-3 секунды xrandr должен показывать только VIRTUAL1,
# при этом RustDesk продолжает работать.
# RustDesk
ps aux | grep rustdesk
# /usr/lib/rustdesk/rustdesk --server должен быть запущен
```
---
## Troubleshooting
### VIRTUAL1 не появляется в xrandr
### VIRTUAL1 не появляется после 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"
# Проверить intel driver:
grep "Loading.*intel" /var/log/Xorg.0.log
# Должно быть: (II) Loading /usr/lib/xorg/modules/drivers/intel_drv.so
# Если modesetting загружается вместо intel:
# Убедиться что 20-intel-virtual.conf существует:
ls /etc/X11/xorg.conf.d/20-intel-virtual.conf
# Убедиться что нет других конфликтующих 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
```
# Должно быть: VIRTUAL1 connected primary (или DUMMY0)
Если primary = HDMI1 — переключаем:
```bash
# Если primary = HDMI1 — переключить:
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 +248,42 @@ systemctl --user cat hdmi-fallback.service
## Откат
```bash
# 1. Удалить конфиги X11
# Хост
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
# 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
```
---
## Технические детали
### Железо
### Почему intel driver, а не modesetting?
```
lspci | grep VGA
# 00:02.0 VGA compatible controller: Intel Corporation Alder Lake-N [UHD Graphics]
На Alder Lake-N (46d2) modesetting driver создает VIRTUAL1 только при определенных условиях и не гарантирует его после reboot. Intel driver с `VirtualHeads=1` создает VIRTUAL1 стабильно.
# Ядро: i915
cat /sys/class/drm/card1-HDMI-A-1/status
# connected (или disconnected)
### Почему клон, а не extended desktop?
# Только 1 CRTC:
cat /sys/kernel/debug/dri/*/i915_display_info | grep "CRTC"
# [CRTC:80:pipe A] — один pipe
```
`--same-as` клонирует framebuffer. При disconnect HDMI clone просто перестает рендериться, но primary (VIRTUAL1) не затрагивается — Xorg не теряет framebuffer.
### Почему не intel драйвер?
### Dummy driver на VPS
Драйвер `xf86-video-intel` — legacy. Официально **не рекомендуется** для Gen 9 и новее (Broadwell+). На Alder Lake-N он вообще не инициализируется корректно. Ubuntu по умолчанию использует `modesetting` — и это правильно.
### Почему именно клон, а не side-by-side?
```
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,18 @@
[Unit]
Description=RustDesk Server with Dummy Display
After=xorg-dummy.service
Requires=xorg-dummy.service
[Service]
Type=simple
User=root
Environment="DISPLAY=:0"
ExecStartPre=/bin/sleep 5
ExecStart=/usr/lib/rustdesk/rustdesk --server
Restart=always
RestartSec=10
StandardOutput=append:/var/log/rdtop-rustdesk.log
StandardError=append:/var/log/rdtop-rustdesk.log
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,25 @@
[Unit]
Description=Xorg Dummy Display for RustDesk VPS
After=network.target
[Service]
Type=simple
User=root
Environment="DISPLAY=:0"
ExecStartPre=/bin/mkdir -p /tmp/.X11-unix
ExecStart=/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
Restart=always
RestartSec=5
StandardOutput=append:/var/log/rdtop-xorg.log
StandardError=append:/var/log/rdtop-xorg.log
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,5 @@
Section "Device"
Identifier "Intel Graphics"
Driver "intel"
Option "VirtualHeads" "1"
EndSection