Compare commits
2
Commits
71e2fef4d9
...
0779b4327c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0779b4327c | ||
|
|
120d4e9498 |
@@ -58,7 +58,7 @@ 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 + noVNC core/app/vendor)
|
||||
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)
|
||||
@@ -92,6 +92,23 @@ run.bat запуск в один клик (спрашивае
|
||||
`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.
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ func (s *Server) Handler() http.Handler {
|
||||
|
||||
mux.Handle(s.cfg.RelayPath, s.requireSession(http.HandlerFunc(s.handleRelay)))
|
||||
mux.Handle(s.cfg.NoVNCPath, s.requireSession(http.HandlerFunc(s.serveNoVNCPage)))
|
||||
|
||||
// favicon is fetched by the browser on every page (incl. the login form);
|
||||
// serve it without a session so it doesn't redirect to /login and 404.
|
||||
mux.HandleFunc("/favicon.ico", s.handleFavicon)
|
||||
|
||||
mux.Handle("/", s.requireSession(http.HandlerFunc(s.handleIndexOrStatic)))
|
||||
|
||||
return s.logRequest(mux)
|
||||
@@ -110,6 +115,19 @@ func (s *Server) handleRelay(w http.ResponseWriter, r *http.Request) {
|
||||
s.relay.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// handleFavicon serves the embedded favicon.ico with a long cache. It is
|
||||
// registered outside requireSession so it loads on the login page too.
|
||||
func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := fs.ReadFile(s.static, "favicon.ico")
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/x-icon")
|
||||
w.Header().Set("Cache-Control", "public, max-age=604800")
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
func (s *Server) serveNoVNCPage(w http.ResponseWriter, r *http.Request) {
|
||||
if data, err := fs.ReadFile(s.static, "vnc.html"); err == nil {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 446 B |
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Web VNC</title>
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||
<style>
|
||||
html,body{margin:0;padding:0;width:100%;height:100%;background:#000;overflow:hidden}
|
||||
#screen{width:100%;height:100%;display:block}
|
||||
@@ -21,6 +22,18 @@
|
||||
<div id="screen"></div>
|
||||
<div id="banner" style="display:none"></div>
|
||||
<script type="module">
|
||||
// noVNC prints "noVNC requires a secure context (TLS). Expect crashes!" whenever
|
||||
// window.isSecureContext is false (i.e. plain http over a LAN IP). For ordinary
|
||||
// VNC-password auth noVNC uses its own pure-JS DES (core/des.js) and does NOT need
|
||||
// crypto.subtle, so the warning is purely cosmetic. Suppress exactly that one line
|
||||
// (let every other console.error through) by wrapping console.error before rfb.js
|
||||
// imports and binds Log.Error to it.
|
||||
const _origConsoleError = console.error.bind(console);
|
||||
const _SECURE_CTX_RE = /noVNC requires a secure context \(TLS\)/;
|
||||
console.error = function(...args) {
|
||||
if (args.length && typeof args[0] === "string" && _SECURE_CTX_RE.test(args[0])) return;
|
||||
_origConsoleError(...args);
|
||||
};
|
||||
const wsURL = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/vnc";
|
||||
const statusURL = "/api/status";
|
||||
const banner = document.getElementById("banner");
|
||||
@@ -85,7 +98,7 @@ async function connect(){
|
||||
showBanner(`
|
||||
<h2>Disconnected</h2>
|
||||
<div>Reason: ${clean ? "connection closed" : "connection lost"}. See the browser console (F12) for the exact error.</div>
|
||||
<div class="hint">The "noVNC requires a secure context (TLS)" console warning is harmless for plain VNC-password auth. A real failure is usually a VNC password mismatch.</div>
|
||||
<div class="hint">A real failure is usually a VNC password mismatch. See the browser console (F12) for details.</div>
|
||||
<button onclick="location.reload()">Retry</button>`);
|
||||
});
|
||||
window.addEventListener("beforeunload", () => { try { rfb.disconnect(); } catch(e){} });
|
||||
|
||||
@@ -19,7 +19,7 @@ func (s *Server) renderLogin(w http.ResponseWriter, errMsg string) {
|
||||
const loginPageHTML = `<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Web VNC</title><style>
|
||||
<title>Web VNC</title><link rel="icon" href="/favicon.ico" type="image/x-icon"><style>
|
||||
*{box-sizing:border-box}
|
||||
body{font-family:system-ui,Segoe UI,sans-serif;background:#0f1720;color:#e2e8f0;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
|
||||
.card{background:#111827;padding:2rem 2.25rem;border-radius:14px;box-shadow:0 10px 40px rgba(0,0,0,.5);width:320px}
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
@echo off
|
||||
@echo off
|
||||
REM ============================================================
|
||||
REM web-vnc launcher - запуск в один клик. Спрашивает только пароль.
|
||||
REM web-vnc launcher - one-click start, asks only for the password.
|
||||
REM ============================================================
|
||||
chcp 65001 >nul
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo.
|
||||
echo === web-vnc: VNC в браузере ===
|
||||
echo === web-vnc: VNC in the browser ===
|
||||
echo.
|
||||
|
||||
REM --- 1. Бинарник ---
|
||||
REM --- 1. Binary ---
|
||||
if exist "web-vnc.exe" goto havebin
|
||||
where go >nul 2>nul
|
||||
if not errorlevel 1 goto dogobuild
|
||||
echo [ошибка] web-vnc.exe не найден, а Go не установлен.
|
||||
echo Установите Go с https://go.dev/dl/ либо положите собранный web-vnc.exe рядом.
|
||||
echo [error] web-vnc.exe not found and Go is not installed.
|
||||
echo Install Go from https://go.dev/dl/ or place a built web-vnc.exe next to this script.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
:dogobuild
|
||||
echo Собираю web-vnc.exe ...
|
||||
echo Building web-vnc.exe ...
|
||||
go build -o web-vnc.exe .\cmd\web-vnc
|
||||
if errorlevel 1 goto buildfail
|
||||
:havebin
|
||||
|
||||
REM --- 2. noVNC-клиент ---
|
||||
REM --- 2. noVNC client ---
|
||||
if exist "internal\server\static\core\rfb.js" goto havenovnc
|
||||
echo noVNC-клиент ещё не встроен. Пытаюсь скачать ...
|
||||
echo noVNC client is not bundled yet. Trying to download ...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\get-novnc.ps1"
|
||||
if errorlevel 1 goto novncfail
|
||||
echo Пересобираю с noVNC ...
|
||||
echo Rebuilding with noVNC ...
|
||||
go build -o web-vnc.exe .\cmd\web-vnc
|
||||
goto havenovnc
|
||||
:novncfail
|
||||
echo [предупреждение] Не удалось скачать noVNC (нет интернета?).
|
||||
echo В браузере откроется страница с подсказкой. Повторите позже: scripts\get-novnc.ps1
|
||||
echo [warning] Could not download noVNC (no internet?).
|
||||
echo The browser will open a page with a hint. Retry later: scripts\get-novnc.ps1
|
||||
echo.
|
||||
:havenovnc
|
||||
|
||||
REM --- 3. VNC-сервер (поиск/загрузка) ---
|
||||
REM --- 3. VNC server (find/download) ---
|
||||
set "SPAWNARGS="
|
||||
set "SPAWN_CMD="
|
||||
if exist "vnc\winvnc.exe" goto uselocalvnc
|
||||
@@ -49,7 +48,7 @@ if exist "C:\Program Files\uvnc bvba\UltraVNC\winvnc.exe" goto autodetectvnc
|
||||
if exist "C:\Program Files (x86)\uvnc bvba\UltraVNC\winvnc.exe" goto autodetectvnc
|
||||
if exist "C:\Program Files\TightVNC\tvnserver.exe" goto autodetectvnc
|
||||
if exist "C:\Program Files (x86)\TightVNC\tvnserver.exe" goto autodetectvnc
|
||||
echo VNC-сервер не найден. Пытаюсь скачать портативный UltraVNC ...
|
||||
echo VNC server not found. Trying to download a portable UltraVNC ...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\get-vnc.ps1"
|
||||
if errorlevel 1 goto novncserver
|
||||
if exist "vnc\winvnc.exe" goto uselocalvnc
|
||||
@@ -59,75 +58,75 @@ goto novncserver
|
||||
if exist "vnc\winvnc.exe" set "SPAWN_CMD=vnc\winvnc.exe -run"
|
||||
if exist "vnc\tvnserver.exe" if not defined SPAWN_CMD set "SPAWN_CMD=vnc\tvnserver.exe -run"
|
||||
set "SPAWNARGS=--spawn-command "%SPAWN_CMD%""
|
||||
echo Найден локальный VNC-сервер: %SPAWN_CMD%
|
||||
echo Found local VNC server: %SPAWN_CMD%
|
||||
goto vncdone
|
||||
:autodetectvnc
|
||||
echo VNC-сервер найден в системе (авто-определение при запуске).
|
||||
echo VNC server found in the system (auto-detection on start).
|
||||
goto vncdone
|
||||
:novncserver
|
||||
echo [предупреждение] VNC-сервер не удалось получить.
|
||||
echo Без VNC-сервера рабочий стол не будет транслироваться.
|
||||
echo Установите UltraVNC/TightVNC вручную или повторите: scripts\get-vnc.ps1
|
||||
echo [warning] Could not get a VNC server.
|
||||
echo Without a VNC server the desktop will not be streamed.
|
||||
echo Install UltraVNC/TightVNC manually or retry: scripts\get-vnc.ps1
|
||||
echo.
|
||||
:vncdone
|
||||
|
||||
REM --- 4. Пароль ---
|
||||
REM --- 4. Password ---
|
||||
:getpw
|
||||
set "PW="
|
||||
set /p "PW=Введите пароль доступа: "
|
||||
set /p "PW=Enter access password: "
|
||||
if "%PW%"=="" goto emptypw
|
||||
goto gotpw
|
||||
:emptypw
|
||||
echo Пароль не может быть пустым.
|
||||
echo Password cannot be empty.
|
||||
goto getpw
|
||||
:gotpw
|
||||
|
||||
REM --- 4b. Sync UltraVNC password with the one entered (only when it differs) ---
|
||||
echo Синхронизирую пароль UltraVNC с введённым. При несовпадении запросит права администратора...
|
||||
echo Syncing UltraVNC password with the one entered. It will ask for admin rights if they differ...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\ensure-vnc-password.ps1" -Password "%PW%"
|
||||
if errorlevel 1 echo [предупреждение] Не удалось синхронизировать пароль UltraVNC - продолжаю.
|
||||
if errorlevel 1 echo [warning] Could not sync the UltraVNC password - continuing.
|
||||
|
||||
REM --- 5. Хэш пароля + тот же пароль для VNC-сервера ---
|
||||
echo Генерирую хэш пароля ...
|
||||
REM --- 5. Password hash + same password for the VNC server ---
|
||||
echo Generating password hash ...
|
||||
for /f "delims=" %%i in ('web-vnc.exe --gen-hash "%PW%"') do set "HASH=%%i"
|
||||
if "%HASH%"=="" goto hashfail
|
||||
REM Тот же пароль будет автоматически передан noVNC для авторизации на VNC-сервере.
|
||||
REM The same password is auto-sent to noVNC to authenticate against the VNC server.
|
||||
set "WEBVNC_VNC_PASSWORD=%PW%"
|
||||
set "PW="
|
||||
|
||||
REM --- 6. Доступные адреса ---
|
||||
REM --- 6. Available addresses ---
|
||||
echo.
|
||||
echo === Адреса для подключения (порт 8080) ===
|
||||
echo === Addresses to connect to (port 8080) ===
|
||||
echo http://localhost:8080
|
||||
for /f "delims=" %%a in ('powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\list-ips.ps1"') do echo http://%%a:8080
|
||||
echo.
|
||||
echo Если с другой машины не открывается - один раз выполните от имени
|
||||
echo администратора: scripts\open-firewall.bat
|
||||
echo If it does not open from another machine, run once as administrator:
|
||||
echo scripts\open-firewall.bat
|
||||
echo.
|
||||
|
||||
REM --- 7. Запуск ---
|
||||
echo Запускаю web-vnc на http://localhost:8080
|
||||
echo Остановить: Ctrl+C
|
||||
REM --- 7. Launch ---
|
||||
echo Starting web-vnc on http://localhost:8080
|
||||
echo Stop: Ctrl+C
|
||||
echo.
|
||||
REM --- 6b. 5900 already listening? -> do NOT spawn a 2nd VNC server (avoid conflict with the UltraVNC service) ---
|
||||
set "SPAWNFLAG=--spawn"
|
||||
powershell -NoProfile -Command "try{$c=New-Object Net.Sockets.TcpClient('127.0.0.1',5900);$c.Close();exit 0}catch{exit 1}"
|
||||
if not errorlevel 1 (
|
||||
echo VNC-сервер уже слушает 127.0.0.1:5900 - подключаюсь к нему без --spawn.
|
||||
echo VNC server already listening on 127.0.0.1:5900 - connecting to it without --spawn.
|
||||
set "SPAWNFLAG="
|
||||
)
|
||||
web-vnc.exe --password-hash "%HASH%" %SPAWNARGS% %SPAWNFLAG% --listen :8080
|
||||
echo.
|
||||
echo Сервер остановлен.
|
||||
echo Server stopped.
|
||||
pause
|
||||
exit /b
|
||||
|
||||
:buildfail
|
||||
echo Сборка не удалась.
|
||||
echo Build failed.
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
:hashfail
|
||||
echo [ошибка] Не удалось сгенерировать хэш пароля.
|
||||
echo [error] Could not generate the password hash.
|
||||
pause
|
||||
exit /b 1
|
||||
+12
-13
@@ -1,16 +1,15 @@
|
||||
@echo off
|
||||
@echo off
|
||||
REM ============================================================
|
||||
REM open-firewall.bat - открывает порт 8080 в Windows Firewall.
|
||||
REM Запускать от имени администратора (один раз).
|
||||
REM Либо запустите дважды: файл сам запросит права администратора.
|
||||
REM open-firewall.bat - opens port 8080 in the Windows Firewall.
|
||||
REM Run as administrator (once). Or just launch it: it will request
|
||||
REM admin rights itself.
|
||||
REM ============================================================
|
||||
chcp 65001 >nul
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM Проверка прав администратора и авто-повышение.
|
||||
REM Admin check + auto-elevate.
|
||||
net session >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo Запрос прав администратора ...
|
||||
echo Requesting administrator rights ...
|
||||
powershell -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
@@ -18,20 +17,20 @@ if errorlevel 1 (
|
||||
set PORT=8080
|
||||
set RULE=web-vnc
|
||||
|
||||
echo Удаляю старое правило (если есть) ...
|
||||
echo Removing the old rule (if any) ...
|
||||
netsh advfirewall firewall delete rule name="%RULE%" >nul 2>nul
|
||||
|
||||
echo Добавляю разрешающее правило для входящего TCP-трафика на порт %PORT% ...
|
||||
echo Adding an allow rule for inbound TCP traffic on port %PORT% ...
|
||||
netsh advfirewall firewall add rule name="%RULE%" dir=in action=allow protocol=TCP localport=%PORT%
|
||||
if errorlevel 1 (
|
||||
echo [ошибка] Не удалось добавить правило.
|
||||
echo [error] Could not add the rule.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Готово. Порт %PORT% открыт для входящих подключений.
|
||||
echo Теперь к web-vnc можно подключаться с других машин сети по адресу:
|
||||
echo http://IP_ЭТОЙ_МАШИНЫ:%PORT%
|
||||
echo Done. Port %PORT% is open for inbound connections.
|
||||
echo You can now connect to web-vnc from other machines on the network at:
|
||||
echo http://THIS_MACHINE_IP:%PORT%
|
||||
echo.
|
||||
pause
|
||||
Reference in New Issue
Block a user