@@ -4,6 +4,7 @@ use v5.38;
4
4
5
5
use TorrustDeploy::App -command;
6
6
use TorrustDeploy::Provision::OpenTofu;
7
+ use TorrustDeploy::Infrastructure::SSH::Connection;
7
8
use Path::Tiny qw( path) ;
8
9
use File::Spec;
9
10
use Time::HiRes qw( sleep) ;
@@ -54,14 +55,17 @@ sub execute {
54
55
# Get VM IP address
55
56
my $vm_ip = $tofu -> get_vm_ip($tofu_dir );
56
57
58
+ # Create SSH connection
59
+ my $ssh_connection = TorrustDeploy::Infrastructure::SSH::Connection-> new(host => $vm_ip );
60
+
57
61
# Wait for cloud-init completion
58
- $self -> _wait_for_cloud_init($vm_ip );
62
+ $self -> _wait_for_cloud_init($ssh_connection );
59
63
60
64
# Verify SSH key authentication after cloud-init completes
61
- $self -> _verify_ssh_key_auth($vm_ip );
65
+ $self -> _verify_ssh_key_auth($ssh_connection );
62
66
63
67
# Show final summary
64
- $self -> _show_final_summary($vm_ip );
68
+ $self -> _show_final_summary($ssh_connection );
65
69
}
66
70
67
71
sub _copy_templates {
@@ -100,7 +104,7 @@ sub _copy_templates {
100
104
}
101
105
102
106
sub _wait_for_cloud_init {
103
- my ($self , $vm_ip ) = @_ ;
107
+ my ($self , $ssh_connection ) = @_ ;
104
108
105
109
say " Waiting for cloud-init to complete..." ;
106
110
say " This may take several minutes while packages are installed and configured." ;
@@ -117,10 +121,9 @@ sub _wait_for_cloud_init {
117
121
while ($attempt < $max_attempts && !$ssh_connected ) {
118
122
$attempt ++;
119
123
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()) {
122
125
$ssh_connected = 1;
123
- say " ✅ SSH password connection established to $vm_ip " ;
126
+ say " ✅ SSH password connection established to " . $ssh_connection -> host ;
124
127
} else {
125
128
if ($attempt % 6 == 0) { # Every 30 seconds
126
129
say " [Waiting for SSH connection... ${attempt} 0s elapsed]" ;
@@ -130,8 +133,8 @@ sub _wait_for_cloud_init {
130
133
}
131
134
132
135
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 );
135
138
die " SSH connection failed" ;
136
139
}
137
140
@@ -142,16 +145,16 @@ sub _wait_for_cloud_init {
142
145
while ($attempt < $max_attempts ) {
143
146
$attempt ++;
144
147
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 " );
146
149
147
- if ($check_result == 0 ) {
150
+ if ($result -> { success } ) {
148
151
say " ✅ Cloud-init setup completed successfully!" ;
149
152
150
153
# 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 } ;
155
158
}
156
159
$cloud_init_success = 1;
157
160
last ;
@@ -167,70 +170,91 @@ sub _wait_for_cloud_init {
167
170
}
168
171
169
172
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 );
172
175
die " Cloud-init timeout" ;
173
176
}
174
177
}
175
178
176
179
sub _show_final_summary {
177
- my ($self , $vm_ip ) = @_ ;
180
+ my ($self , $ssh_connection ) = @_ ;
178
181
179
182
say " 📦 Final system summary:" ;
180
183
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" ;
182
186
chomp $docker_version if $docker_version ;
183
187
say " Docker: $docker_version " if $docker_version ;
184
188
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" ;
186
191
chomp $ufw_status if $ufw_status ;
187
192
say " Firewall: $ufw_status " if $ufw_status ;
188
193
189
194
say " Provisioning completed successfully!" ;
190
- say " VM is ready at IP: $vm_ip " ;
195
+ say " VM is ready at IP: " . $ssh_connection -> host ;
191
196
}
192
197
193
198
sub _print_cloud_init_logs {
194
- my ($self , $vm_ip ) = @_ ;
199
+ my ($self , $ssh_connection ) = @_ ;
195
200
196
201
say " 📄 Cloud-init logs (for debugging):" ;
197
202
198
203
# Print cloud-init-output.log
199
204
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 } ;
203
208
} else {
204
209
say " Cloud-init output log not available" ;
205
210
}
206
211
207
212
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 } ;
211
216
} else {
212
217
say " Cloud-init main log not available" ;
213
218
}
214
219
}
215
220
216
221
sub _verify_ssh_key_auth {
217
- my ($self , $vm_ip ) = @_ ;
222
+ my ($self , $ssh_connection ) = @_ ;
218
223
219
224
say " 🔑 Checking SSH key authentication..." ;
220
225
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);
225
229
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
+ }
233
252
}
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" ;
234
258
}
235
259
236
260
1;
0 commit comments