@@ -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."""
@@ -3375,6 +3379,39 @@ def tail_serial_log(path, stop_event):
33753379 pass
33763380
33773381
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+
33783415def detect_host_ssh_port (sshd_config_path = "/etc/ssh/sshd_config" ):
33793416 try :
33803417 with open (sshd_config_path , 'r' ) as f :
@@ -4558,6 +4595,8 @@ def cmd_exists(cmd):
45584595 cmd_text = format_command_for_display (cmd_list )
45594596 debuglog (config ['debug' ], "CMD:\n " + cmd_text )
45604597
4598+ vnc_log_path = os .path .join (output_dir , "{}.vncproxy.log" .format (vm_name ))
4599+
45614600 # Function to start (or restart) the VNC Web Proxy monitoring the given QEMU PID
45624601 def start_vnc_proxy_for_pid (qemu_pid ):
45634602 if config ['vnc' ] != "off" and web_port :
@@ -4572,7 +4611,7 @@ def start_vnc_proxy_for_pid(qemu_pid):
45724611 str (qemu_pid ),
45734612 '1' if is_audio_enabled else '0' ,
45744613 config ['qmon' ] if config ['qmon' ] else "" ,
4575- os . path . join ( output_dir , "{}.vncproxy.log" . format ( vm_name )) ,
4614+ vnc_log_path ,
45764615 '1' if is_vnc_console else '0' ,
45774616 '0.0.0.0' if (config ['public' ] or config ['public_vnc' ]) else '127.0.0.1' ,
45784617 str (config ['remote_vnc' ]) if config ['remote_vnc' ] else '0' ,
@@ -4593,12 +4632,30 @@ def start_vnc_proxy_for_pid(qemu_pid):
45934632 if supports_ansi_color ():
45944633 display_local_url = "\x1b [32m{}\x1b [0m" .format (local_url )
45954634 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 ()
45964641 return p
45974642 except Exception as e :
45984643 debuglog (config ['debug' ], "Failed to start VNC proxy process: {}" .format (e ))
45994644 return None
46004645
46014646 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+
46024659 if config ['console' ]:
46034660 proc = subprocess .Popen (cmd_list )
46044661 proxy_proc = start_vnc_proxy_for_pid (proc .pid )
@@ -4970,6 +5027,7 @@ def finish_wait_timer():
49705027
49715028
49725029 wait_timer_stop .set ()
5030+ tunnel_wait_stop .set ()
49735031 if wait_timer_thread :
49745032 wait_timer_thread .join (0.2 )
49755033 finish_wait_timer ()
@@ -4991,10 +5049,7 @@ def finish_wait_timer():
49915049 display_url = tunnel_url
49925050 if supports_ansi_color ():
49935051 display_url = "\x1b [32m{}\x1b [0m" .format (tunnel_url )
4994- log ("Open this link to access WebVNC (via {}): {}" .format (tunnel_service , display_url ))
4995- if config .get ('remote_vnc_is_default' ):
4996- log ("Notice: Remote VNC tunnel is enabled by default as no local browser was detected." )
4997- log (" Use '--remote-vnc off' to disable it." )
5052+ # Redundant log removed, already handled by watch_vnc_tunnel_log
49985053 else :
49995054 # Check for errors
50005055 err_match = re .search (r"(?:Cloudflare )?Tunnel Error: (.*)" , log_text )
0 commit comments