- set-password.bat: one-time password setup -> writes webvnc.conf (HASH + VNC_PASSWORD), syncs UltraVNC via ensure-vnc-password.ps1; if the 'web-vnc' task is installed, restarts it (elevated) to reload the new password, skipping restart when the password is unchanged. - run.bat: no longer prompts for the password; reads it from webvnc.conf (asks to run set-password.bat first if absent). Also clears --spawn-command (not just --spawn) when 5900 is already listening, to avoid spawning a 2nd VNC server. - scripts/autostart-run.bat: non-interactive launcher for the scheduled task (reads webvnc.conf, finds only a LOCAL VNC server, never downloads at boot). - scripts/install-autostart.bat / uninstall-autostart.bat: register/remove a 'web-vnc' scheduled task (ONSTART, SYSTEM, HIGHEST) via schtasks. - scripts/restart-autostart.bat: restart the task to apply a new password; checks whether web-vnc.exe is running (stop+start, or just start). - .gitignore: ignore webvnc.conf (secret). - AGENTS.md / README.md: document the autostart flow, set-password.bat, the new scripts, and that the running gateway must be restarted to apply a password change.
171 lines
18 KiB
Markdown
171 lines
18 KiB
Markdown
# AGENTS.md
|
||
|
||
Руководство для агентов (и людей), работающих с этим репозиторием.
|
||
|
||
## Сопровождение этого файла (ВАЖНО)
|
||
|
||
- **AGENTS.md должен отражать актуальное состояние кода.** Это живой документ,
|
||
а не одноразовая справка.
|
||
- При любом изменении, влияющем на: архитектуру, состав пакетов/файлов, флаги
|
||
CLI или env-переменные (`WEBVNC_*`), протокол/поведение гейтвея, скрипты,
|
||
кодировки файлов, процесс сборки/запуска или известные ограничения —
|
||
**обязательно обнови соответствующий раздел AGENTS.md в том же коммите**.
|
||
- Если заметил расхождение между AGENTS.md и реальным кодом (функция переименована,
|
||
файл удалён, флаг изменён и т.п.) — сначала приведи AGENTS.md в соответствие
|
||
с кодом, затем продолжай работу. Не оставляй устаревшие инструкции.
|
||
- Коммит, меняющий поведение/структуру, но не трогающий AGENTS.md при расхождении,
|
||
считается неполным.
|
||
## Что это
|
||
|
||
`web-vnc` — один Go-бинарник, который открывает доступ к рабочему столу (VNC)
|
||
через браузер (noVNC) с защитой по паролю. Гейтвей сам раздаёт noVNC-клиент,
|
||
проверяет пароль, ставит HMAC-сессию и прозрачно релеит WebSocket браузера в
|
||
TCP VNC-сервера (RFB). Опционально сам находит и запускает VNC-сервер.
|
||
|
||
## Ключевые принципы (НЕ нарушать)
|
||
|
||
- **Только стандартная библиотека Go.** Внешних Go-модулей нет и быть не должно —
|
||
проект собирается офлайн (в среде сборки нет интернета/Go-proxy).
|
||
WebSocket (RFC 6455) и хэш пароля (PBKDF2-HMAC-SHA256) реализованы вручную
|
||
в `internal/relay` и `internal/auth`.
|
||
- **noVNC-клиент скачивается отдельно** (`scripts/get-novnc.*`) во встроенную
|
||
статику `internal/server/static/`. Эти папки (`core/`,`app/`,`vendor/`,
|
||
`utils/`,`novnc-original.html`) в git не коммитятся (см. `.gitignore`).
|
||
Наша собственная обёртка — `internal/server/static/vnc.html` (коммитится).
|
||
- **Пароль** — один общий для всех (по требованию). Веб-пароль (PBKDF2) хранится
|
||
как `--password-hash`; тот же пароль может передаваться VNC-серверу через
|
||
`--vnc-password`/`WEBVNC_VNC_PASSWORD`, чтобы noVNC авторизовался
|
||
автоматически (одно поле ввода для пользователя).
|
||
|
||
## Сборка и запуск
|
||
|
||
```powershell
|
||
# среда без интернета: Go уже установлен, прокси недоступен -> stdlib-only
|
||
$env:GOCACHE = "$env:TEMP\go-build" # дефолтный кэш бывает без прав на запись
|
||
go build -o web-vnc.exe ./cmd/web-vnc
|
||
.\web-vnc.exe --gen-hash "пароль" # напечатает хэш
|
||
.\web-vnc.exe --password-hash <хэш> --spawn
|
||
```
|
||
|
||
Проверка: `go vet ./...`, `gofmt -l internal cmd` (должно быть пусто).
|
||
|
||
## Структура
|
||
|
||
```
|
||
cmd/web-vnc/main.go CLI: флаги, спавн VNC, запуск сервера, --gen-hash
|
||
internal/config флаги + env (WEBVNC_*)
|
||
internal/auth PBKDF2-HMAC-SHA256, HMAC session-cookie, rate-limit
|
||
internal/relay websocket.go — RFC6455 на stdlib; relay.go — WS<->TCP
|
||
internal/vncspawner кросс-ОС поиск/запуск VNC-сервера (build-теги по ОС)
|
||
internal/server HTTP-роуты, /api/status, middleware сессии, embed.FS
|
||
internal/server/static встроенные ассеты (vnc.html, favicon.ico + noVNC core/app/vendor)
|
||
scripts/get-novnc.{ps1,sh} скачать noVNC
|
||
scripts/get-vnc.ps1 скачать портативный UltraVNC в vnc/ (с верификацией zip)
|
||
scripts/list-ips.ps1 список IPv4 машины (используется run.bat)
|
||
scripts/open-firewall.bat открыть порт 8080 в Windows Firewall (от админа)
|
||
scripts/ensure-vnc-password.ps1 синхронизирует VNC-пароль UltraVNC с введённым (пишет только при несовпадении; UAC только при смене) — вызывается из set-password.bat
|
||
set-password.bat настройка пароля: спрашивает пароль, синхронизирует VNC-пароль UltraVNC (ensure-vnc-password.ps1), генерирует хэш и пишет webvnc.conf (HASH= + VNC_PASSWORD=, gitignored). Если установлено задание «web-vnc» — перезапускает его через scripts/restart-autostart.bat (с elevation), чтобы живый gateway перезалил пароль; рестарт пропускается, только если пароль не изменился (PBKDF2-хэш всегда новый из-за соли, поэтому изменение детектится сравнением введённого пароля со старым VNC_PASSWORD из webvnc.conf). Саму проверку «запущен ли web-vnc.exe» делает restart-autostart.bat (elevated, надёжно видит SYSTEM-процесс).
|
||
webvnc.conf создаётся set-password.bat; содержит HASH=<pbkdf2-хэш> и VNC_PASSWORD=<тот же пароль для noVNC>; читается run.bat и scripts/autostart-run.bat; в git не коммитится (.gitignore).
|
||
run.bat интерактивный запуск в один клик: собирает бинарник/noVNC/VNC, читает пароль из webvnc.conf (НЕ спрашивает — если файла нет, просит сначала запустить set-password.bat); если 5900 уже занят (сервис UltraVNC) — не порождает второй VNC-сервер, а подключается к существующему
|
||
scripts/autostart-run.bat НЕинтерактивный launcher для автостарта (нет пауз/запросов): читает webvnc.conf, ищет только ЛОКАЛЬНЫЙ VNC-сервер (ничего не качает), запускает web-vnc.exe в foreground. Вызывается плановым заданием из install-autostart.bat.
|
||
scripts/install-autostart.bat регистрирует плановое задание «web-vnc» (schtasks /SC ONSTART /RU SYSTEM /RL HIGHEST), запускающее scripts/autostart-run.bat при загрузке ОС; запускать от админа (самопрос elevation); требует уже созданный webvnc.conf.
|
||
scripts/uninstall-autostart.bat удаляет плановое задание «web-vnc» (от админа).
|
||
scripts/restart-autostart.bat перезапускает задание «web-vnc» (от админа, auto-elevate): проверяет tasklist, запущен ли web-vnc.exe — если да, делает schtasks /End + пауза 2 c + schtasks /Run; если нет — только schtasks /Run. Вызывается из set-password.bat, или вручную.
|
||
```
|
||
|
||
## Соглашения и подводные камни
|
||
|
||
- **Кодировка файлов `.bat`:** текст всех `.bat` — **English ASCII**, окончания строк **CRLF**,
|
||
**без BOM** и без `chcp` — ASCII читается `cmd` одинаково при любой OEM-кодировке, так
|
||
что кириллица в `echo`/`set /p`/`for /f` больше не нужна. Это намеренный отход от
|
||
кириллицы в bat-файлах (см. коммит «Translate run.bat and open-firewall.bat to English
|
||
ASCII»). Исторически кириллица в `.bat` требовала **UTF-8 с BOM** + `chcp 65001 >nul`
|
||
после `@echo off` (BOM заставлял `cmd` читать файл как UTF-8; без BOM + `chcp 65001` в
|
||
`cmd` давал `????`, т.к. cmd читает файл по OEM-кодировке, а не по `chcp`). Альтернатива
|
||
без BOM — системная опция «Beta: Use Unicode UTF-8 for worldwide language support»
|
||
(OEM=65001), но она влияет на всю систему (reboot, могут ломаться legacy-приложения
|
||
под cp866/cp1251). Если когда-нибудь вернёшь кириллицу в `.bat` — пиши через
|
||
`[System.IO.File]::WriteAllText(path, content -replace "(?<!\r)\n","`r`n", [Text.UTF8Encoding]::new($true))`
|
||
(с BOM). Для **English ASCII** — то же, но `[Text.UTF8Encoding]::new($false)` (без BOM).- **Go-файлы:** UTF-8 **без BOM**, LF. То же самое правило — не использовать
|
||
`Set-Content -Encoding UTF8` (ставит BOM). Используй
|
||
`[System.IO.File]::WriteAllText(path, content, [Text.UTF8Encoding]::new($false))`.
|
||
- **`.gitignore`:** бинарники игнорируются **root-anchored** (`/web-vnc`,
|
||
`/web-vnc.exe`), иначе шаблон `web-vnc` ловит пакет `cmd/web-vnc/`.
|
||
- **vncspawner:** функции поиска VNC-сервера определены через build-теги
|
||
(`detect_windows.go`/`detect_linux.go`/`detect_darwin.go`), каждый файл
|
||
определяет `func candidates() []Candidate`. Не ссылаться на
|
||
платформо-специфичные функции из общих файлов.
|
||
- **Тесты в этой среде:** интернет отсутствует; проверяй через локальные
|
||
заглушки (TCP-эхо-сервер на 5900, `Get-NetIPAddress` часто пуст —
|
||
`list-ips.ps1` имеет фолбэк на `ipconfig`). Запущенный web-vnc.exe из
|
||
фонового job может остаться «зомби» (Stop-Process иногда access denied);
|
||
используй разные порты для тестов и по возможности запускай killable-способом.
|
||
- **favicon.ico:** браузер запрашивает `/favicon.ico` на каждой странице
|
||
(включая форму логина). Роут `/favicon.ico` зарегистрирован **вне** session-
|
||
middleware (чтобы без сессии не редиректил на /login и не давал 404) и отдаёт
|
||
встроенный `internal/server/static/favicon.ico` (иконка-монитор, коммитится;
|
||
не входит в скачиваемые noVNC-ассеты). На логине и vnc.html есть
|
||
`<link rel="icon" href="/favicon.ico">`.
|
||
- **Secure context (TLS) в noVNC:** noVNC (`core/rfb.js`, `app/ui.js`) печатает
|
||
`Log.Error("noVNC requires a secure context (TLS). Expect crashes!")` при
|
||
`!window.isSecureContext` — т.е. на plain-http по LAN-IP. Для обычной VNC-
|
||
password-авторизации это **безобидно**: noVNC использует чистый-JS DES из
|
||
`core/des.js` и НЕ нуждается в `crypto.subtle`. Чтобы не пугать пользователя
|
||
красной ошибкой в консоли, обёртка `vnc.html` ДО импорта `core/rfb.js`
|
||
оборачивает `console.error` фильтром, гасящим **только** эту строку (всё
|
||
остальное проходит в оригинальный `console.error`). Файлы noVNC (core/, app/,
|
||
…) НЕ правятся — они gitignored и перезаливаются `scripts/get-novnc.*`.
|
||
Корректный способ сделать контекст действительно secure — HTTPS (self-signed),
|
||
но это отдельная фича; пока — фильтр в vnc.html.
|
||
- **Hijack WebSocket:** `internal/relay` сам делает апгрейд через
|
||
`http.Hijacker`; гейтвей НЕ использует gorilla/websocket.
|
||
|
||
## Автостарт (запуск при загрузке ОС)
|
||
|
||
Чтобы gateway поднимался сам при включении машины (а не интерактивно через `run.bat`),
|
||
пароль должен быть настроен заранее и не запрашиваться при старте. Поэтому процедура:
|
||
|
||
1. **Один раз** запустить `set-password.bat` — он спрашивает пароль, синхронизирует
|
||
VNC-пароль UltraVNC (`scripts/ensure-vnc-password.ps1`), генерирует хэш и пишет
|
||
`webvnc.conf` (HASH + VNC_PASSWORD). Файл `webvnc.conf` — **секрет**, в git не
|
||
коммитится (см. `.gitignore`).
|
||
2. **Один раз (от админа)** запустить `scripts/install-autostart.bat` — регистрирует
|
||
плановое задание `web-vnc` через `schtasks /Create /SC ONSTART /RU SYSTEM /RL HIGHEST`,
|
||
которое при загрузке ОС запускает `scripts/autostart-run.bat`.
|
||
3. `scripts/autostart-run.bat` — **неинтерактивный** launcher: читает `webvnc.conf`,
|
||
при необходимости собирает `web-vnc.exe` (если есть Go), ищет только **локальный**
|
||
VNC-сервер (ничего не качает — на старте сети/интернета может не быть), и запускает
|
||
`web-vnc.exe --password-hash <HASH> --listen :8080` в foreground (задание держит
|
||
процесс живым). Если `127.0.0.1:5900` уже слушает (сервис UltraVNC) — запускает
|
||
**без** `--spawn` и **без** `--spawn-command`, подключаясь к существующему серверу
|
||
(иначе попытка породить второй VNC-сервер конфликтовала бы с сервисом).
|
||
4. Снять автостарт: `scripts/uninstall-autostart.bat` (от админа) — `schtasks /Delete`.
|
||
|
||
Замечания:
|
||
- Задание выполняется от `SYSTEM`, поэтому VNC-сервер, отдавающий рабочий стол, должен
|
||
быть отдельным сервисом (UltraVNC `uvnc_service`) — `autostart-run.bat` его НЕ порождает,
|
||
а лишь подключается к `127.0.0.1:5900`. Портативный `vnc\winvnc.exe` используется только
|
||
если 5900 ещё не слушает.
|
||
- **Смена пароля на ходу:** `web-vnc.exe` читает хэш и `WEBVNC_VNC_PASSWORD` **один раз
|
||
при старте** (`config.Parse`/`auth.New`) и никогда не перечитывает `webvnc.conf`. Поэтому
|
||
`set-password.bat` после перезаписи `webvnc.conf` сам перезапускает задание «web-vnc» через
|
||
`scripts\restart-autostart.bat` (elevation): тот проверяет `tasklist`, запущен ли
|
||
`web-vnc.exe`, и делает `schtasks /End` + `/Run` (или только `/Run`, если процесс не
|
||
запущен). Рестарт пропускается, если пароль не изменился (детект по старому `VNC_PASSWORD`
|
||
из `webvnc.conf`, т.к. PBKDF2-хэш всегда новый из-за соли). Если задание не установлено —
|
||
`set-password.bat` только пишет `webvnc.conf` и подсказывает `install-autostart.bat`. Для
|
||
интерактивного `run.bat` нового пароля — просто перезапусти его вручную.
|
||
## Что делать дальше (известные TODO)
|
||
|
||
- Надёжный авто-скачиватель VNC-сервера (URL UltraVNC на SourceForge нестабилен —
|
||
`get-vnc.ps1` теперь верифицирует zip-магию и даёт фолбэк на ручную установку).
|
||
- На Windows захват экрана может требовать запуск VNC-сервера от администратора.
|
||
- VNC-сервер требует свой пароль; `--vnc-password` передаёт его noVNC
|
||
автоматически, но пароль VNC-сервера нужно один раз настроить под тот же.
|
||
**UltraVNC как сервис (`uvnc_service`, LocalSystem):** пароль хранится в
|
||
`%ProgramData%\UltraVNC\ultravnc.ini`; задать его обычным пользователем через
|
||
tray-иконку молча не получается (нет прав на запись).
|
||
`set-password.bat` синхронизирует этот пароль сам через `scripts/ensure-vnc-password.ps1`
|
||
(пишет только при несовпадении; UAC только при смене пароля). VNC-пароль = первые 8 байт,
|
||
поэтому используй ASCII-пароль <= 8 символов и в `set-password.bat`, и в VNC-сервере.
|