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:
Codex
2026-07-30 19:15:09 +03:00
parent cf0915878b
commit 6a1ee64b14
5 changed files with 271 additions and 5 deletions
+13 -2
View File
@@ -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."