Files
web-vnc/AGENTS.md
T
Codex 754f77a8d0 feat: autostart as a service + separate password setup (set-password.bat)
- 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.
2026-08-05 15:23:58 +03:00

171 lines
18 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)
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-сервере.