feat: web-vnc single-binary browser VNC gateway with password access
- 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)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# get-novnc.ps1
|
||||
# Downloads the noVNC web client and installs its assets into the embedded
|
||||
# static directory so they get baked into the single web-vnc binary.
|
||||
#
|
||||
# Usage (from repo root): .\scripts\get-novnc.ps1
|
||||
# Optionally pass a version: .\scripts\get-novnc.ps1 -Version v1.4.0
|
||||
param(
|
||||
[string]$Version = "v1.4.0"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$staticDir = Join-Path $repoRoot "internal\server\static"
|
||||
New-Item -ItemType Directory -Force -Path $staticDir | Out-Null
|
||||
|
||||
$work = Join-Path $env:TEMP "webvnc-novnc-$([guid]::NewGuid())"
|
||||
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
||||
try {
|
||||
$archive = Join-Path $work "novnc.tar.gz"
|
||||
$url = "https://github.com/novnc/noVNC/archive/refs/tags/$Version.tar.gz"
|
||||
Write-Host "Downloading noVNC $Version from $url"
|
||||
Invoke-WebRequest -Uri $url -OutFile $archive -UseBasicParsing
|
||||
|
||||
Write-Host "Extracting..."
|
||||
tar -xzf $archive -C $work
|
||||
$extracted = Get-ChildItem -Directory -Path $work | Where-Object { $_.Name -like "noVNC-*" } | Select-Object -First 1
|
||||
if (-not $extracted) { throw "Extraction produced no noVNC-* directory" }
|
||||
|
||||
# Copy noVNC assets, but keep our custom vnc.html wrapper intact.
|
||||
foreach ($sub in @("core","app","vendor","utils")) {
|
||||
$src = Join-Path $extracted.FullName $sub
|
||||
if (Test-Path $src) {
|
||||
Copy-Item -Path $src -Destination $staticDir -Recurse -Force
|
||||
Write-Host " installed $sub/"
|
||||
}
|
||||
}
|
||||
# Optionally keep noVNC's own page under a different name for reference.
|
||||
if (Test-Path (Join-Path $extracted.FullName "vnc.html")) {
|
||||
Copy-Item -Path (Join-Path $extracted.FullName "vnc.html") -Destination (Join-Path $staticDir "novnc-original.html") -Force
|
||||
Write-Host " copied noVNC vnc.html -> novnc-original.html (our vnc.html stays)"
|
||||
}
|
||||
Write-Host "Done. Rebuild web-vnc to embed the new client: go build -o web-vnc.exe .\cmd\web-vnc"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Path $work -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# get-novnc.sh
|
||||
# Downloads the noVNC web client and installs its assets into the embedded
|
||||
# static directory so they get baked into the single web-vnc binary.
|
||||
#
|
||||
# Usage (from repo root): ./scripts/get-novnc.sh [version]
|
||||
set -euo pipefail
|
||||
VERSION="${1:-v1.4.0}"
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
STATIC_DIR="$REPO_ROOT/internal/server/static"
|
||||
mkdir -p "$STATIC_DIR"
|
||||
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
URL="https://github.com/novnc/noVNC/archive/refs/tags/$VERSION.tar.gz"
|
||||
echo "Downloading noVNC $VERSION from $URL"
|
||||
curl -fL "$URL" -o "$WORK/novnc.tar.gz"
|
||||
|
||||
echo "Extracting..."
|
||||
tar -xzf "$WORK/novnc.tar.gz" -C "$WORK"
|
||||
EXTRACTED="$(find "$WORK" -maxdepth 1 -type d -name 'noVNC-*' | head -n1)"
|
||||
[ -z "$EXTRACTED" ] && { echo "Extraction produced no noVNC-* directory"; exit 1; }
|
||||
|
||||
for sub in core app vendor utils; do
|
||||
[ -d "$EXTRACTED/$sub" ] || continue
|
||||
cp -R "$EXTRACTED/$sub" "$STATIC_DIR/"
|
||||
echo " installed $sub/"
|
||||
done
|
||||
[ -f "$EXTRACTED/vnc.html" ] && cp "$EXTRACTED/vnc.html" "$STATIC_DIR/novnc-original.html" && echo " copied noVNC vnc.html -> novnc-original.html (our vnc.html stays)"
|
||||
echo "Done. Rebuild web-vnc to embed the new client: go build -o web-vnc ./cmd/web-vnc"
|
||||
@@ -0,0 +1,115 @@
|
||||
# get-vnc.ps1
|
||||
# Downloads a portable VNC server (UltraVNC) for Windows and places its
|
||||
# files into a local "vnc" folder so web-vnc can auto-launch it with --spawn.
|
||||
#
|
||||
# Usage (from repo root): .\scripts\get-vnc.ps1
|
||||
# Override the download URL(s): .\scripts\get-vnc.ps1 -Url "https://.../UltraVNC_x64.zip"
|
||||
#
|
||||
# If all download attempts fail, the script opens https://uvnc.com/downloads.html
|
||||
# in your browser — download the UltraVNC .zip (NOT the installer), extract it and
|
||||
# copy winvnc.exe together with its companion .dll/.dsm files into the project's
|
||||
# "vnc" folder, then run `run.bat`.
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$Url = @(
|
||||
"https://downloads.sourceforge.net/project/ultravnc/UltraVNC%201.4.3.0%20bin/UltraVNC_1.4.3.0_x64.zip",
|
||||
"https://downloads.sourceforge.net/project/ultravnc/UltraVNC_1.4.3.0/UltraVNC_1.4.3.0_x64.zip",
|
||||
"https://sourceforge.net/projects/ultravnc/files/UltraVNC%201.4.3.0%20bin/UltraVNC_1.4.3.0_x64.zip/download"
|
||||
)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$vncDir = Join-Path $repoRoot "vnc"
|
||||
New-Item -ItemType Directory -Force -Path $vncDir | Out-Null
|
||||
|
||||
$work = Join-Path $env:TEMP ("webvnc-vnc-" + [guid]::NewGuid())
|
||||
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
||||
|
||||
function Test-Zip([string]$path) {
|
||||
if (-not (Test-Path $path)) { return $false }
|
||||
$fs = [System.IO.File]::OpenRead($path)
|
||||
try {
|
||||
$b = New-Object byte[] 4
|
||||
$n = $fs.Read($b, 0, 4)
|
||||
return ($n -ge 2 -and $b[0] -eq 0x50 -and $b[1] -eq 0x4B) # "PK"
|
||||
} finally { $fs.Close() }
|
||||
}
|
||||
|
||||
function Get-Zip {
|
||||
param([string[]]$urls)
|
||||
$ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 web-vnc-installer"
|
||||
foreach ($u in $urls) {
|
||||
$archive = Join-Path $work "vnc.zip"
|
||||
Remove-Item $archive -ErrorAction SilentlyContinue
|
||||
Write-Host ""
|
||||
Write-Host "Trying: $u"
|
||||
foreach ($attempt in 1..2) {
|
||||
try {
|
||||
Invoke-WebRequest -Uri $u -OutFile $archive -UseBasicParsing -TimeoutSec 300 -UserAgent $ua -MaximumRedirection 20
|
||||
if (Test-Zip $archive) {
|
||||
$len = [Math]::Round((Get-Item $archive).Length / 1MB, 1)
|
||||
Write-Host " downloaded zip (${len} MB)"
|
||||
return $archive
|
||||
}
|
||||
Write-Host " response was not a zip (HTML page?), retrying ..."
|
||||
Remove-Item $archive -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
} catch {
|
||||
Write-Host " attempt ${attempt} failed: $($_.Exception.Message)"
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Manual-Fallback {
|
||||
$page = "https://uvnc.com/downloads.html"
|
||||
Write-Host ""
|
||||
Write-Host "============================================================" -ForegroundColor Yellow
|
||||
Write-Host "Automatic download failed. Opening the UltraVNC download page." -ForegroundColor Yellow
|
||||
Write-Host "============================================================" -ForegroundColor Yellow
|
||||
try { Start-Process $page } catch { Write-Host "Open manually: $page" }
|
||||
Write-Host ""
|
||||
Write-Host "Manual steps:"
|
||||
Write-Host " 1. On the page, download the UltraVNC .zip archive (the portable"
|
||||
Write-Host " package, NOT the installer)."
|
||||
Write-Host " 2. Extract it."
|
||||
Write-Host " 3. Copy winvnc.exe AND its companion .dll/.dsm files (everything"
|
||||
Write-Host " from the extracted folder) into this folder:"
|
||||
Write-Host " $vncDir"
|
||||
Write-Host " 4. Run: .\run.bat"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
try {
|
||||
$archive = Get-Zip -urls $Url
|
||||
if (-not $archive) { Manual-Fallback; exit 1 }
|
||||
|
||||
Write-Host "Extracting ..."
|
||||
Expand-Archive -Path $archive -DestinationPath $work -Force
|
||||
|
||||
$server = Get-ChildItem -Recurse -Path $work -Filter "winvnc.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if (-not $server) {
|
||||
$server = Get-ChildItem -Recurse -Path $work -Filter "tvnserver.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
}
|
||||
if (-not $server) {
|
||||
Write-Host "winvnc.exe not found in the archive." -ForegroundColor Yellow
|
||||
Manual-Fallback
|
||||
exit 1
|
||||
}
|
||||
|
||||
$srcDir = $server.DirectoryName
|
||||
Write-Host "Found VNC server in: $srcDir"
|
||||
Write-Host "Copying files into: $vncDir"
|
||||
Copy-Item -Path (Join-Path $srcDir "*") -Destination $vncDir -Recurse -Force
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done. Installed: $(Join-Path $vncDir $server.Name)" -ForegroundColor Green
|
||||
Write-Host "Now run: .\run.bat"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Path $work -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# list-ips.ps1 — prints reachable IPv4 addresses of this machine (one per line).
|
||||
# Used by run.bat to show http://<ip>:8080 access URLs.
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$ips = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
# Primary: pure .NET network interfaces (locale independent).
|
||||
try {
|
||||
[System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() |
|
||||
Where-Object {
|
||||
$_.OperationalStatus -eq [System.Net.NetworkInformation.OperationalStatus]::Up -and
|
||||
$_.NetworkInterfaceType -ne [System.Net.NetworkInformation.NetworkInterfaceType]::Loopback
|
||||
} |
|
||||
ForEach-Object { $_.GetIPProperties().UnicastAddresses } |
|
||||
Where-Object {
|
||||
$_.Address.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork -and
|
||||
$_.Address.IPAddressToString -notlike "127.*" -and
|
||||
$_.Address.IPAddressToString -notlike "169.254.*"
|
||||
} |
|
||||
ForEach-Object { $ips.Add($_.Address.IPAddressToString) }
|
||||
} catch {}
|
||||
|
||||
# Fallback: parse ipconfig (handles localized output via the "IPv4" token).
|
||||
if ($ips.Count -eq 0) {
|
||||
ipconfig | Select-String -Pattern "IPv4" | ForEach-Object {
|
||||
if ($_ -match "(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})") {
|
||||
$ip = $matches[1]
|
||||
if ($ip -notlike "127.*" -and $ip -notlike "169.254.*") { $ips.Add($ip) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Deduplicate and print.
|
||||
$ips | Sort-Object -Unique | ForEach-Object { Write-Output $_ }
|
||||
@@ -0,0 +1,37 @@
|
||||
@echo off
|
||||
REM ============================================================
|
||||
REM open-firewall.bat - ®âªàë¢ ¥â ¯®àâ 8080 ¢ Windows Firewall.
|
||||
REM ‡ ¯ã᪠âì ®â ¨¬¥¨ ¤¬¨¨áâà â®à (®¤¨ à §).
|
||||
REM ‹¨¡® § ¯ãáâ¨â¥ ¤¢ ¦¤ë: ä ©« á ¬ § ¯à®á¨â ¯à ¢ ¤¬¨¨áâà â®à .
|
||||
REM ============================================================
|
||||
chcp 866 >nul
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM �஢¥àª ¯à ¢ ¤¬¨¨áâà â®à ¨ ¢â®-¯®¢ë襨¥.
|
||||
net session >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo ‡ ¯à®á ¯à ¢ ¤¬¨¨áâà â®à ...
|
||||
powershell -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
set PORT=8080
|
||||
set RULE=web-vnc
|
||||
|
||||
echo “¤ «ïî áâ ஥ ¯à ¢¨«® (¥á«¨ ¥áâì) ...
|
||||
netsh advfirewall firewall delete rule name="%RULE%" >nul 2>nul
|
||||
|
||||
echo „®¡ ¢«ïî à §à¥è î饥 ¯à ¢¨«® ¤«ï ¢å®¤ï饣® TCP-âà 䨪 ¯®àâ %PORT% ...
|
||||
netsh advfirewall firewall add rule name="%RULE%" dir=in action=allow protocol=TCP localport=%PORT%
|
||||
if errorlevel 1 (
|
||||
echo [®è¨¡ª ] �¥ 㤠«®áì ¤®¡ ¢¨âì ¯à ¢¨«®.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ƒ®â®¢®. �®àâ %PORT% ®âªàëâ ¤«ï ¢å®¤ïé¨å ¯®¤ª«î票©.
|
||||
echo ’¥¯¥àì ª web-vnc ¬®¦® ¯®¤ª«îç âìáï á ¤àã£¨å ¬ è¨ á¥â¨ ¯® ¤à¥áã:
|
||||
echo http://IP_�’މ_Œ€˜ˆ�›:%PORT%
|
||||
echo.
|
||||
pause
|
||||
Reference in New Issue
Block a user