Files
web-vnc/AGENTS.md
T
Codex f315618bfc fix(autostart): wait for the VNC server before starting web-vnc
autostart-run.bat checked 5900 once at startup; at boot the UltraVNC service may
not be up yet, so web-vnc either spawned a 2nd VNC server (conflict) or started
serving on :8080 before the VNC server was ready, breaking browser connections.

Now autostart-run.bat polls 127.0.0.1:5900 for up to ~30s (one PowerShell loop:
TcpClient + Start-Sleep 1, exits on first accepted connection) and only then
launches web-vnc.exe -- without --spawn when 5900 already listens, or spawning a
local server (portable vnc\winvnc.exe / tvnserver.exe, else auto-detect) if it
never came up. Docs (AGENTS.md, README.md) updated.
2026-08-05 16:14:39 +03:00

179 lines
19 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.
# 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)
Все пользовательские точки входа лежат в корне репозитория:
set-password.bat настройка пароля: спрашивает пароль, генерирует хэш и пишет webvnc.conf (HASH= + VNC_PASSWORD=, gitignored) — ДО синхронизации UltraVNC, чтобы веб-пароль был настроен даже если синк упадёт или UAC отменят; затем синхронизирует VNC-пароль UltraVNC (scripts/ensure-vnc-password.ps1). Если установлено задание «web-vnc» — перезапускает его через restart-autostart.bat (с elevation), чтобы живый gateway перезалил пароль; рестарт пропускается, только если пароль не изменился (PBKDF2-хэш всегда новый из-за соли, поэтому изменение детектится сравнением введённого пароля со старым VNC_PASSWORD из webvnc.conf). Саму проверку «запущен ли web-vnc.exe» делает restart-autostart.bat (elevated, надёжно видит SYSTEM-процесс).
run.bat интерактивный запуск в один клик: собирает бинарник/noVNC/VNC, читает пароль из webvnc.conf (НЕ спрашивает — если файла нет, просит сначала запустить set-password.bat); если 5900 уже занят (сервис UltraVNC) — не порождает второй VNC-сервер, а подключается к существующему
install-autostart.bat регистрирует плановое задание «web-vnc» (schtasks /SC ONSTART /RU SYSTEM /RL HIGHEST), запускающее scripts/autostart-run.bat при загрузке ОС; запускать от админа (самопрос elevation); требует уже созданный webvnc.conf.
uninstall-autostart.bat удаляет плановое задание «web-vnc» (от админа).
restart-autostart.bat перезапускает задание «web-vnc» (от админа, auto-elevate): проверяет tasklist, запущен ли web-vnc.exe — если да, делает schtasks /End + пауза 2 c + schtasks /Run; если нет — только schtasks /Run. Вызывается из set-password.bat, или вручную.
open-firewall.bat открыть порт 8080 в Windows Firewall (от админа, один раз).
webvnc.conf создаётся set-password.bat; содержит HASH=<pbkdf2-хэш> и VNC_PASSWORD=<тот же пароль для noVNC>; читается run.bat и scripts/autostart-run.bat; в git не коммитится (.gitignore).
Внутренние помощники (вызываются другими скриптами, не пользователем) — в scripts/:
scripts/autostart-run.bat НЕинтерактивный launcher для автостарта (нет пауз/запросов): читает webvnc.conf, ПОЛЛИТ 127.0.0.1:5900 до ~30 c (UltraVNC-сервис может ещё подниматься при загрузке) и только потом запускает web-vnc.exe — без --spawn если 5900 уже слушает, иначе спавнит локальный VNC-сервер (portable vnc\\winvnc.exe/tvnserver.exe или авто-детект). Ничего не качает на старте. Вызывается плановым заданием из install-autostart.bat.
scripts/ensure-vnc-password.ps1 синхронизирует VNC-пароль UltraVNC с введённым (пишет только при несовпадении; UAC только при смене) — вызывается из set-password.bat.
scripts/get-novnc.{ps1,sh} скачать noVNC
scripts/get-vnc.ps1 скачать портативный UltraVNC в vnc/ (с верификацией zip)
scripts/list-ips.ps1 список IPv4 машины (используется run.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` — он спрашивает пароль, генерирует хэш
и пишет `webvnc.conf` (HASH + VNC_PASSWORD), а уже ПОТОМ синхронизирует VNC-пароль
UltraVNC (`scripts/ensure-vnc-password.ps1`) — так веб-пароль оказывается настроен,
даже если синк упадёт или UAC отменят. Файл `webvnc.conf`**секрет**, в git не
коммитится (см. `.gitignore`).
2. **Один раз (от админа)** запустить `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), затем **опрашивает
`127.0.0.1:5900` до ~30 c** (один PowerShell-проект крутит цикл try/TcpClient +
`Start-Sleep 1`; выходит сразу, как только порт принял соединение) — UltraVNC-сервис
при загрузке может ещё подниматься. Если `5900` слушает — запускает
`web-vnc.exe --password-hash <HASH> --listen :8080` **без** `--spawn` (подключается
к существующему серверу, не плодя второй). Если за ~30 c не поднялось — спавнит
локальный VNC-сервер (`--spawn-command "vnc\winvnc.exe -run"` если есть portable,
иначе `--spawn` с авто-детектом) и запускает web-vnc. На старте ничего не качает.
Задание держит процесс живым в foreground.
4. Снять автостарт: `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» через
`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-сервере.