main
1package cli
2
3import (
4 "flag"
5 "fmt"
6 "os"
7 "strings"
8 "time"
9)
10
11// headerList is a flag.Value that collects multiple -H flags.
12type headerList []string
13
14func (h *headerList) String() string {
15 return strings.Join(*h, ", ")
16}
17
18func (h *headerList) Set(value string) error {
19 *h = append(*h, value)
20 return nil
21}
22
23// ParseFlags parses command-line flags and returns a Config.
24func ParseFlags(args []string) (*Config, error) {
25 cfg := DefaultConfig()
26
27 fs := flag.NewFlagSet("govnc", flag.ContinueOnError)
28
29 // Custom usage
30 fs.Usage = func() {
31 fmt.Fprint(os.Stderr, usage)
32 }
33
34 // Connection flags
35 var headers headerList
36 fs.Var(&headers, "H", "Add custom header (repeatable)")
37 fs.Var(&headers, "header", "Add custom header (repeatable)")
38 fs.StringVar(&cfg.Connection.Cookie, "b", "", "Send cookies")
39 fs.StringVar(&cfg.Connection.Cookie, "cookie", "", "Send cookies")
40 fs.StringVar(&cfg.Connection.UserAgent, "A", "govnc/1.0", "User-Agent header")
41 fs.StringVar(&cfg.Connection.UserAgent, "user-agent", "govnc/1.0", "User-Agent header")
42 fs.StringVar(&cfg.Connection.Proxy, "x", "", "Use proxy (http, socks5)")
43 fs.StringVar(&cfg.Connection.Proxy, "proxy", "", "Use proxy (http, socks5)")
44
45 var connectTimeout int
46 fs.IntVar(&connectTimeout, "connect-timeout", 30, "Connection timeout in seconds")
47 fs.BoolVar(&cfg.Connection.Insecure, "k", false, "Skip TLS verification")
48 fs.BoolVar(&cfg.Connection.Insecure, "insecure", false, "Skip TLS verification")
49
50 // Authentication flags
51 fs.StringVar(&cfg.VNC.User, "u", "", "VNC authentication (user:password)")
52 fs.StringVar(&cfg.VNC.User, "user", "", "VNC authentication (user:password)")
53 fs.BoolVar(&cfg.VNC.AuthNone, "auth-none", false, "Force no authentication")
54
55 // VNC flags
56 fs.BoolVar(&cfg.VNC.Shared, "shared", true, "Request shared session")
57 fs.BoolVar(&cfg.VNC.Exclusive, "exclusive", false, "Request exclusive session")
58
59 var encodings string
60 fs.StringVar(&encodings, "encoding", "", "Comma-separated encodings")
61
62 // Input flags
63 fs.StringVar(&cfg.Input.InputFile, "i", "", "Read input from file (- for stdin)")
64 fs.StringVar(&cfg.Input.InputFile, "input", "", "Read input from file (- for stdin)")
65 fs.StringVar(&cfg.Input.TypeText, "type", "", "Type string and exit")
66 fs.IntVar(&cfg.Input.KeyDelay, "delay", 0, "Delay between keystrokes in ms")
67
68 // Clipboard flags
69 fs.StringVar(&cfg.Clipboard.ClipInDir, "clip-in", "", "Watch directory for outgoing clipboard")
70 fs.StringVar(&cfg.Clipboard.ClipOutDir, "clip-out", "", "Save incoming clipboard to directory")
71 fs.StringVar(&cfg.Clipboard.ClipSend, "clip-send", "", "Send clipboard text and exit")
72
73 // Server flags
74 fs.StringVar(&cfg.Server.HTTPAddr, "http", "", "Start HTTP/WS server on address")
75 fs.StringVar(&cfg.Server.ProxyMode, "proxy-mode", "shared", "Client session mode (shared|isolated)")
76 fs.IntVar(&cfg.Server.MaxClients, "max-clients", 10, "Max concurrent WebSocket clients")
77 fs.StringVar(&cfg.Server.NoVNCPath, "novnc-path", "", "Path to noVNC static files")
78 fs.StringVar(&cfg.Server.APIPrefix, "api-prefix", "/api", "API route prefix")
79 fs.StringVar(&cfg.Server.CORSOrigin, "cors-origin", "*", "CORS allowed origin")
80
81 // Output flags
82 fs.IntVar(&cfg.Output.Verbose, "v", 0, "Verbose output (use multiple times for more)")
83 fs.BoolVar(&cfg.Output.Silent, "s", false, "Silent mode")
84 fs.BoolVar(&cfg.Output.Silent, "silent", false, "Silent mode")
85 fs.StringVar(&cfg.Output.OutputFile, "o", "", "Write output to file")
86 fs.StringVar(&cfg.Output.OutputFile, "output", "", "Write output to file")
87
88 // Help flags
89 var showHelp bool
90 fs.BoolVar(&showHelp, "h", false, "Show help")
91 fs.BoolVar(&showHelp, "help", false, "Show help")
92
93 var showVersion bool
94 fs.BoolVar(&showVersion, "version", false, "Show version")
95
96 // Parse flags
97 if err := fs.Parse(args); err != nil {
98 return nil, err
99 }
100
101 if showHelp {
102 fs.Usage()
103 os.Exit(0)
104 }
105
106 if showVersion {
107 fmt.Println("govnc version 1.0.0")
108 os.Exit(0)
109 }
110
111 // Process headers
112 for _, h := range headers {
113 parts := strings.SplitN(h, ":", 2)
114 if len(parts) == 2 {
115 cfg.Connection.Headers.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]))
116 }
117 }
118
119 // Process timeout (connectTimeout is in seconds, convert to time.Duration)
120 if connectTimeout > 0 {
121 cfg.Connection.ConnectTimeout = time.Duration(connectTimeout) * time.Second
122 }
123
124 // Process user:password
125 if cfg.VNC.User != "" {
126 parts := strings.SplitN(cfg.VNC.User, ":", 2)
127 if len(parts) == 2 {
128 cfg.VNC.Password = parts[1]
129 }
130 cfg.VNC.User = parts[0]
131 }
132
133 // Process encodings
134 if encodings != "" {
135 cfg.VNC.Encodings = parseEncodings(encodings)
136 }
137
138 // Get URL from remaining args
139 remaining := fs.Args()
140 if len(remaining) > 0 {
141 cfg.URL = remaining[0]
142 }
143
144 // Check for URL in environment
145 if cfg.URL == "" {
146 cfg.URL = os.Getenv("VNC_URL")
147 }
148
149 // Check for cookies in environment
150 if cfg.Connection.Cookie == "" {
151 cfg.Connection.Cookie = os.Getenv("VNC_COOKIES")
152 }
153
154 // Check for password in environment
155 if cfg.VNC.Password == "" {
156 cfg.VNC.Password = os.Getenv("VNC_PASSWORD")
157 }
158
159 return cfg, nil
160}
161
162// parseEncodings parses a comma-separated list of encoding names.
163func parseEncodings(s string) []int32 {
164 names := strings.Split(s, ",")
165 var encodings []int32
166 for _, name := range names {
167 name = strings.TrimSpace(strings.ToLower(name))
168 switch name {
169 case "raw":
170 encodings = append(encodings, 0)
171 case "copyrect":
172 encodings = append(encodings, 1)
173 case "rre":
174 encodings = append(encodings, 2)
175 case "hextile":
176 encodings = append(encodings, 5)
177 case "tight":
178 encodings = append(encodings, 7)
179 case "zrle":
180 encodings = append(encodings, 16)
181 }
182 }
183 return encodings
184}
185
186const usage = `govnc - VNC over WebSocket client and proxy
187
188USAGE:
189 govnc [OPTIONS] <URL>
190
191CONNECTION OPTIONS:
192 -H, --header <Header: Value> Add custom header (repeatable)
193 -b, --cookie <data|@file> Send cookies
194 -A, --user-agent <string> User-Agent header (default: govnc/1.0)
195 -x, --proxy <host:port> Use proxy (http, socks5)
196 --connect-timeout <seconds> Connection timeout (default: 30)
197 -k, --insecure Skip TLS verification
198
199AUTHENTICATION:
200 -u, --user <user:password> VNC authentication
201 --auth-none Force no authentication
202
203VNC OPTIONS:
204 --shared Request shared session (default: true)
205 --exclusive Request exclusive session
206 --encoding <list> Comma-separated encodings (tight,zrle,raw,...)
207
208INPUT OPTIONS:
209 -i, --input <file|-> Read input from file (- for stdin)
210 --type <string> Type string and exit
211 --delay <ms> Delay between keystrokes
212
213CLIPBOARD OPTIONS:
214 --clip-in <dir> Watch directory for outgoing clipboard
215 --clip-out <dir> Save incoming clipboard to directory
216 --clip-send <text|@file> Send clipboard text and exit
217
218SERVER MODE:
219 --http <addr:port> Start HTTP/WS server
220 --proxy-mode <shared|isolated> Client session mode (default: shared)
221 --max-clients <n> Max concurrent WebSocket clients
222 --novnc-path <path> Path to noVNC static files
223 --api-prefix <prefix> API route prefix (default: /api)
224 --cors-origin <origin> CORS allowed origin (default: *)
225
226OUTPUT OPTIONS:
227 -v Verbose output (use multiple times)
228 -s, --silent Silent mode
229 -o, --output <file> Write output to file
230
231OTHER:
232 -h, --help Show help
233 --version Show version
234
235ENVIRONMENT VARIABLES:
236 VNC_URL Default WebSocket URL
237 VNC_COOKIES Default cookies
238 VNC_PASSWORD VNC password
239
240EXAMPLES:
241 # Basic interactive connection
242 govnc wss://vnc.example.com/websockify
243
244 # With auth headers
245 govnc -b "session=abc" -H "X-Auth: token" wss://vnc.example.com/ws
246
247 # Type command and exit
248 govnc --type "ls -la\n" wss://vnc.example.com/ws
249
250 # Start proxy server with noVNC web UI
251 govnc --http :8080 wss://vnc.example.com/ws
252 # Then: http://localhost:8080/noVNC/vnc.html
253
254 # Use REST API
255 curl -X PUT localhost:8080/api/keys -d '{"text":"hello\n"}'
256 curl -X PUT localhost:8080/api/clipboard -d '{"text":"copied"}'
257 curl localhost:8080/api/session
258`