# 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 ONLOGON /RL HIGHEST, от имени текущего пользователя) — открывает ВИДИМУЮ консоль с scripts/autostart-run.bat при входе пользователя в систему; запускать от админа (самопрос elevation); требует уже созданный webvnc.conf. (Вариант ONSTART/SYSTEM скрыт в session 0 и окна не показывает — поэтому не используется.) 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= и VNC_PASSWORD=<тот же пароль для noVNC>; читается run.bat и scripts/autostart-run.bat; в git не коммитится (.gitignore). Внутренние помощники (вызываются другими скриптами, не пользователем) — в scripts/: scripts/autostart-run.bat launcher для автостарта: лежит в scripts/, но работает относительно КОРНЯ репо (cd /d %~dp0.. — web-vnc.exe/webvnc.conf/vnc/go.mod там). Читает 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): окно остаётся, пока web-vnc serves; при фатальной ошибке/no binary/no conf — pause, чтобы окно не закрылось и было видно причину. Вызывается также restart-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 "(?`. - **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 ONLOGON /RL HIGHEST` (от имени текущего пользователя, без `/RU SYSTEM`), которое при **входе пользователя в систему** открывает **видимую консоль** с `scripts/autostart-run.bat`. Запуск от SYSTEM/ONSTART намеренно не используется: он выполняется в session 0 и НЕ показывает окно на рабочем столе, поэтому увидеть ошибки запуска было бы невозможно. 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 --listen :8080` **без** `--spawn` (подключается к существующему серверу, не плодя второй). Если за ~30 c не поднялось — спавнит локальный VNC-сервер (`--spawn-command "vnc\winvnc.exe -run"` если есть portable, иначе `--spawn` с авто-детектом) и запускает web-vnc. На старте ничего не качает. Задание держит процесс живым в foreground. 4. Снять автостарт: `uninstall-autostart.bat` (от админа) — `schtasks /Delete`. Замечания: - Задание выполняется от имени **текущего пользователя** при его входе (видимая консоль), а не от SYSTEM в session 0. Поэтому web-vnc может сам спавнить VNC-сервер в сеансе пользователя (что удобнее для захвата рабочего стола, чем SYSTEM). Если уже работает сервис UltraVNC (`uvnc_service`) и слушает 5900 — `autostart-run.bat` подключается к нему без `--spawn`. Портативный `vnc\winvnc.exe` спавнится только если 5900 не слушает. - Автостарт срабатывает при **входе пользователя**, а не до входа. Если нужен запуск до логина (без окна) — это отдельный скрытый вариант ONSTART/SYSTEM; текущий по умолчанию ориентирован на «видимую консоль после ребута/логина». - **Смена пароля на ходу:** `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-сервере.