- 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)
105 lines
4.1 KiB
Go
105 lines
4.1 KiB
Go
// 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
|
|
}
|