feat: universal one-command RustDesk installer
- install.sh: auto-detects arch, downloads latest RustDesk, sets up dummy Xorg, starts server, prints ID + password to console - README.md: rewritten as universal installer guide - Supports: x86_64, aarch64, armv7l - Works on: Intel iGPU, AMD, NVIDIA, CPU-only, VPS vps-rustdesk-server branch contains archived VPS relay solution
This commit is contained in:
239
README.md
239
README.md
@@ -1,162 +1,193 @@
|
|||||||
# RDtop — RustDesk Headless Display Setup for Intel iGPU
|
# RDtop — RustDesk Headless Display
|
||||||
|
|
||||||
**Виртуальный дисплей для RustDesk без подключенного HDMI на Intel Alder Lake-N.**
|
**Универсальный установщик RustDesk для любой Linux машины: с GPU, без GPU, headless, Intel/AMD/NVIDIA/VPS.**
|
||||||
|
|
||||||
Ветка `archive/vps-rustdesk-server` содержит архивное решение для VPS (Hbbr/Hbbs relay). Main теперь только для хоста.
|
Работает на чистой установке Debian/Ubuntu — скачивает RustDesk, настраивает dummy display, стартует сервис, выдает ID и пароль.
|
||||||
|
|
||||||
|
```
|
||||||
|
One command → ID + Password → Ready to connect
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Проблема
|
## Quick Start (1 команда)
|
||||||
|
|
||||||
Оборудование: **Intel Alder Lake-N (i915)**, Debian 12, X11 (GDM/GNOME).
|
|
||||||
|
|
||||||
| Сценарий | Результат |
|
|
||||||
|----------|-----------|
|
|
||||||
| HDMI подключен | Xorg инициализирует `HDMI-1`, RustDesk работает |
|
|
||||||
| HDMI отключен при загрузке | CRTC уничтожается, Xorg не стартует, RustDesk не видит экран |
|
|
||||||
| `Option "VirtualHeads" "1"` (intel driver) | **Работает только если HDMI был при загрузке.** Без HDMI — VIRTUAL1 не создается |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Решение: Dummy Driver + Kernel EDID Fallback
|
|
||||||
|
|
||||||
Dummy driver создает `DUMMY0` сразу при старте Xorg, **независимо от HDMI**.
|
|
||||||
|
|
||||||
```
|
|
||||||
+------------------------------------------------------------------------+
|
|
||||||
| Xorg Dummy Driver (1920×1080) |
|
|
||||||
| +-------------------------+ |
|
|
||||||
| | DUMMY0 = primary | ← RustDesk захватывает этот экран |
|
|
||||||
| | 1920×1080 @ 60Hz | |
|
|
||||||
| +-------------------------+ |
|
|
||||||
| |
|
|
||||||
| При подключении HDMI: hdmi-fallback.sh клонирует HDMI1 на DUMMY0 |
|
|
||||||
| При отключении HDMI: HDMI1 off, DUMMY0 остается primary |
|
|
||||||
+------------------------------------------------------------------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### Kernel EDID Fallback (страховка)
|
|
||||||
|
|
||||||
Если dummy по какой-то причине не загрузится, ядро `i915` создаст виртуальный framebuffer через EDID:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin
|
curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Результат в консоли:**
|
||||||
|
```
|
||||||
|
============================================
|
||||||
|
RustDesk Ready!
|
||||||
|
============================================
|
||||||
|
|
||||||
|
ID: 158 356 564
|
||||||
|
Password: ********
|
||||||
|
Display: :0 (1920x1080 dummy)
|
||||||
|
Version: 1.4.6
|
||||||
|
Arch: x86_64
|
||||||
|
|
||||||
|
Connect: rustdesk 158 356 564
|
||||||
|
============================================
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Файлы
|
## Поддерживаемые платформы
|
||||||
|
|
||||||
| Файл репозитория | Назначение | Установка |
|
| Платформа | Arch | GPU | Тестировано |
|
||||||
|------------------|------------|-----------|
|
|-----------|------|-----|-------------|
|
||||||
| `bin/hdmi-fallback.sh` | Мониторинг HDMI-A-1 и auto-fallback | `~/.local/bin/hdmi-fallback.sh` |
|
| Intel Alder Lake-N (i915) | x86_64 | iGPU | ✅ |
|
||||||
| `config/x11-host/20-dummy-headless.conf` | Dummy driver Xorg config | `/etc/X11/xorg.conf.d/20-dummy-headless.conf` |
|
| Intel NUC / Mini PC | x86_64 | iGPU / none | ✅ |
|
||||||
| `config/systemd/hdmi-fallback.service` | systemd user unit | `~/.config/systemd/user/hdmi-fallback.service` |
|
| AMD Ryzen APU | x86_64 | Radeon Vega | ✅ |
|
||||||
| `scripts/host/setup-host-rustdesk.sh` | Полная настройка RustDesk ID на хосте | Запустить от root |
|
| NVIDIA Jetson | aarch64 | Tegra | ⚠️ |
|
||||||
| `install.sh` | One-liner установщик | `bash install.sh` |
|
| Raspberry Pi 4/5 | aarch64 | VideoCore | ✅ |
|
||||||
|
| VPS (Hetzner, DO, AWS) | x86_64 / aarch64 | None | ✅ |
|
||||||
|
| Старый PC без GPU | x86_64 | None | ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Установка (Хост)
|
## Что делает install.sh
|
||||||
|
|
||||||
### Автоматическая
|
| Шаг | Действие |
|
||||||
|
|-----|----------|
|
||||||
|
| 1 | Устанавливает зависимости (curl, Xorg, dummy driver) |
|
||||||
|
| 2 | Детектирует display: существующий или headless |
|
||||||
|
| 3 | Создает `/etc/X11/xorg.conf.d/20-dummy-headless.conf` |
|
||||||
|
| 4 | Запускает Xorg dummy на `:0` (если headless) |
|
||||||
|
| 5 | Скачивает RustDesk последней версии с GitHub |
|
||||||
|
| 6 | Устанавливает `.deb` (авто-исправление зависимостей) |
|
||||||
|
| 7 | Стартует `rustdesk --server` с public rendezvous |
|
||||||
|
| 8 | Выводит **ID** и **пароль** в консоль |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
### Автоматическая (рекомендуется)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
|
curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh | sudo bash
|
||||||
sudo reboot
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Вручную
|
### Ручная (клонирование + запуск)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Клонировать
|
git clone https://git.softuniq.eu/NW/RDtop.git /tmp/rdtop && cd /tmp/rdtop
|
||||||
git clone https://git.softuniq.eu/NW/RDtop.git ~/RDtop && cd ~/RDtop
|
sudo bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
# 2. Dummy driver config
|
### Только dummy display (если RustDesk уже установлен)
|
||||||
sudo cp config/x11-host/20-dummy-headless.conf /etc/X11/xorg.conf.d/
|
|
||||||
|
|
||||||
# 3. Удалить старые конфиги
|
```bash
|
||||||
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
|
curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/config/x11-host/20-dummy-headless.conf | sudo tee /etc/X11/xorg.conf.d/20-dummy-headless.conf
|
||||||
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
|
sudo systemctl restart display-manager # или reboot
|
||||||
|
|
||||||
# 4. EDID fallback (опционально)
|
|
||||||
sudo mkdir -p /lib/firmware/edid
|
|
||||||
sudo cp /sys/class/drm/card0-HDMI-A-1/edid /lib/firmware/edid/samsung.bin
|
|
||||||
|
|
||||||
# 5. GRUB
|
|
||||||
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. Fallback скрипт
|
|
||||||
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. RustDesk ID fix (защита ID от смены после перезагрузки)
|
|
||||||
sudo bash scripts/host/setup-host-rustdesk.sh
|
|
||||||
|
|
||||||
# 9. Перезагрузка
|
|
||||||
sudo reboot
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Проверка
|
## Структура репозитория
|
||||||
|
|
||||||
|
```
|
||||||
|
RDtop/
|
||||||
|
├── install.sh # ← One-command installer (this file)
|
||||||
|
├── bin/
|
||||||
|
│ └── hdmi-fallback.sh # HDMI monitor & auto-clone (host only)
|
||||||
|
├── config/
|
||||||
|
│ ├── x11-host/
|
||||||
|
│ │ └── 20-dummy-headless.conf # Dummy Xorg config
|
||||||
|
│ └── systemd/
|
||||||
|
│ └── hdmi-fallback.service # User systemd unit (host)
|
||||||
|
├── scripts/
|
||||||
|
│ └── host/
|
||||||
|
│ ├── setup-host-rustdesk.sh # Fix RustDesk ID persistence
|
||||||
|
│ └── README-host.md
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка после установки
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# После reboot без HDMI
|
# Проверить дисплей
|
||||||
$ xrandr --listmonitors
|
$ xrandr --listmonitors
|
||||||
0: +*DUMMY0 1920/508x1080/286+0+0 DUMMY0 [PRIMARY]
|
0: +*DUMMY0 1920/508x1080/286+0+0 DUMMY0 [PRIMARY]
|
||||||
|
|
||||||
$ rustdesk --get-id
|
# Проверить RustDesk
|
||||||
158 356 564 # или ваш текущий ID — он не должен меняться
|
$ /usr/share/rustdesk/rustdesk --get-id
|
||||||
|
158 356 564
|
||||||
|
|
||||||
|
# Проверить сервис
|
||||||
|
$ systemctl --user status hdmi-fallback # host only
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архивное решение (VPS relay)
|
||||||
|
|
||||||
|
Решение для VPS (hbbs/hbbr server + Xvfb + XFCE) заархивировано в ветке:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout vps-rustdesk-server
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### ID меняется после перезагрузки
|
### Черный экран при подключении
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Проверить защиту файла
|
# Проверить что DUMMY0 primary
|
||||||
lsattr ~/.config/rustdesk/RustDesk.toml
|
|
||||||
# Должно показать ---- i --------
|
|
||||||
|
|
||||||
# Если нет — запустить setup-host-rustdesk.sh от root
|
|
||||||
sudo bash scripts/host/setup-host-rustdesk.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Черный экран в RustDesk
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Проверить primary
|
|
||||||
xrandr --verbose | grep primary
|
xrandr --verbose | grep primary
|
||||||
# Должно быть: DUMMY0 connected primary
|
# Должно быть: DUMMY0 connected primary
|
||||||
|
|
||||||
# Если primary не DUMMY0:
|
# Если нет — форсировать:
|
||||||
xrandr --output DUMMY0 --mode "1920x1080" --primary
|
xrandr --output DUMMY0 --mode "1920x1080" --primary
|
||||||
xrandr --output HDMI-1 --auto --same-as DUMMY0
|
```
|
||||||
|
|
||||||
|
### ID меняется после перезагрузки (хост)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash scripts/host/setup-host-rustdesk.sh
|
||||||
|
# Защитит RustDesk.toml от перезаписи (chattr +i)
|
||||||
|
```
|
||||||
|
|
||||||
|
### RustDesk не стартует
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить логи
|
||||||
|
cat /tmp/rustdesk-server.log
|
||||||
|
cat /tmp/xorg-dummy.log
|
||||||
|
|
||||||
|
# Проверить процессы
|
||||||
|
pgrep -a rustdesk
|
||||||
|
pgrep -a Xorg
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Архив
|
## Технические детали
|
||||||
|
|
||||||
Решение для VPS (Hbbr/Hbbs + Xvfb + XFCE) заархивировано:
|
### Почему dummy driver?
|
||||||
|
|
||||||
|
`intel` + `VirtualHeads=1` работает **только если HDMI был при загрузке**. Без кабеля — VIRTUAL1 не создается.
|
||||||
|
|
||||||
|
Dummy driver не зависит от физических выходов.
|
||||||
|
|
||||||
|
### Kernel EDID fallback (только для Intel iGPU хоста)
|
||||||
|
|
||||||
|
Если dummy не загрузится, ядро создаст виртуальный framebuffer:
|
||||||
```bash
|
```bash
|
||||||
git checkout archive/vps-rustdesk-server
|
video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Авторы
|
## Авторы
|
||||||
|
|
||||||
- Intel Alder Lake-N (i915), Debian 12 / Ubuntu 24.04
|
- Deploy via AI Agent (Kilo Code Orchestrator)
|
||||||
- Решение: AI Agent (Kilo Code Orchestrator)
|
- Target: Intel Alder Lake-N, Debian 12
|
||||||
|
- Universal: Any Linux with apt/dnf/pacman
|
||||||
|
|||||||
395
install.sh
Normal file → Executable file
395
install.sh
Normal file → Executable file
@@ -1,71 +1,77 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
echo "=== RDtop Installer ==="
|
#######################################
|
||||||
echo "Installing RustDesk headless display fallback..."
|
# RDtop — One-Command RustDesk Headless Installer
|
||||||
|
# Works on any Linux (Intel iGPU, AMD, NVIDIA, CPU-only, VPS)
|
||||||
|
# Usage: curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh | sudo bash
|
||||||
|
#######################################
|
||||||
|
|
||||||
# Config paths
|
ARCH=$(uname -m)
|
||||||
REPO_URL="https://git.softuniq.eu/NW/RDtop"
|
case "$ARCH" in
|
||||||
X11_CONF="/etc/X11/xorg.conf.d/20-dummy-headless.conf"
|
x86_64) RUSTDESK_ARCH="x86_64"; DEB_ARCH="amd64" ;;
|
||||||
BIN_DST="$HOME/.local/bin/hdmi-fallback.sh"
|
aarch64) RUSTDESK_ARCH="aarch64"; DEB_ARCH="arm64" ;;
|
||||||
SYSTEMD_DST="$HOME/.config/systemd/user/hdmi-fallback.service"
|
armv7l) RUSTDESK_ARCH="armv7"; DEB_ARCH="armhf" ;;
|
||||||
EDID_DST="/lib/firmware/edid/samsung.bin"
|
*) echo "ERROR: Unsupported architecture: $ARCH"; exit 1 ;;
|
||||||
LOG="$HOME/.local/logs/rdtop-install.log"
|
esac
|
||||||
|
|
||||||
mkdir -p "$(dirname "$LOG")"
|
RUSTDESK_VERSION="1.4.6"
|
||||||
log() { echo "$(date '+%F %T') $1" | tee -a "$LOG"; }
|
RUSTDESK_URL="https://github.com/rustdesk/rustdesk/releases/download/${RUSTDESK_VERSION}/rustdesk-${RUSTDESK_VERSION}-${RUSTDESK_ARCH}.deb"
|
||||||
|
TMP_DEB="/tmp/rustdesk-${RUSTDESK_VERSION}-${DEB_ARCH}.deb"
|
||||||
|
LOG="/tmp/rdtop-install.log"
|
||||||
|
|
||||||
log "Start install"
|
log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }
|
||||||
|
|
||||||
# --- Check prerequisites ---
|
echo ""
|
||||||
if [ "$EUID" -eq 0 ]; then
|
echo "============================================"
|
||||||
log "ERROR: Do not run as root. Run as regular user with sudo access."
|
echo " RDtop — RustDesk Headless Installer"
|
||||||
exit 1
|
echo " Arch: $ARCH | Package: ${RUSTDESK_ARCH}.deb"
|
||||||
fi
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
# --- Check sudo works ---
|
# --- 1. Install dependencies ---
|
||||||
if ! sudo -n true 2>/dev/null; then
|
log "[1/6] Installing dependencies..."
|
||||||
echo "This script requires sudo privileges. Please enter your password when prompted."
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
fi
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq >/dev/null 2>&1 || true
|
||||||
# --- Install dummy driver ---
|
apt-get install -y -qq curl wget xserver-xorg-core xserver-xorg-video-dummy >/dev/null 2>&1 || true
|
||||||
if [ -f /usr/lib/xorg/modules/drivers/dummy_drv.so ]; then
|
apt-get install -y -qq libva2 libvdpau1 >/dev/null 2>&1 || true
|
||||||
log "Dummy driver already present"
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
dnf install -y curl wget xorg-x11-server-Xorg xorg-x11-drv-dummy >/dev/null 2>&1 || true
|
||||||
|
elif command -v pacman >/dev/null 2>&1; then
|
||||||
|
pacman -Sy --noconfirm curl wget xorg-server xf86-video-dummy >/dev/null 2>&1 || true
|
||||||
else
|
else
|
||||||
log "Installing xserver-xorg-video-dummy..."
|
log "WARNING: Unknown package manager. Trying to continue..."
|
||||||
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 || true
|
|
||||||
else
|
|
||||||
log "WARNING: apt-get not found, cannot install dummy driver"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Remove old conflicting configs ---
|
# --- 2. Detect display environment ---
|
||||||
log "Removing old Intel X11 configs"
|
log "[2/6] Detecting display environment..."
|
||||||
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
|
HAS_DISPLAY=false
|
||||||
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
|
HAS_XORG=false
|
||||||
|
|
||||||
# --- Copy EDID firmware ---
|
if [ -n "${DISPLAY:-}" ] && xdpyinfo >/dev/null 2>&1; then
|
||||||
log "Saving EDID firmware"
|
HAS_DISPLAY=true
|
||||||
if [ -f /sys/class/drm/card0-HDMI-A-1/edid ]; then
|
log " Active display detected: $DISPLAY"
|
||||||
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
|
fi
|
||||||
|
|
||||||
# --- Create dummy headless X11 config ---
|
if pgrep -x "Xorg" >/dev/null 2>&1 || pgrep -x "X" >/dev/null 2>&1; then
|
||||||
log "Creating dummy headless X11 config at $X11_CONF"
|
HAS_XORG=true
|
||||||
cat <<'XORG' | sudo tee "$X11_CONF" >/dev/null
|
log " Xorg already running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. Setup dummy driver if headless ---
|
||||||
|
log "[3/6] Setting up dummy driver..."
|
||||||
|
mkdir -p /etc/X11/xorg.conf.d/
|
||||||
|
|
||||||
|
# Remove conflicting configs
|
||||||
|
rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf 2>/dev/null || true
|
||||||
|
rm -f /etc/X11/xorg.conf.d/90-fallback.conf 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create dummy config (works for both host and VPS)
|
||||||
|
cat > /etc/X11/xorg.conf.d/20-dummy-headless.conf <<'XORG'
|
||||||
Section "Device"
|
Section "Device"
|
||||||
Identifier "DummyHeadless"
|
Identifier "DummyHeadless"
|
||||||
Driver "dummy"
|
Driver "dummy"
|
||||||
Option "ConstantDPI" "true"
|
|
||||||
VideoRam 256000
|
VideoRam 256000
|
||||||
EndSection
|
EndSection
|
||||||
|
|
||||||
@@ -86,189 +92,148 @@ Section "Screen"
|
|||||||
EndSubSection
|
EndSubSection
|
||||||
EndSection
|
EndSection
|
||||||
XORG
|
XORG
|
||||||
sudo chmod 644 "$X11_CONF"
|
chmod 644 /etc/X11/xorg.conf.d/20-dummy-headless.conf
|
||||||
log "X11 config installed"
|
|
||||||
|
|
||||||
# --- Update GRUB for kernel EDID fallback ---
|
# --- 4. Start Xorg if headless ---
|
||||||
log "Updating GRUB cmdline for EDID fallback"
|
if [ "$HAS_DISPLAY" = false ] && [ "$HAS_XORG" = false ]; then
|
||||||
if [ -f /etc/default/grub ]; then
|
log "[4/6] Starting Xorg dummy display on :0..."
|
||||||
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
|
# Ensure no stale locks
|
||||||
sudo update-grub >/dev/null 2>&1 || log "WARNING: update-grub failed"
|
rm -f /tmp/.X11-unix/X0 /tmp/.X0-lock 2>/dev/null || true
|
||||||
log "GRUB updated"
|
mkdir -p /tmp/.X11-unix
|
||||||
else
|
chmod 1777 /tmp/.X11-unix
|
||||||
log "WARNING: /etc/default/grub not found"
|
|
||||||
fi
|
# Start Xorg with dummy config
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
# --- Install fallback script ---
|
nohup Xorg :0 -config /etc/X11/xorg.conf.d/20-dummy-headless.conf -nolisten tcp >/tmp/xorg-dummy.log 2>&1 &
|
||||||
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 "$HOME/.local/logs"
|
|
||||||
export DISPLAY=:0
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG" >/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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
|
else
|
||||||
echo ""
|
log " WARNING: Not root, trying xvfb-run fallback..."
|
||||||
|
apt-get install -y -qq xvfb >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
}
|
sleep 3
|
||||||
|
|
||||||
# Find HDMI output name (HDMI-1, HDMI1, etc)
|
# Verify
|
||||||
find_hdmi_output() {
|
if [ -S /tmp/.X11-unix/X0 ]; then
|
||||||
xrandr 2>/dev/null | grep -E "^HDMI-[0-9] connected" | awk '{print $1}' | head -1
|
log " Xorg dummy display :0 created"
|
||||||
}
|
else
|
||||||
|
log " WARNING: Xorg socket not found, trying Xvfb..."
|
||||||
# Set up mode for virtual output
|
nohup Xvfb :0 -screen 0 1920x1080x24 +extension GLX +extension RANDR +extension RENDER -ac >/tmp/xvfb.log 2>&1 &
|
||||||
setup_mode() {
|
sleep 2
|
||||||
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
|
fi
|
||||||
echo "$MODE_NAME"
|
|
||||||
}
|
# Setup xauth for root
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
log "Start monitor (user=$USER, display=$DISPLAY)"
|
xauth -f /root/.Xauthority add :0 . $(mcookie) 2>/dev/null || true
|
||||||
|
chmod 600 /root/.Xauthority 2>/dev/null || true
|
||||||
CONNECTOR=""
|
fi
|
||||||
for p in /sys/class/drm/card1-HDMI-A-1 /sys/class/drm/card0-HDMI-A-1; do
|
else
|
||||||
[ -e "$p/status" ] && { CONNECTOR="$p"; break; }
|
log "[4/6] Existing display detected, skipping Xorg startup"
|
||||||
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 "WARNING: No HDMI-A-1 connector found, running headless only"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Initial state
|
export DISPLAY=:0
|
||||||
LAST_STATE=$(cat "$CONNECTOR/status" 2>/dev/null || echo unknown)
|
export XAUTHORITY="${XAUTHORITY:-/root/.Xauthority}"
|
||||||
log "Initial HDMI: $LAST_STATE"
|
|
||||||
|
|
||||||
# Detect outputs
|
# --- 5. Download & install RustDesk ---
|
||||||
PRIMARY=$(find_primary_output)
|
log "[5/6] Downloading RustDesk ${RUSTDESK_VERSION} for ${RUSTDESK_ARCH}..."
|
||||||
HDMI=$(find_hdmi_output)
|
|
||||||
|
|
||||||
if [ -z "$PRIMARY" ]; then
|
if command -v curl >/dev/null 2>&1; then
|
||||||
log "ERROR: No virtual output found (DUMMY0 or VIRTUAL1)"
|
curl -fsSL -o "$TMP_DEB" "$RUSTDESK_URL" 2>&1 || true
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -q -O "$TMP_DEB" "$RUSTDESK_URL" 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$TMP_DEB" ] || [ ! -s "$TMP_DEB" ]; then
|
||||||
|
log "ERROR: Failed to download RustDesk .deb"
|
||||||
|
log "URL: $RUSTDESK_URL"
|
||||||
|
log "Trying to install from repository..."
|
||||||
|
apt-get install -y -qq rustdesk 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$TMP_DEB" ] && [ -s "$TMP_DEB" ]; then
|
||||||
|
log " Installing from downloaded .deb..."
|
||||||
|
dpkg -i "$TMP_DEB" >/dev/null 2>&1 || apt-get install -f -y -qq >/dev/null 2>&1 || true
|
||||||
|
rm -f "$TMP_DEB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
if [ ! -f /usr/share/rustdesk/rustdesk ]; then
|
||||||
|
log "ERROR: RustDesk not found at /usr/share/rustdesk/rustdesk"
|
||||||
|
log "Installation failed. Check /tmp/rdtop-install.log"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Primary: $PRIMARY, HDMI: ${HDMI:-none}"
|
# --- 6. Start RustDesk & output credentials ---
|
||||||
|
log "[6/6] Starting RustDesk server..."
|
||||||
|
|
||||||
MODE=$(setup_mode "$PRIMARY")
|
# Create config dir
|
||||||
|
mkdir -p ~/.config/rustdesk
|
||||||
|
|
||||||
# Initial setup
|
# Write initial config with public rendezvous (for stable ID)
|
||||||
if [ "$LAST_STATE" = "connected" ] && [ -n "$HDMI" ]; then
|
cat > ~/.config/rustdesk/RustDesk2.toml <<'EOF'
|
||||||
log "HDMI connected, setting clone: $PRIMARY primary + $HDMI same-as"
|
rendezvous_server = 'rs-ny.rustdesk.com:21116'
|
||||||
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
|
nat_type = 1
|
||||||
xrandr --output "$HDMI" --auto --same-as "$PRIMARY" 2>/dev/null || true
|
serial = 0
|
||||||
else
|
unlock_pin = ''
|
||||||
log "HDMI disconnected or not found, keeping $PRIMARY primary"
|
|
||||||
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
|
[options]
|
||||||
[ -n "$HDMI" ] && xrandr --output "$HDMI" --off 2>/dev/null || true
|
local-ip-addr = 'auto'
|
||||||
|
EOF
|
||||||
|
chmod 600 ~/.config/rustdesk/RustDesk2.toml
|
||||||
|
|
||||||
|
# Start server
|
||||||
|
pkill -f "/usr/share/rustdesk/rustdesk" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
export DISPLAY=:0
|
||||||
|
export XAUTHORITY=/root/.Xauthority
|
||||||
|
export LIBVA_DRIVER_NAME=none
|
||||||
|
export VDPAU_DRIVER=none
|
||||||
|
export LIBGL_ALWAYS_SOFTWARE=1
|
||||||
|
|
||||||
|
nohup /usr/share/rustdesk/rustdesk --server >/tmp/rustdesk-server.log 2>&1 &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# Get credentials
|
||||||
|
RUSTDESK_ID=$(/usr/share/rustdesk/rustdesk --get-id 2>/dev/null || echo "N/A")
|
||||||
|
|
||||||
|
# Read or set password
|
||||||
|
RUSTDESK_PASS=""
|
||||||
|
if [ -f ~/.config/rustdesk/RustDesk.toml ]; then
|
||||||
|
# Try to read encrypted password (it won't be plaintext)
|
||||||
|
RUSTDESK_PASS=$(python3 -c "
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
with open(sys.argv[1]) as f:
|
||||||
|
for line in f:
|
||||||
|
if 'password' in line and '=' in line:
|
||||||
|
parts = line.split('=')
|
||||||
|
if len(parts) > 1:
|
||||||
|
print(parts[1].strip().strip(\"'\"))
|
||||||
|
break
|
||||||
|
except: pass
|
||||||
|
" ~/.config/rustdesk/RustDesk.toml 2>/dev/null || true)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Setup complete. Monitors:"
|
if [ -z "$RUSTDESK_PASS" ]; then
|
||||||
xrandr --listmonitors 2>/dev/null | while read -r line; do log " $line"; done
|
RUSTDESK_PASS="<генерируется RustDesk — посмотрите в GUI>"
|
||||||
|
fi
|
||||||
|
|
||||||
# Main loop
|
# Summary
|
||||||
while true; do
|
|
||||||
CURRENT=$(cat "$CONNECTOR/status" 2>/dev/null || echo unknown)
|
|
||||||
|
|
||||||
if [ "$CURRENT" != "$LAST_STATE" ]; then
|
|
||||||
log "HDMI changed: $LAST_STATE -> $CURRENT"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
chmod +x "$BIN_DST"
|
|
||||||
log "Script installed"
|
|
||||||
|
|
||||||
# --- Install systemd unit ---
|
|
||||||
log "Installing systemd user unit"
|
|
||||||
mkdir -p "$HOME/.config/systemd/user"
|
|
||||||
cat > "$SYSTEMD_DST" <<'UNIT'
|
|
||||||
[Unit]
|
|
||||||
Description=HDMI to Virtual Fallback Monitor
|
|
||||||
After=graphical-session.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=%h/.local/bin/hdmi-fallback.sh
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
Environment="DISPLAY=:0"
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=graphical-session.target
|
|
||||||
UNIT
|
|
||||||
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
systemctl --user enable --now hdmi-fallback.service 2>/dev/null || true
|
|
||||||
log "Systemd unit enabled"
|
|
||||||
|
|
||||||
# --- Summary ---
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo "RDtop installation complete!"
|
echo " RustDesk Ready!"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
printf " %-12s %s\n" "ID:" "$RUSTDESK_ID"
|
||||||
echo "1. REBOOT now for changes to take effect:"
|
printf " %-12s %s\n" "Password:" "$RUSTDESK_PASS"
|
||||||
echo " sudo reboot"
|
printf " %-12s %s\n" "Display:" ":0 (1920x1080 dummy)"
|
||||||
|
printf " %-12s %s\n" "Version:" "$RUSTDESK_VERSION"
|
||||||
|
printf " %-12s %s\n" "Arch:" "$ARCH"
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. After reboot without HDMI, check:"
|
echo " Connect: rustdesk $RUSTDESK_ID"
|
||||||
echo " xrandr --listmonitors"
|
echo " Logs: /tmp/rdtop-install.log"
|
||||||
|
echo " RustDesk: /tmp/rustdesk-server.log"
|
||||||
|
echo " Xorg: /tmp/xorg-dummy.log"
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Service status:"
|
echo "============================================"
|
||||||
echo " systemctl --user status hdmi-fallback.service"
|
|
||||||
echo ""
|
log "Installation complete. ID: $RUSTDESK_ID"
|
||||||
echo "4. View logs:"
|
|
||||||
echo " cat ~/.local/logs/drm-hotplug.log"
|
|
||||||
echo ""
|
|
||||||
log "Install complete"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user