Files
web-vnc/AGENTS.md
T
Codex 02fefa1e4e fix(autostart): run from repo root + visible console at logon
autostart-run.bat did 'cd /d %~dp0' -> scripts/, but web-vnc.exe/webvnc.conf/
go.mod live in the repo root, so the binary was never found and web-vnc never
started after reboot. Now it cd's to %~dp0.. (repo root).

The task was ONSTART/RU SYSTEM (session 0) so it could not show a window, making
failures invisible. install-autostart.bat now registers ONLOGON /RL HIGHEST as the
current user -> a visible console opens at logon. autostart-run.bat sets a window
title, prints status, stays open while web-vnc serves, and pauses on fatal errors
(no binary / no webvnc.conf) so the reason is visible. Docs updated.
2026-08-06 01:09:47 +03:00

20 KiB
Raw Blame History

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 авторизовался автоматически (одно поле ввода для пользователя).

Сборка и запуск

# среда без интернета: 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=<pbkdf2-хэш> и 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: текст всех .batEnglish 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","rn", [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 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 <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-сервере.