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,104 @@
|
||||
// Package config holds runtime configuration for the web-vnc gateway.
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config is the resolved application configuration.
|
||||
type Config struct {
|
||||
ListenAddr string // HTTP/WS listen address, e.g. ":8080"
|
||||
VNCAddr string // upstream VNC server address, e.g. "127.0.0.1:5900"
|
||||
VNCPassword string // optional VNC server password, auto-sent to noVNC (empty = none)
|
||||
PasswordHash string // hash of the shared web access password
|
||||
SessionSecret []byte // HMAC key for signing session cookies
|
||||
CookieName string // name of the session cookie
|
||||
SessionTTL time.Duration // validity of a session cookie
|
||||
WebRoot string // optional on-disk override for static files (empty = embed.FS)
|
||||
SpawnVNC bool // auto-launch a VNC server on startup
|
||||
SpawnCommand string // optional explicit command to launch the VNC server
|
||||
NoVNCPath string // path serving the noVNC client page
|
||||
RelayPath string // path of the WS relay endpoint
|
||||
LoginPath string
|
||||
LogoutPath string
|
||||
StatusPath string // VNC health-check endpoint path
|
||||
OS string // resolved host OS (GOOS) for the spawner
|
||||
}
|
||||
|
||||
// Parse reads flags + env and returns a validated Config.
|
||||
func Parse(args []string) (Config, error) {
|
||||
fs := flag.NewFlagSet("web-vnc", flag.ContinueOnError)
|
||||
var (
|
||||
secretStr string
|
||||
)
|
||||
c := Config{}
|
||||
|
||||
fs.StringVar(&c.ListenAddr, "listen", envStr("WEBVNC_LISTEN", ":8080"), "HTTP listen address")
|
||||
fs.StringVar(&c.VNCAddr, "vnc", envStr("WEBVNC_VNC", "127.0.0.1:5900"), "upstream VNC server address")
|
||||
fs.StringVar(&c.VNCPassword, "vnc-password", envStr("WEBVNC_VNC_PASSWORD", ""), "VNC server password to auto-send to noVNC (empty = server has no VNC password)")
|
||||
fs.StringVar(&c.PasswordHash, "password-hash", envStr("WEBVNC_PASSWORD_HASH", ""), "hash of the shared password (use --gen-hash to create one)")
|
||||
fs.StringVar(&secretStr, "session-secret", envStr("WEBVNC_SESSION_SECRET", ""), "HMAC secret for signing session cookies (random if empty)")
|
||||
fs.StringVar(&c.CookieName, "cookie-name", "webvnc_session", "session cookie name")
|
||||
fs.DurationVar(&c.SessionTTL, "session-ttl", envDur("WEBVNC_SESSION_TTL", 8*time.Hour), "session cookie lifetime")
|
||||
fs.StringVar(&c.WebRoot, "web-root", envStr("WEBVNC_WEB_ROOT", ""), "optional on-disk static files dir (overrides embedded assets)")
|
||||
fs.BoolVar(&c.SpawnVNC, "spawn", envBool("WEBVNC_SPAWN", false), "auto-launch a detected VNC server on startup")
|
||||
fs.StringVar(&c.SpawnCommand, "spawn-command", envStr("WEBVNC_SPAWN_COMMAND", ""), "explicit command to launch the VNC server (overrides auto-detection)")
|
||||
fs.StringVar(&c.NoVNCPath, "novnc-path", "/vnc.html", "path serving the noVNC client page")
|
||||
fs.StringVar(&c.RelayPath, "relay-path", "/vnc", "WebSocket relay endpoint path")
|
||||
fs.StringVar(&c.LoginPath, "login-path", "/login", "login endpoint path")
|
||||
fs.StringVar(&c.LogoutPath, "logout-path", "/logout", "logout endpoint path")
|
||||
fs.StringVar(&c.StatusPath, "status-path", "/api/status", "VNC health check endpoint path")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
c.OS = runtime.GOOS
|
||||
if secretStr != "" {
|
||||
c.SessionSecret = []byte(secretStr)
|
||||
} else {
|
||||
c.SessionSecret = randBytes(32)
|
||||
}
|
||||
|
||||
if c.PasswordHash == "" {
|
||||
return c, fmt.Errorf("a password is required: provide --password-hash (hash) or run 'web-vnc --gen-hash <password>'")
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func envStr(key, fallback string) string {
|
||||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func envBool(key string, fallback bool) bool {
|
||||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||||
return strings.EqualFold(v, "true") || v == "1" || strings.EqualFold(v, "yes")
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func envDur(key string, fallback time.Duration) time.Duration {
|
||||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||||
if d, err := time.ParseDuration(v); err == nil {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func randBytes(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic("crypto/rand failed: " + err.Error())
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user