@@ -149,7 +149,11 @@ def quote(arg):
149149def log (msg ):
150150 t = time .time ()
151151 timestamp = time .strftime ("%Y-%m-%d %H:%M:%S" , time .localtime (t )) + ".{:03d}" .format (int (t % 1 * 1000 ))
152+ if hasattr (sys .stdout , "isatty" ) and sys .stdout .isatty ():
153+ # Clear current line (progress bar) and move cursor to beginning
154+ sys .stdout .write ("\r \x1b [K" )
152155 print ("[{}] {}" .format (timestamp , msg ))
156+ sys .stdout .flush ()
153157
154158def supports_ansi_color (stream = sys .stdout ):
155159 """Checks if the stream supports ANSI color sequences."""
@@ -2384,6 +2388,8 @@ def print_usage():
23842388 Use "--vnc off" to disable.
23852389 --remote-vnc Create a public URL for the VNC Web UI using Cloudflare, Localhost.run, or Pinggy.
23862390 Usage: --remote-vnc (auto), --remote-vnc cf, --remote-vnc lhr, --remote-vnc pinggy.
2391+ Enabled by default if no local browser is detected (e.g., in Cloud Shell).
2392+ Use "--remote-vnc no" to disable.
23872393 --vga <type> VGA device type (e.g., virtio, std, virtio-gpu). Default: virtio (std for NetBSD).
23882394 --res, --resolution Set initial screen resolution (e.g., 1280x800). Default: 1280x800.
23892395 --mon <port> QEMU monitor telnet port (localhost).
@@ -3373,6 +3379,39 @@ def tail_serial_log(path, stop_event):
33733379 pass
33743380
33753381
3382+ def watch_vnc_tunnel_log (log_path , stop_event , is_default_notice = False ):
3383+ """Monitor VNC proxy log and print tunnel URL as soon as it appears."""
3384+ if not log_path :
3385+ return
3386+ start_wait = time .time ()
3387+ while not os .path .exists (log_path ):
3388+ if stop_event .is_set () or (time .time () - start_wait > 30 ):
3389+ return
3390+ time .sleep (0.5 )
3391+
3392+ try :
3393+ with open (log_path , 'r' ) as f :
3394+ while not stop_event .is_set ():
3395+ line = f .readline ()
3396+ if not line :
3397+ time .sleep (0.5 )
3398+ continue
3399+ match = re .search (r"Open this link to access WebVNC \(via ([^)]+)\): (https?://[^\s]+)" , line )
3400+ if match :
3401+ service = match .group (1 )
3402+ url = match .group (2 )
3403+ display_url = url
3404+ if supports_ansi_color ():
3405+ display_url = "\x1b [32m{}\x1b [0m" .format (url )
3406+ log ("Open this link to access WebVNC (via {}): {}" .format (service , display_url ))
3407+ if is_default_notice :
3408+ log ("Notice: Remote VNC tunnel is enabled by default as no local browser was detected." )
3409+ log (" Use '--remote-vnc off' to disable it." )
3410+ return
3411+ except Exception :
3412+ pass
3413+
3414+
33763415def detect_host_ssh_port (sshd_config_path = "/etc/ssh/sshd_config" ):
33773416 try :
33783417 with open (sshd_config_path , 'r' ) as f :
@@ -3460,7 +3499,8 @@ def main():
34603499 'public_vnc' : False ,
34613500 'public_ssh' : False ,
34623501 'accept_vm_ssh' : False ,
3463- 'remote_vnc' : False
3502+ 'remote_vnc' : None ,
3503+ 'remote_vnc_is_default' : False
34643504 }
34653505
34663506 ssh_passthrough = []
@@ -3477,6 +3517,8 @@ def main():
34773517 working_dir = "/tmp/anyvm.org"
34783518 if not os .path .exists (working_dir ):
34793519 os .makedirs (working_dir )
3520+ config ['remote_vnc' ] = True
3521+ config ['remote_vnc_is_default' ] = True
34803522
34813523 # Manual argument parsing
34823524 args = sys .argv [1 :]
@@ -3575,7 +3617,11 @@ def main():
35753617 config ['public_ssh' ] = True
35763618 elif arg == "--remote-vnc" :
35773619 if i + 1 < len (args ) and not args [i + 1 ].startswith ("-" ):
3578- config ['remote_vnc' ] = args [i + 1 ]
3620+ val = args [i + 1 ]
3621+ if val .lower () in ["no" , "off" , "false" , "0" ]:
3622+ config ['remote_vnc' ] = False
3623+ else :
3624+ config ['remote_vnc' ] = val
35793625 i += 1
35803626 else :
35813627 config ['remote_vnc' ] = True
@@ -3608,11 +3654,21 @@ def main():
36083654 i += 1
36093655 else :
36103656 config ['synctime' ] = True
3657+ else :
3658+ log ("Warning: Unrecognized argument: {}" .format (arg ))
36113659 i += 1
36123660
36133661 if config ['debug' ]:
36143662 debuglog (True , "Debug logging enabled" )
36153663
3664+ # If remote VNC not explicitly specified, enable it by default if browser is unavailable
3665+ if config ['remote_vnc' ] is None :
3666+ if config ['vnc' ].lower () != "off" and not is_browser_available ():
3667+ config ['remote_vnc' ] = True
3668+ config ['remote_vnc_is_default' ] = True
3669+ else :
3670+ config ['remote_vnc' ] = False
3671+
36163672 is_vnc_console = (config .get ('vnc' ) == "console" )
36173673
36183674 if not config ['os' ]:
@@ -4539,6 +4595,8 @@ def cmd_exists(cmd):
45394595 cmd_text = format_command_for_display (cmd_list )
45404596 debuglog (config ['debug' ], "CMD:\n " + cmd_text )
45414597
4598+ vnc_log_path = os .path .join (output_dir , "{}.vncproxy.log" .format (vm_name ))
4599+
45424600 # Function to start (or restart) the VNC Web Proxy monitoring the given QEMU PID
45434601 def start_vnc_proxy_for_pid (qemu_pid ):
45444602 if config ['vnc' ] != "off" and web_port :
@@ -4553,7 +4611,7 @@ def start_vnc_proxy_for_pid(qemu_pid):
45534611 str (qemu_pid ),
45544612 '1' if is_audio_enabled else '0' ,
45554613 config ['qmon' ] if config ['qmon' ] else "" ,
4556- os . path . join ( output_dir , "{}.vncproxy.log" . format ( vm_name )) ,
4614+ vnc_log_path ,
45574615 '1' if is_vnc_console else '0' ,
45584616 '0.0.0.0' if (config ['public' ] or config ['public_vnc' ]) else '127.0.0.1' ,
45594617 str (config ['remote_vnc' ]) if config ['remote_vnc' ] else '0' ,
@@ -4574,12 +4632,30 @@ def start_vnc_proxy_for_pid(qemu_pid):
45744632 if supports_ansi_color ():
45754633 display_local_url = "\x1b [32m{}\x1b [0m" .format (local_url )
45764634 log ("VNC Web UI available at {}" .format (display_local_url ))
4635+
4636+ # Start tunnel watcher thread if remote VNC is enabled
4637+ if config .get ('remote_vnc' ):
4638+ t = threading .Thread (target = watch_vnc_tunnel_log , args = (vnc_log_path , tunnel_wait_stop , config .get ('remote_vnc_is_default' )))
4639+ t .daemon = True
4640+ t .start ()
45774641 return p
45784642 except Exception as e :
45794643 debuglog (config ['debug' ], "Failed to start VNC proxy process: {}" .format (e ))
45804644 return None
45814645
45824646 proxy_proc = None
4647+ tunnel_wait_stop = threading .Event ()
4648+
4649+ # Pre-startup cleanup of VNC tunnel information
4650+ try :
4651+ if os .path .exists (vnc_log_path ):
4652+ os .remove (vnc_log_path )
4653+ remote_file = vnc_log_path .replace (".vncproxy.log" , ".remote" )
4654+ if os .path .exists (remote_file ):
4655+ os .remove (remote_file )
4656+ except :
4657+ pass
4658+
45834659 if config ['console' ]:
45844660 proc = subprocess .Popen (cmd_list )
45854661 proxy_proc = start_vnc_proxy_for_pid (proc .pid )
@@ -4951,6 +5027,7 @@ def finish_wait_timer():
49515027
49525028
49535029 wait_timer_stop .set ()
5030+ tunnel_wait_stop .set ()
49545031 if wait_timer_thread :
49555032 wait_timer_thread .join (0.2 )
49565033 finish_wait_timer ()
@@ -4972,7 +5049,7 @@ def finish_wait_timer():
49725049 display_url = tunnel_url
49735050 if supports_ansi_color ():
49745051 display_url = "\x1b [32m{}\x1b [0m" .format (tunnel_url )
4975- log ( "Open this link to access WebVNC (via {}): {}" . format ( tunnel_service , display_url ))
5052+ # Redundant log removed, already handled by watch_vnc_tunnel_log
49765053 else :
49775054 # Check for errors
49785055 err_match = re .search (r"(?:Cloudflare )?Tunnel Error: (.*)" , log_text )
@@ -5165,6 +5242,9 @@ def finish_wait_timer():
51655242 if supports_ansi_color ():
51665243 display_url = "\x1b [32m{}\x1b [0m" .format (tunnel_url )
51675244 log ("WebVNC ({}): {}" .format (tunnel_service , display_url ))
5245+ if config .get ('remote_vnc_is_default' ):
5246+ log ("Notice: Remote VNC tunnel is enabled by default as no local browser was detected." )
5247+ log (" Use '--remote-vnc off' to disable it." )
51685248 log ("======================================" )
51695249
51705250 if not config ['detach' ]:
@@ -5194,6 +5274,9 @@ def finish_wait_timer():
51945274 if supports_ansi_color ():
51955275 display_url = "\x1b [32m{}\x1b [0m" .format (tunnel_url )
51965276 log ("WebVNC ({}): {}" .format (tunnel_service , display_url ))
5277+ if config .get ('remote_vnc_is_default' ):
5278+ log ("Notice: Remote VNC tunnel is enabled by default as no local browser was detected." )
5279+ log (" Use '--remote-vnc off' to disable it." )
51975280 log ("======================================" )
51985281 else :
51995282 log ("VM has exited" )
0 commit comments