# 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)"`