@@ -4,6 +4,7 @@ use v5.38;
44
55use TorrustDeploy::App -command;
66use TorrustDeploy::Provision::OpenTofu;
7+ use TorrustDeploy::Infrastructure::SSH::Connection;
78use Path::Tiny qw( path) ;
89use File::Spec;
910use Time::HiRes qw( sleep) ;
@@ -54,14 +55,17 @@ sub execute {
5455 # Get VM IP address
5556 my $vm_ip = $tofu -> get_vm_ip($tofu_dir );
5657
58+ # Create SSH connection
59+ my $ssh_connection = TorrustDeploy::Infrastructure::SSH::Connection-> new(host => $vm_ip );
60+
5761 # Wait for cloud-init completion
58- $self -> _wait_for_cloud_init($vm_ip );
62+ $self -> _wait_for_cloud_init($ssh_connection );
5963
6064 # Verify SSH key authentication after cloud-init completes
61- $self -> _verify_ssh_key_auth($vm_ip );
65+ $self -> _verify_ssh_key_auth($ssh_connection );
6266
6367 # Show final summary
64- $self -> _show_final_summary($vm_ip );
68+ $self -> _show_final_summary($ssh_connection );
6569}
6670
6771sub _copy_templates {
@@ -100,7 +104,7 @@ sub _copy_templates {
100104}
101105
102106sub _wait_for_cloud_init {
103- my ($self , $vm_ip ) = @_ ;
107+ my ($self , $ssh_connection ) = @_ ;
104108
105109 say " Waiting for cloud-init to complete..." ;
106110 say " This may take several minutes while packages are installed and configured." ;
@@ -117,10 +121,9 @@ sub _wait_for_cloud_init {
117121 while ($attempt < $max_attempts && !$ssh_connected ) {
118122 $attempt ++;
119123
120- my $ssh_test = system (" timeout 5 sshpass -p 'torrust123' ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust\@ $vm_ip 'echo \" SSH connected\" ' >/dev/null 2>&1" );
121- if ($ssh_test == 0) {
124+ if ($ssh_connection -> test_password_connection()) {
122125 $ssh_connected = 1;
123- say " ✅ SSH password connection established to $vm_ip " ;
126+ say " ✅ SSH password connection established to " . $ssh_connection -> host ;
124127 } else {
125128 if ($attempt % 6 == 0) { # Every 30 seconds
126129 say " [Waiting for SSH connection... ${attempt} 0s elapsed]" ;
@@ -130,8 +133,8 @@ sub _wait_for_cloud_init {
130133 }
131134
132135 if (!$ssh_connected ) {
133- say " ❌ Failed to establish SSH connection to $vm_ip after " . ($max_attempts * 5 / 60) . " minutes" ;
134- $self -> _print_cloud_init_logs($vm_ip );
136+ say " ❌ Failed to establish SSH connection to " . $ssh_connection -> host . " after " . ($max_attempts * 5 / 60) . " minutes" ;
137+ $self -> _print_cloud_init_logs($ssh_connection );
135138 die " SSH connection failed" ;
136139 }
137140
@@ -142,16 +145,16 @@ sub _wait_for_cloud_init {
142145 while ($attempt < $max_attempts ) {
143146 $attempt ++;
144147
145- my $check_result = system ( " timeout 10 sshpass -p 'torrust123' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust \@ $vm_ip ' test -f $completion_file ' >/dev/null 2>&1 " );
148+ my $result = $ssh_connection -> execute_command( " test -f $completion_file " );
146149
147- if ($check_result == 0 ) {
150+ if ($result -> { success } ) {
148151 say " ✅ Cloud-init setup completed successfully!" ;
149152
150153 # Show completion message
151- my $completion_content = ` timeout 10 sshpass -p 'torrust123' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust \@ $vm_ip ' cat $completion_file ' 2>/dev/null ` ;
152- if ($completion_content ) {
153- chomp $completion_content ;
154- say " 📅 Completion marker: $completion_content " ;
154+ my $completion_result = $ssh_connection -> execute_command( " cat $completion_file " ) ;
155+ if ($completion_result -> { success } && $completion_result -> { output } ) {
156+ chomp $completion_result -> { output } ;
157+ say " 📅 Completion marker: " . $completion_result -> { output } ;
155158 }
156159 $cloud_init_success = 1;
157160 last ;
@@ -167,70 +170,91 @@ sub _wait_for_cloud_init {
167170 }
168171
169172 if (!$cloud_init_success ) {
170- say " ❌ Timeout waiting for cloud-init to complete on $vm_ip after " . ($max_attempts * 5 / 60) . " minutes" ;
171- $self -> _print_cloud_init_logs($vm_ip );
173+ say " ❌ Timeout waiting for cloud-init to complete on " . $ssh_connection -> host . " after " . ($max_attempts * 5 / 60) . " minutes" ;
174+ $self -> _print_cloud_init_logs($ssh_connection );
172175 die " Cloud-init timeout" ;
173176 }
174177}
175178
176179sub _show_final_summary {
177- my ($self , $vm_ip ) = @_ ;
180+ my ($self , $ssh_connection ) = @_ ;
178181
179182 say " 📦 Final system summary:" ;
180183
181- my $docker_version = ` timeout 10 sshpass -p 'torrust123' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust\@ $vm_ip 'docker --version 2>/dev/null || echo "Docker not available"' 2>/dev/null` ;
184+ my $docker_result = $ssh_connection -> execute_command(' docker --version' );
185+ my $docker_version = $docker_result -> {success } ? $docker_result -> {output } : " Docker not available" ;
182186 chomp $docker_version if $docker_version ;
183187 say " Docker: $docker_version " if $docker_version ;
184188
185- my $ufw_status = ` timeout 10 sshpass -p 'torrust123' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust\@ $vm_ip 'ufw status 2>/dev/null | head -1 || echo "UFW not available"' 2>/dev/null` ;
189+ my $ufw_result = $ssh_connection -> execute_command(' ufw status | head -1' );
190+ my $ufw_status = $ufw_result -> {success } ? $ufw_result -> {output } : " UFW not available" ;
186191 chomp $ufw_status if $ufw_status ;
187192 say " Firewall: $ufw_status " if $ufw_status ;
188193
189194 say " Provisioning completed successfully!" ;
190- say " VM is ready at IP: $vm_ip " ;
195+ say " VM is ready at IP: " . $ssh_connection -> host ;
191196}
192197
193198sub _print_cloud_init_logs {
194- my ($self , $vm_ip ) = @_ ;
199+ my ($self , $ssh_connection ) = @_ ;
195200
196201 say " 📄 Cloud-init logs (for debugging):" ;
197202
198203 # Print cloud-init-output.log
199204 say " === /var/log/cloud-init-output.log ===" ;
200- my $output_log = ` timeout 30 sshpass -p 'torrust123' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust \@ $vm_ip 'sudo cat /var/log/cloud-init-output.log 2>/dev/null || echo "Log file not available"' 2>/dev/null ` ;
201- if ($output_log && $output_log !~ / ^Log file not available / ) {
202- print $output_log ;
205+ my $output_result = $ssh_connection -> execute_command_with_sudo( ' cat /var/log/cloud-init-output.log' ) ;
206+ if ($output_result -> { success } ) {
207+ print $output_result -> { output } ;
203208 } else {
204209 say " Cloud-init output log not available" ;
205210 }
206211
207212 say " === /var/log/cloud-init.log ===" ;
208- my $main_log = ` timeout 30 sshpass -p 'torrust123' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null torrust \@ $vm_ip 'sudo cat /var/log/cloud-init.log 2>/dev/null || echo "Log file not available"' 2>/dev/null ` ;
209- if ($main_log && $main_log !~ / ^Log file not available / ) {
210- print $main_log ;
213+ my $main_result = $ssh_connection -> execute_command_with_sudo( ' cat /var/log/cloud-init.log' ) ;
214+ if ($main_result -> { success } ) {
215+ print $main_result -> { output } ;
211216 } else {
212217 say " Cloud-init main log not available" ;
213218 }
214219}
215220
216221sub _verify_ssh_key_auth {
217- my ($self , $vm_ip ) = @_ ;
222+ my ($self , $ssh_connection ) = @_ ;
218223
219224 say " 🔑 Checking SSH key authentication..." ;
220225
221- my $ssh_key_path = " $ENV {HOME}/.ssh/testing_rsa" ;
222-
223- # Test SSH key authentication
224- my $result = system (" timeout 10 ssh -i '$ssh_key_path ' -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no torrust\@ $vm_ip 'echo \" SSH key authentication successful\" ' >/dev/null 2>&1" );
226+ # SSH authentication might need time to fully stabilize after cloud-init reboot
227+ # Try with progressive delays: immediate, 5s, 10s, 15s
228+ my @retry_delays = (0, 5, 10, 15);
225229
226- if ($result == 0) {
227- say " ✅ SSH key authentication is working correctly!" ;
228- say " You can now connect using: ssh -i ~/.ssh/testing_rsa torrust\@ $vm_ip " ;
229- } else {
230- say " ❌ SSH key authentication failed" ;
231- $self -> _print_cloud_init_logs($vm_ip );
232- die " SSH key authentication failed" ;
230+ for my $attempt (0..$#retry_delays ) {
231+ if ($attempt > 0) {
232+ my $delay = $retry_delays [$attempt ];
233+ say " ⏳ Waiting ${delay} s before retry attempt " . ($attempt + 1) . " ..." ;
234+ sleep $delay ;
235+ }
236+
237+ # Create a fresh SSH connection for key authentication test
238+ # This ensures we don't have any state issues from cloud-init monitoring
239+ my $fresh_ssh = TorrustDeploy::Infrastructure::SSH::Connection-> new(
240+ host => $ssh_connection -> host
241+ );
242+
243+ if ($fresh_ssh -> test_key_connection()) {
244+ say " ✅ SSH key authentication is working correctly!" ;
245+ say " You can now connect using: ssh -i " . $fresh_ssh -> ssh_key_path . " " . $fresh_ssh -> username . " @" . $fresh_ssh -> host;
246+ return ;
247+ }
248+
249+ if ($attempt < $#retry_delays ) {
250+ say " ⚠️ SSH key authentication failed, will retry..." ;
251+ }
233252 }
253+
254+ # All retries failed
255+ say " ❌ SSH key authentication failed after all retries" ;
256+ $self -> _print_cloud_init_logs($ssh_connection );
257+ die " SSH key authentication failed" ;
234258}
235259
2362601;
0 commit comments