Files
RDtop/README.md
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

355 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)"`