feat(run): sync UltraVNC password from run.bat + clearer noVNC errors
- run.bat: call scripts/ensure-vnc-password.ps1 so the password typed once also sets the UltraVNC service VNC password (UAC only when it differs); skip --spawn when 5900 is already taken (don't launch a 2nd winvnc). - scripts/ensure-vnc-password.ps1: new - compares the password against %ProgramData%\UltraVNC\ultravnc.ini (reverse-engineered UltraVNC DES obfuscation) and only writes+restarts the service when it differs. - scripts/set-vnc-password.bat: set UltraVNC service VNC password as admin (createpassword/setpasswd + verify, GUI fallback). Manual fallback. - internal/server/static/vnc.html: don't let the generic 'disconnect' banner overwrite the specific 'securityfailure' reason (noVNC's disconnect event has no reason field, so it always said 'unknown'). - AGENTS.md: document the above (scripts, run.bat, UltraVNC service note).
This commit is contained in:
@@ -63,7 +63,9 @@ scripts/get-novnc.{ps1,sh} скачать noVNC
|
||||
scripts/get-vnc.ps1 скачать портативный UltraVNC в vnc/ (с верификацией zip)
|
||||
scripts/list-ips.ps1 список IPv4 машины (используется run.bat)
|
||||
scripts/open-firewall.bat открыть порт 8080 в Windows Firewall (от админа)
|
||||
run.bat запуск в один клик (спрашивает только пароль)
|
||||
scripts/set-vnc-password.bat задать VNC-пароль сервису UltraVNC (от админа; без него noVNC получает authentication rejected)
|
||||
scripts/ensure-vnc-password.ps1 синхронизирует VNC-пароль UltraVNC с паролем из run.bat (пишет только при несовпадении; UAC только при смене) — вызывается из run.bat
|
||||
run.bat запуск в один клик (спрашивает только пароль); синхронизирует VNC-пароль UltraVNC с введённым (см. ensure-vnc-password.ps1); если 5900 уже занят (сервис UltraVNC) — не порождает второй VNC-сервер, а подключается к существующему
|
||||
```
|
||||
|
||||
## Соглашения и подводные камни
|
||||
@@ -95,4 +97,10 @@ run.bat запуск в один клик (спрашивае
|
||||
`get-vnc.ps1` теперь верифицирует zip-магию и даёт фолбэк на ручную установку).
|
||||
- На Windows захват экрана может требовать запуск VNC-сервера от администратора.
|
||||
- VNC-сервер требует свой пароль; `--vnc-password` передаёт его noVNC
|
||||
автоматически, но пароль VNC-сервера нужно один раз настроить под тот же.
|
||||
автоматически, но пароль VNC-сервера нужно один раз настроить под тот же.
|
||||
**UltraVNC как сервис (`uvnc_service`, LocalSystem):** пароль хранится в
|
||||
`%ProgramData%\UltraVNC\ultravnc.ini`; задать его обычным пользователем через
|
||||
tray-иконку молча не получается (нет прав на запись).
|
||||
`run.bat` синхронизирует этот пароль сам через `scripts/ensure-vnc-password.ps1`
|
||||
(пишет только при несовпадении, UAC только при смене; вручную — `scripts/set-vnc-password.bat` от админа). VNC-пароль = первые 8 байт,
|
||||
поэтому используй ASCII-пароль <= 8 символов и там, и в `run.bat`.
|
||||
|
||||
@@ -25,6 +25,9 @@ const wsURL = (location.protocol === "https:" ? "wss://" : "ws://") + location.h
|
||||
const statusURL = "/api/status";
|
||||
const banner = document.getElementById("banner");
|
||||
let VNC_PASSWORD = "";
|
||||
// a securityfailure / unreachable-VNC banner is more specific than the generic
|
||||
// disconnect event, so we must not let "disconnect" clobber it.
|
||||
let specificError = false;
|
||||
function showBanner(html){ banner.innerHTML = html; banner.style.display = "block"; }
|
||||
function hideBanner(){ banner.style.display = "none"; }
|
||||
|
||||
@@ -64,18 +67,25 @@ async function connect(){
|
||||
}
|
||||
});
|
||||
rfb.addEventListener("securityfailure", (ev) => {
|
||||
specificError = true;
|
||||
const reason = (ev.detail && ev.detail.reason) ? ev.detail.reason : "";
|
||||
showBanner(`
|
||||
<h2>VNC authentication failed</h2>
|
||||
<div>The VNC server rejected the password${reason ? (": " + reason) : "."}</div>
|
||||
<div class="hint">Configure the VNC server (UltraVNC/TightVNC) with the same password you use for the web gate, then retry.</div>
|
||||
<div class="hint">The VNC server password (UltraVNC: the "VNC Password" field) must equal the password you type in run.bat. VNC passwords are effectively the first 8 bytes — use plain ASCII, max 8 chars.</div>
|
||||
<button onclick="location.reload()">Retry</button>`);
|
||||
});
|
||||
rfb.addEventListener("disconnect", (ev) => {
|
||||
// noVNC's "disconnect" event detail is { clean } only — it never carries a reason.
|
||||
// The real cause was already reported via "securityfailure" or the unreachable-VNC
|
||||
// banner, so do not overwrite it with a useless "Reason: unknown".
|
||||
if (specificError) return;
|
||||
const d = ev.detail || {};
|
||||
const clean = !!(d && d.clean);
|
||||
showBanner(`
|
||||
<h2>Disconnected</h2>
|
||||
<div>Reason: ${d.reason || "unknown"}</div>
|
||||
<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>
|
||||
<button onclick="location.reload()">Retry</button>`);
|
||||
});
|
||||
window.addEventListener("beforeunload", () => { try { rfb.disconnect(); } catch(e){} });
|
||||
@@ -85,6 +95,7 @@ async function start(){
|
||||
const st = await fetchStatus();
|
||||
if (st) VNC_PASSWORD = st.vncPassword || "";
|
||||
if (st && st.vnc === false) {
|
||||
specificError = true;
|
||||
const addr = st.vncAddr || "127.0.0.1:5900";
|
||||
const hint = st.spawned
|
||||
? "Auto-launch was attempted but the VNC server is not listening yet. Install UltraVNC/TightVNC on Windows (or x11vnc/TigerVNC on Linux), then restart."
|
||||
|
||||
@@ -82,6 +82,11 @@ echo
|
||||
goto getpw
|
||||
:gotpw
|
||||
|
||||
REM --- 4b. Sync UltraVNC password with the one entered (only when it differs) ---
|
||||
echo ‘¨åந§¨àãî ¯ ஫ì UltraVNC á ¢¢¥¤ñë¬. �ਠ¥á®¢¯ ¤¥¨¨ § ¯à®á¨â ¯à ¢ ¤¬¨¨áâà â®à ...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\ensure-vnc-password.ps1" -Password "%PW%"
|
||||
if errorlevel 1 echo [¯à¥¤ã¯à¥¦¤¥¨¥] �¥ 㤠«®áì á¨åந§¨à®¢ âì ¯ ஫ì UltraVNC - ¯à®¤®«¦ î.
|
||||
|
||||
REM --- 5. •íè ¯ ஫ï + â®â ¦¥ ¯ à®«ì ¤«ï VNC-á¥à¢¥à ---
|
||||
echo ƒ¥¥à¨àãî åíè ¯ ஫ï ...
|
||||
for /f "delims=" %%i in ('web-vnc.exe --gen-hash "%PW%"') do set "HASH=%%i"
|
||||
@@ -104,7 +109,14 @@ REM --- 7.
|
||||
echo ‡ ¯ã᪠î web-vnc http://localhost:8080
|
||||
echo Žáâ ®¢¨âì: Ctrl+C
|
||||
echo.
|
||||
web-vnc.exe --password-hash "%HASH%" %SPAWNARGS% --spawn --listen :8080
|
||||
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.
|
||||
set "SPAWNFLAG="
|
||||
)
|
||||
web-vnc.exe --password-hash "%HASH%" %SPAWNARGS% %SPAWNFLAG% --listen :8080
|
||||
echo.
|
||||
echo ‘¥à¢¥à ®áâ ®¢«¥.
|
||||
pause
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# ensure-vnc-password.ps1 - keep the UltraVNC service VNC password in sync
|
||||
# with the password the user types in run.bat. Called from run.bat.
|
||||
#
|
||||
# It only WRITES when the password actually differs (so a normal launch with
|
||||
# the same password does nothing and asks for NO admin rights). When it must
|
||||
# change the password it re-launches itself elevated (one UAC prompt) and
|
||||
# restarts the uvnc_service so the new password takes effect.
|
||||
#
|
||||
# Usage: powershell -NoProfile -ExecutionPolicy Bypass -File ensure-vnc-password.ps1 -Password "<pw>"
|
||||
# Exit codes: 0 = password matches (or was set OK); 1 = could not set it.
|
||||
|
||||
param([Parameter(Mandatory=$true)][string]$Password)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ini = Join-Path $env:ProgramData "UltraVNC\ultravnc.ini"
|
||||
|
||||
function Write-Info($m){ Write-Host "ensure-vnc-password: $m" }
|
||||
|
||||
if (-not (Test-Path $ini)) {
|
||||
# No installed UltraVNC service config (e.g. portable copy / TightVNC) - nothing to sync.
|
||||
Write-Info "no %ProgramData%\UltraVNC\ultravnc.ini - nothing to sync."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- VNC password obfuscation: DES-ECB(key=bitrev([23,82,107,6,35,78,88,7]), pw8) + 1-byte checksum ---
|
||||
function Invoke-BitRev([byte]$b){ $r=0; for($i=0;$i -lt 8;$i++){ $r = $r -bor ((($b -shr $i) -band 1) -shl (7-$i)) }; return [byte]$r }
|
||||
function Get-ExpectedPasswd([string]$pw){
|
||||
$fixedkey = [byte[]](23,82,107,6,35,78,88,7)
|
||||
$kr = New-Object byte[] 8; for($i=0;$i -lt 8;$i++){ $kr[$i] = Invoke-BitRev $fixedkey[$i] }
|
||||
$pb = [System.Text.Encoding]::ASCII.GetBytes($pw)
|
||||
if ($pb.Length -gt 8) { $pb = $pb[0..7] }
|
||||
$plain = New-Object byte[] 8; for($i=0;$i -lt 8;$i++){ if($i -lt $pb.Length){ $plain[$i] = $pb[$i] } }
|
||||
$des = New-Object System.Security.Cryptography.DESCryptoServiceProvider
|
||||
$des.Mode = [System.Security.Cryptography.CipherMode]::ECB
|
||||
$des.Padding = [System.Security.Cryptography.PaddingMode]::None
|
||||
$des.Key = $kr
|
||||
$cipher = $des.CreateEncryptor().TransformFinalBlock($plain,0,8)
|
||||
$sum = 0; foreach($b in $cipher){ $sum = ($sum + $b) -band 0xFF }
|
||||
$h = ($cipher | ForEach-Object { $_.ToString("X2") }) -join ""
|
||||
return $h + $sum.ToString("X2")
|
||||
}
|
||||
|
||||
function Get-CurrentPasswd{
|
||||
$line = Get-Content $ini -ErrorAction SilentlyContinue | Where-Object { $_ -match '^\s*passwd\s*=' -and $_ -notmatch 'passwd2' } | Select-Object -First 1
|
||||
if (-not $line) { return "" }
|
||||
return ($line -split '=',2)[1].Trim()
|
||||
}
|
||||
|
||||
$expected = Get-ExpectedPasswd $Password
|
||||
$current = Get-CurrentPasswd
|
||||
if ($current -ieq $expected) {
|
||||
Write-Info "UltraVNC password already matches - nothing to do."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Info "UltraVNC password differs (ini=$current, expected=$expected). Need to set it (admin required)."
|
||||
|
||||
# Locate UltraVNC install + password tools.
|
||||
$uvncDirs = @(
|
||||
"$env:ProgramFiles\uvnc bvba\UltraVNC",
|
||||
"$env:ProgramFiles\UltraVNC",
|
||||
"${env:ProgramFiles(x86)}\uvnc bvba\UltraVNC",
|
||||
"${env:ProgramFiles(x86)}\UltraVNC"
|
||||
)
|
||||
$uvnc = $uvncDirs | Where-Object { Test-Path "$_\winvnc.exe" } | Select-Object -First 1
|
||||
if (-not $uvnc) { Write-Info "UltraVNC install not found - cannot set password."; exit 1 }
|
||||
|
||||
# Are we admin?
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
if (-not $isAdmin) {
|
||||
Write-Info "re-launching elevated to set the password..."
|
||||
$p = Start-Process -FilePath "powershell.exe" -Verb RunAs -Wait -PassThru -ArgumentList @("-NoProfile","-ExecutionPolicy","Bypass","-File","`"$PSCommandPath`"","-Password","`"$Password`"")
|
||||
if ($p.ExitCode -ne 0) { Write-Info "elevated run failed (exit $($p.ExitCode))."; exit 1 }
|
||||
$now = Get-CurrentPasswd
|
||||
if ($now -ieq $expected) { Write-Info "password set OK."; exit 0 }
|
||||
Write-Info "password still not updated after elevated run (ini=$now)."; exit 1
|
||||
}
|
||||
|
||||
# --- admin path: actually set the password ---
|
||||
function Set-Tool{
|
||||
param([string]$exe)
|
||||
if (-not (Test-Path $exe)) { return $false }
|
||||
try {
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $exe
|
||||
$psi.Arguments = "`"$Password`""
|
||||
$psi.UseShellExecute = $false
|
||||
$p = [System.Diagnostics.Process]::Start($psi)
|
||||
if (-not $p.WaitForExit(5000)) { $p.Kill() }
|
||||
return ($p.ExitCode -eq 0)
|
||||
} catch { return $false }
|
||||
}
|
||||
|
||||
$ok = $false
|
||||
if (Set-Tool (Join-Path $uvnc "createpassword.exe")) { $ok = $true }
|
||||
$now = Get-CurrentPasswd
|
||||
if ($now -ine $expected -and (Test-Path (Join-Path $uvnc "setpasswd.exe"))) {
|
||||
# fallback to setpasswd.exe "<pw>"
|
||||
try {
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = Join-Path $uvnc "setpasswd.exe"
|
||||
$psi.Arguments = "`"$Password`""
|
||||
$psi.UseShellExecute = $false
|
||||
$p = [System.Diagnostics.Process]::Start($psi)
|
||||
if (-not $p.WaitForExit(5000)) { $p.Kill() }
|
||||
} catch {}
|
||||
$now = Get-CurrentPasswd
|
||||
}
|
||||
|
||||
if ($now -ine $expected) {
|
||||
Write-Info "tools did not update the password (ini=$now). Set it manually via UltraVNC Admin Properties as admin."
|
||||
exit 1
|
||||
}
|
||||
Write-Info "password updated in $ini."
|
||||
|
||||
# Restart the service so it reloads the new password.
|
||||
$svc = "uvnc_service"
|
||||
$svcExists = $false
|
||||
try { $null = (& sc.exe query $svc 2>$null); $svcExists = ($LASTEXITCODE -eq 0) } catch {}
|
||||
if ($svcExists) {
|
||||
Write-Info "restarting service $svc ..."
|
||||
& net.exe stop $svc 2>$null | Out-Null
|
||||
& net.exe start $svc 2>$null | Out-Null
|
||||
}
|
||||
exit 0
|
||||
@@ -0,0 +1,109 @@
|
||||
@echo off
|
||||
REM ============================================================
|
||||
REM set-vnc-password.bat - reliably set the UltraVNC VNC password
|
||||
REM for the running service (uvnc_service / LocalSystem), which
|
||||
REM reads %ProgramData%\UltraVNC\ultravnc.ini.
|
||||
REM
|
||||
REM Runs AS ADMINISTRATOR (auto-elevates). It first tries the
|
||||
REM UltraVNC CLI helpers (createpassword.exe / setpasswd.exe) and
|
||||
REM VERIFIES the password actually changed in ultravnc.ini; if they
|
||||
REM fail silently (as they can), it opens the UltraVNC settings GUI
|
||||
REM so you can set "VNC Password" there. Then it restarts the
|
||||
REM service. Use the SAME password in run.bat (ASCII, max 8 chars).
|
||||
REM ============================================================
|
||||
chcp 866 >nul
|
||||
cd /d "%~dp0"
|
||||
|
||||
net session >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo Requesting administrator rights ...
|
||||
powershell -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
set "UVNC="
|
||||
for %%P in (
|
||||
"C:\Program Files\uvnc bvba\UltraVNC"
|
||||
"C:\Program Files\UltraVNC"
|
||||
"C:\Program Files (x86)\uvnc bvba\UltraVNC"
|
||||
"C:\Program Files (x86)\UltraVNC"
|
||||
) do (
|
||||
if exist "%%~P\winvnc.exe" if not defined UVNC set "UVNC=%%~P"
|
||||
)
|
||||
if not defined UVNC (
|
||||
echo [error] UltraVNC installation not found.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo UltraVNC: %UVNC%
|
||||
set "INI=%ProgramData%\UltraVNC\ultravnc.ini"
|
||||
|
||||
:getpw
|
||||
set "PW="
|
||||
set /p "PW=Enter VNC password (ASCII, max 8 chars): "
|
||||
if "%PW%"=="" ( echo Password cannot be empty. & goto getpw )
|
||||
|
||||
REM record old passwd
|
||||
set "OLDPW="
|
||||
if exist "%INI%" for /f "tokens=2 delims==" %%a in ('findstr /b /i "passwd=" "%INI%" 2^>nul ^| findstr /v /i "passwd2"') do set "OLDPW=%%a"
|
||||
echo Old passwd=%OLDPW%
|
||||
|
||||
REM try CLI helpers, verify after each
|
||||
set "DONE=0"
|
||||
if exist "%UVNC%\createpassword.exe" (
|
||||
echo Trying createpassword.exe ...
|
||||
"%UVNC%\createpassword.exe" "%PW%" >nul 2>nul
|
||||
call :checkchanged
|
||||
)
|
||||
if "%DONE%"=="0" if exist "%UVNC%\setpasswd.exe" (
|
||||
echo Trying setpasswd.exe ...
|
||||
"%UVNC%\setpasswd.exe" "%PW%" "%PW%" >nul 2>nul
|
||||
call :checkchanged
|
||||
)
|
||||
if "%DONE%"=="0" (
|
||||
echo.
|
||||
echo CLI helpers did not change the password file. Opening UltraVNC settings...
|
||||
echo -> right-click the UltraVNC tray icon, choose Admin Properties,
|
||||
echo set the "VNC Password" to: %PW%
|
||||
echo -> close the settings/tray app, then come back here and press a key.
|
||||
echo.
|
||||
start "" "%UVNC%\winvnc.exe"
|
||||
pause
|
||||
call :checkchanged
|
||||
)
|
||||
set "PW="
|
||||
if "%DONE%"=="0" (
|
||||
echo [warn] Password still not changed in %INI%.
|
||||
echo Set it manually in UltraVNC Admin Properties as administrator.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM restart the service so it reloads the password
|
||||
set "SVC=uvnc_service"
|
||||
sc query %SVC% >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [warn] Service '%SVC%' not found; restart winvnc manually if it runs in app mode.
|
||||
goto showini
|
||||
)
|
||||
echo Restarting service %SVC% ...
|
||||
net stop %SVC% >nul 2>nul
|
||||
net start %SVC%
|
||||
|
||||
:showini
|
||||
echo.
|
||||
echo --- %INI% ---
|
||||
if exist "%INI%" type "%INI%"
|
||||
echo.
|
||||
echo Done. Use the SAME password in run.bat, then reconnect in the browser.
|
||||
pause
|
||||
exit /b
|
||||
|
||||
:checkchanged
|
||||
set "NEWPW="
|
||||
if exist "%INI%" for /f "tokens=2 delims==" %%a in ('findstr /b /i "passwd=" "%INI%" 2^>nul ^| findstr /v /i "passwd2"') do set "NEWPW=%%a"
|
||||
if not "%NEWPW%"=="%OLDPW%" (
|
||||
echo Password file updated. New passwd=%NEWPW%
|
||||
set "DONE=1"
|
||||
)
|
||||
goto :eof
|
||||
Reference in New Issue
Block a user