- Go stdlib-only gateway: serves noVNC client, password auth, WS<->TCP relay - auth: PBKDF2-HMAC-SHA256 password hash, HMAC session cookies, login rate limit - relay: hand-written RFC 6455 WebSocket + transparent RFB bridge - vncspawner: cross-OS VNC server detection/launch (Windows/Linux/macOS) - server: /login /logout /vnc /api/status routes, session middleware, embed.FS - scripts: get-novnc, get-vnc (zip-verified), list-ips, open-firewall - run.bat: one-click launcher (asks only password), lists access IPs - README/AGENTS.md (Russian), .gitignore (root-anchored binaries)
203 lines
12 KiB
Markdown
203 lines
12 KiB
Markdown
# web-vnc
|
||
|
||
Один бинарник, который открывает доступ к рабочему столу (VNC) через браузер
|
||
(noVNC) с защитой по **паролю**. Программа `web-vnc`:
|
||
|
||
- раздаёт HTML5-клиент noVNC,
|
||
- пускает зрителя по общему паролю,
|
||
- прокидывает WebSocket браузера к локальному VNC-серверу (RFB поверх WS),
|
||
- и (по желанию) сама запускает VNC-сервер.
|
||
|
||
Написано на чистом Go, **только стандартная библиотека** — никаких внешних
|
||
Go-модулей, собирается офлайн.
|
||
|
||
```
|
||
Браузер (noVNC) ──ws──▶ web-vnc (один бинарник) ──tcp RFB──▶ VNC-сервер (127.0.0.1:5900)
|
||
├─ вход по паролю (PBKDF2-HMAC-SHA256)
|
||
├─ HMAC session-cookie
|
||
└─ прозрачный релей WS↔TCP
|
||
```
|
||
|
||
## Самый простой запуск (Windows) — одним батником
|
||
|
||
В папке проекта есть `run.bat`. Он делает всё сам и спрашивает **только пароль**:
|
||
|
||
```bat
|
||
run.bat
|
||
```
|
||
|
||
Что делает `run.bat`:
|
||
1. Если `web-vnc.exe` отсутствует — собирает его (нужен установленный Go).
|
||
Если Go нет — подсказывает, что сделать.
|
||
2. Если noVNC-клиент ещё не встроен — пытается его скачать (нужен интернет).
|
||
Без интернета продолжит работу, но в браузере будет страница-заглушка.
|
||
3. Спрашивает пароль доступа.
|
||
4. Генерирует из него хэш.
|
||
5. Ищет VNC-сервер: если он не установлен — пытается скачать портативный
|
||
UltraVNC (`scripts\get-vnc.ps1`) и запускать его.
|
||
6. Выводит список всех адресов для подключения (`http://localhost:8080` и
|
||
`http://<каждый IPv4 машины>:8080`) и запускает сервер с флагом `--spawn`.
|
||
|
||
После запуска откройте в браузере один из выведенных адресов,
|
||
введите тот же пароль — и попадёте на рабочий стол.
|
||
|
||
## Быстрый старт вручную (Windows)
|
||
|
||
```powershell
|
||
# 1. Подтянуть noVNC-клиент во встроенную статику (один раз)
|
||
.\scripts\get-novnc.ps1
|
||
|
||
# 2. Собрать бинарник
|
||
go build -o web-vnc.exe .\cmd\web-vnc
|
||
|
||
# 3. Сгенерировать хэш пароля
|
||
$hash = .\web-vnc.exe --gen-hash "ваш-пароль"
|
||
|
||
# 4. Запустить (сам найдёт/запустит VNC-сервер и поднимется на :8080)
|
||
.\web-vnc.exe --password-hash $hash --spawn
|
||
```
|
||
|
||
## Быстрый старт (Linux / macOS)
|
||
|
||
```bash
|
||
./scripts/get-novnc.sh
|
||
go build -o web-vnc ./cmd/web-vnc
|
||
hash=$(./web-vnc --gen-hash "ваш-пароль")
|
||
./web-vnc --password-hash "$hash" --spawn
|
||
```
|
||
|
||
## Как это работает
|
||
|
||
1. Пользователь открывает `http://хост:8080/` и попадает на `/login`.
|
||
2. Вводит общий пароль. Он сверяется с **солёным PBKDF2-HMAC-SHA256**-хэшем
|
||
(120 000 итераций). При успехе сервер ставит **HMAC-подписанную,
|
||
HttpOnly**-куку сессии (по умолчанию 8 ч, без серверного хранилища).
|
||
3. Браузер загружает `/vnc.html` (только с валидной сессией). Клиент noVNC
|
||
открывает WebSocket на `/vnc`.
|
||
4. `web-vnc` проверяет сессию **до** апгрейда WebSocket и затем прозрачно
|
||
релеит байты между WebSocket и локальным VNC-сервером
|
||
(`127.0.0.1:5900` по умолчанию). RFB-протокол проходит нетронутым,
|
||
как у `websockify`.
|
||
|
||
> VNC-сервер должен слушать на **loopback (127.0.0.1)** и может работать
|
||
> **без VNC-пароля** — защита по паролю теперь на веб-гейтвее. Если же ваш
|
||
> VNC-сервер требует свой пароль, noVNC спросит и его.
|
||
|
||
## Авто-запуск VNC-сервера (`--spawn`)
|
||
|
||
С `--spawn` программа ищет установленный VNC-сервер и запускает его как
|
||
дочерний процесс (отдельно, без окна на Windows), после чего подключается к нему.
|
||
|
||
| ОС | Что ищет |
|
||
|---------|-------------------------------------------------------|
|
||
| Windows | UltraVNC (`winvnc.exe`), TightVNC (`tvnserver.exe`) |
|
||
| Linux | `x11vnc`, `tigervncserver` / `Xvnc` |
|
||
| macOS | встроенный Screen Sharing (`kickstart`) |
|
||
|
||
Переопределить авто-поиск своей командой:
|
||
|
||
```powershell
|
||
.\web-vnc.exe --password-hash $hash --spawn-command "C:\Path\To\winvnc.exe -run"
|
||
```
|
||
|
||
Без `--spawn` убедитесь, что VNC-сервер уже слушает по адресу из `--vnc`.
|
||
|
||
## Настройка
|
||
|
||
Все флаги дублируются переменными окружения (`WEBVNC_*`).
|
||
|
||
| Флаг | Env | По умолчанию | Описание |
|
||
|--------------------|---------------------------|--------------------|------------------------------------------------|
|
||
| `--listen` | `WEBVNC_LISTEN` | `:8080` | адрес HTTP/WS |
|
||
| `--vnc` | `WEBVNC_VNC` | `127.0.0.1:5900` | адрес вышестоящего VNC-сервера |
|
||
| `--password-hash` | `WEBVNC_PASSWORD_HASH` | (обязателен) | хэш PBKDF2 из `--gen-hash` |
|
||
| `--session-secret` | `WEBVNC_SESSION_SECRET` | случайный при старте | HMAC-ключ для подписи куки сессии |
|
||
| `--session-ttl` | `WEBVNC_SESSION_TTL` | `8h` | время жизни куки сессии |
|
||
| `--spawn` | `WEBVNC_SPAWN` | false | авто-запуск найденного VNC-сервера |
|
||
| `--spawn-command` | `WEBVNC_SPAWN_COMMAND` | (нет) | явная команда запуска VNC-сервера |
|
||
| `--web-root` | `WEBVNC_WEB_ROOT` | (встроенные) | раздавать статику с диска вместо embed |
|
||
| `--novnc-path` | | `/vnc.html` | путь страницы noVNC-клиента |
|
||
| `--relay-path` | | `/vnc` | endpoint WebSocket-релея |
|
||
|
||
### Сгенерировать хэш пароля
|
||
|
||
```bash
|
||
web-vnc --gen-hash "ваш-пароль"
|
||
# напечатает, например: pbkdf2-sha256$120000$<соль>$<ключ>
|
||
```
|
||
|
||
Формат самодокументируемый:
|
||
`pbkdf2-sha256$<итерации>$<base64-соль>$<base64-ключ>`.
|
||
|
||
### Запуск одной строкой
|
||
|
||
```powershell
|
||
.\web-vnc.exe --password-hash (. \web-vnc.exe --gen-hash "secret") --spawn
|
||
```
|
||
|
||
## Безопасность
|
||
|
||
- **Без TLS**: рассчитано на **приватную сеть**. Если выставляете наружу —
|
||
поставьте перед ним reverse-proxy с TLS (nginx/caddy).
|
||
- Логин с лимитом попыток (5 в минуту на IP, в памяти).
|
||
- Кука сессии подписана `--session-secret`. Задайте фиксированный
|
||
`--session-secret`, чтобы сессии переживали перезапуск (иначе секрет
|
||
меняется при каждом старте, и старые сессии инвалидируются).
|
||
- VNC-сервер держите на `127.0.0.1`, чтобы до него нельзя было достучаться
|
||
в обход гейтвея.
|
||
|
||
|
||
## Устранение неполадок
|
||
|
||
- **После ввода пароля — ошибка / нет картинки.**
|
||
Значит, веб-гейтвей не смог подключиться к VNC-серверу (по адресу `--vnc`,
|
||
по умолчанию `127.0.0.1:5900`). На странице теперь показывается понятное
|
||
сообщение вместо криптографической ошибки noVNC.
|
||
Решение: должен работать VNC-сервер, который отдаёт рабочий стол:
|
||
- Windows: установите **UltraVNC** или **TightVNC**, либо запустите
|
||
`scripts\get-vnc.ps1` (скачает портативный UltraVNC в папку `vnc\`, и
|
||
`run.bat` сам его запустит).
|
||
- Linux: `x11vnc` или `TigerVNC`.
|
||
- macOS: встроенный Screen Sharing.
|
||
Важно: VNC-сервер должен слушать на `127.0.0.1:5900`. На Windows для захвата
|
||
экрана может потребоваться запуск от имени администратора (UAC).
|
||
|
||
- **С другого компьютера страница не открывается (таймаут/недоступно).**
|
||
По умолчанию Windows Firewall блокирует входящие подключения. Один раз
|
||
выполните от имени администратора:
|
||
```bat
|
||
scripts\open-firewall.bat
|
||
```
|
||
Это откроет входящий TCP-порт 8080. (`run.bat` выводит адреса и подсказку.)
|
||
|
||
- **noVNC-клиент не встроен (страница-заглушка).**
|
||
Запустите `scripts\get-novnc.ps1` (нужен интернет), пересоберите и перезапустите.
|
||
## Структура проекта
|
||
|
||
```
|
||
cmd/web-vnc/main.go точка входа CLI: флаги, спавн VNC, запуск сервера
|
||
internal/config конфигурация (флаги + env)
|
||
internal/auth хэш пароля PBKDF2, HMAC-куки сессии, rate-limit
|
||
internal/relay WebSocket на stdlib (RFC 6455) + мост WS↔TCP
|
||
internal/vncspawner кросс-ОС поиск и запуск VNC-сервера
|
||
internal/server HTTP-роуты, middleware сессии, встроенная статика
|
||
internal/server/static встроенные веб-ассеты (vnc.html + core/app/vendor noVNC)
|
||
scripts/get-novnc.{ps1,sh} скачать noVNC во встроенную статику
|
||
scripts/get-vnc.ps1 скачать портативный VNC-сервер (UltraVNC) в папку vnc/
|
||
scripts/list-ips.ps1 список IPv4 машины (используется run.bat)
|
||
scripts/open-firewall.bat открыть порт 8080 в Windows Firewall (один раз, от админа)
|
||
run.bat запуск в один клик (спрашивает только пароль)
|
||
```
|
||
|
||
## Кросс-компиляция под другую ОС
|
||
|
||
```bash
|
||
GOOS=linux GOARCH=amd64 go build -o web-vnc-linux ./cmd/web-vnc
|
||
GOOS=windows GOARCH=amd64 go build -o web-vnc.exe ./cmd/web-vnc
|
||
GOOS=darwin GOARCH=arm64 go build -o web-vnc-macos ./cmd/web-vnc
|
||
```
|
||
|
||
## Лицензия
|
||
|
||
MIT. Встроенные ассеты noVNC сохраняют свою лицензию (MPL-2.0) —
|
||
см. `internal/server/static/LICENSE` после запуска `get-novnc`. |