Files
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

186 lines
20 KiB
Markdown
Raw Permalink 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 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`:** текст всех `.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 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-сервере.