@@ -19,6 +19,9 @@ use crate::libvirt::domain::VirtiofsFilesystem;
1919use crate :: utils:: parse_memory_to_mb;
2020use crate :: xml_utils;
2121
22+ /// SSH wait timeout in seconds
23+ const SSH_WAIT_TIMEOUT_SECONDS : u64 = 180 ;
24+
2225/// Create a virsh command with optional connection URI
2326pub ( super ) fn virsh_command ( connect_uri : Option < & str > ) -> Result < std:: process:: Command > {
2427 let mut cmd = std:: process:: Command :: new ( "virsh" ) ;
@@ -253,6 +256,10 @@ pub struct LibvirtRunOpts {
253256 #[ clap( long) ]
254257 pub ssh : bool ,
255258
259+ /// Wait for SSH to become available and verify connectivity (for testing)
260+ #[ clap( long, conflicts_with = "ssh" ) ]
261+ pub ssh_wait : bool ,
262+
256263 /// Mount host container storage (RO) at /run/host-container-storage
257264 #[ clap( long = "bind-storage-ro" ) ]
258265 pub bind_storage_ro : bool ,
@@ -319,6 +326,60 @@ impl LibvirtRunOpts {
319326 }
320327}
321328
329+ /// Wait for SSH to become available on a libvirt domain
330+ ///
331+ /// Polls SSH connectivity by attempting simple commands until successful or timeout.
332+ fn wait_for_ssh_ready (
333+ global_opts : & crate :: libvirt:: LibvirtOptions ,
334+ domain_name : & str ,
335+ timeout_secs : u64 ,
336+ ) -> Result < ( ) > {
337+ use std:: time:: Duration ;
338+
339+ debug ! (
340+ "Waiting for SSH to become available on domain '{}' (timeout: {}s)" ,
341+ domain_name, timeout_secs
342+ ) ;
343+
344+ // Create progress bar
345+ let pb = crate :: boot_progress:: create_boot_progress_bar ( ) ;
346+ pb. set_message ( "Waiting for SSH to become available..." ) ;
347+
348+ // Clone values for closure
349+ let global_opts_clone = global_opts. clone ( ) ;
350+ let domain_name_clone = domain_name. to_string ( ) ;
351+
352+ // Use shared polling function with libvirt-specific test
353+ let ( _elapsed, pb) = crate :: utils:: wait_for_readiness (
354+ pb,
355+ "Waiting for SSH" ,
356+ || {
357+ // Create a test SSH connection with short timeout
358+ let ssh_opts = crate :: libvirt:: ssh:: LibvirtSshOpts {
359+ domain_name : domain_name_clone. clone ( ) ,
360+ user : "root" . to_string ( ) ,
361+ command : vec ! [ "true" . to_string( ) ] , // Simple command to test connectivity
362+ strict_host_keys : false ,
363+ timeout : 5 , // Short timeout for each attempt
364+ log_level : "ERROR" . to_string ( ) ,
365+ extra_options : vec ! [ ] ,
366+ suppress_output : true , // Suppress error messages during connectivity testing
367+ } ;
368+
369+ // Try to connect
370+ match crate :: libvirt:: ssh:: run_ssh_impl ( & global_opts_clone, ssh_opts) {
371+ Ok ( _) => Ok ( true ) ,
372+ Err ( _) => Ok ( false ) ,
373+ }
374+ } ,
375+ Duration :: from_secs ( timeout_secs) ,
376+ Duration :: from_secs ( 2 ) , // Poll every 2 seconds
377+ ) ?;
378+
379+ pb. finish_and_clear ( ) ;
380+ Ok ( ( ) )
381+ }
382+
322383/// Execute the libvirt run command
323384pub fn run ( global_opts : & crate :: libvirt:: LibvirtOptions , opts : LibvirtRunOpts ) -> Result < ( ) > {
324385 use crate :: images;
@@ -445,12 +506,21 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtRunOpts) -
445506 }
446507 }
447508
448- if opts. ssh {
509+ if opts. ssh_wait {
510+ // Wait for SSH to be ready and verify connectivity
511+ wait_for_ssh_ready ( global_opts, & vm_name, SSH_WAIT_TIMEOUT_SECONDS ) ?;
512+ println ! ( "Ready; use bcvk libvirt ssh to connect" ) ;
513+ Ok ( ( ) )
514+ } else if opts. ssh {
515+ // Wait for SSH then enter interactive shell
516+ wait_for_ssh_ready ( global_opts, & vm_name, SSH_WAIT_TIMEOUT_SECONDS ) ?;
517+
449518 // Use the libvirt SSH functionality directly
450519 let ssh_opts = crate :: libvirt:: ssh:: LibvirtSshOpts {
451520 domain_name : vm_name,
452521 user : "root" . to_string ( ) ,
453522 command : vec ! [ ] ,
523+ suppress_output : false ,
454524 strict_host_keys : false ,
455525 timeout : 30 ,
456526 log_level : "ERROR" . to_string ( ) ,
0 commit comments