// 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 '") } 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 }