@@ -26,7 +26,7 @@ use crate::{
2626};
2727
2828const SSH_KEY_NAME: &str = "ssh_key";
29−pub(crate) const SERIAL_LOG_NAME: &str = "serial.log";
29+pub(crate) const VM_ROOT_LOG_NAME: &str = "vm_root.log";
3030pub(crate) const DEFAULT_SSH_USER: &str = "vibecoder";
3131const SSH_CONNECT_RETRIES: usize = 30;
3232const SSH_CONNECT_DELAY_MS: u64 = 500;
@@ -118,7 +118,7 @@ pub(crate) fn ensure_ssh_keypair(
118118 "vibebox",
119119 ])
120120 .stdin(Stdio::null())
121− .stdout(Stdio::null())
121+ .stdout(Stdio::inherit())
122122 .stderr(Stdio::inherit())
123123 .status()?;
124124
@@ -246,6 +246,8 @@ fn wait_for_vm_ipv4(
246246 timeout: Duration,
247247) -> Result<(), Box<dyn std::error::Error>> {
248248 let start = Instant::now();
249+ let mut next_log_at = start + Duration::from_secs(10);
250+ tracing::debug!("waiting for vm ipv4");
249251 loop {
250252 let config = load_or_create_instance_config(instance_dir)?;
251253 if config.vm_ipv4.is_some() {
@@ -254,8 +256,10 @@ fn wait_for_vm_ipv4(
254256 if start.elapsed() > timeout {
255257 return Err("Timed out waiting for VM IPv4".into());
256258 }
257− if start.elapsed().as_secs() % 5 == 0 {
258− tracing::debug!("still waiting for vm ipv4");
259+ if Instant::now() >= next_log_at {
260+ let waited = start.elapsed();
261+ tracing::debug!("still waiting for vm ipv4, {}s elapsed", waited.as_secs(),);
262+ next_log_at += Duration::from_secs(10);
259263 }
260264 thread::sleep(Duration::from_millis(200));
261265 }
@@ -270,9 +274,13 @@ fn run_ssh_session(
270274 loop {
271275 attempts += 1;
272276 if !ssh_port_open(&ip) {
273− tracing::debug!(attempts, "ssh port not open yet");
274− eprintln!(
275− "[vibebox] waiting for ssh port on {ip} (attempt {attempts}/{SSH_CONNECT_RETRIES})"
277+ tracing::debug!(attempts, "ssh port doesn't open yet");
278+ tracing::info!(
279+ attempts,
280+ ip = %ip,
281+ "waiting for ssh port ({}/{})",
282+ attempts,
283+ SSH_CONNECT_RETRIES
276284 );
277285 if attempts >= SSH_CONNECT_RETRIES {
278286 return Err(
@@ -283,10 +291,14 @@ fn run_ssh_session(
283291 continue;
284292 }
285293
286− eprintln!(
287− "[vibebox] starting ssh to {ssh_user}@{ip} (attempt {attempts}/{SSH_CONNECT_RETRIES})"
294+ tracing::info!(
295+ attempts,
296+ user = %ssh_user,
297+ ip = %ip,
298+ "starting ssh ({}/{})",
299+ attempts,
300+ SSH_CONNECT_RETRIES
288301 );
289− tracing::info!(attempts, user = %ssh_user, ip = %ip, "starting ssh");
290302 let status = Command::new("ssh")
291303 .args([
292304 "-i",
@@ -319,21 +331,22 @@ fn run_ssh_session(
319331
320332 match status {
321333 Ok(status) if status.success() => {
322− eprintln!("[vibebox] ssh exited with status: {status}");
334+ tracing::info!(status = %status, "ssh exited");
323335 break;
324336 }
325337 Ok(status) if status.code() == Some(255) => {
326− eprintln!("[vibebox] ssh connection failed: {status}");
338+ tracing::warn!(status = %status, "ssh connection failed");
327339 if attempts >= SSH_CONNECT_RETRIES {
328340 return Err(format!("ssh failed after {SSH_CONNECT_RETRIES} attempts").into());
329341 }
330342 thread::sleep(Duration::from_millis(500));
331343 }
332344 Ok(status) => {
333− eprintln!("[vibebox] ssh exited with status: {status}");
345+ tracing::info!(status = %status, "ssh exited");
334346 break;
335347 }
336348 Err(err) => {
349+ tracing::error!(error = %err, "failed to start ssh");
337350 return Err(format!("failed to start ssh: {err}").into());
338351 }
339352 }
@@ -404,7 +417,7 @@ fn spawn_ssh_io(
404417) -> vm::IoContext {
405418 let io_control = vm::IoControl::new();
406419
407− let log_path = instance_dir.join(SERIAL_LOG_NAME);
420+ let log_path = instance_dir.join(VM_ROOT_LOG_NAME);
408421 let log_file = fs::OpenOptions::new()
409422 .create(true)
410423 .write(true)
@@ -458,7 +471,7 @@ fn spawn_ssh_io(
458471 if cfg.vm_ipv4.as_deref() != Some(ip.as_str()) {
459472 cfg.vm_ipv4 = Some(ip.clone());
460473 let _ = write_instance_config(&instance_path, &cfg);
461− eprintln!("[vibebox] detected vm IPv4: {ip}");
474+ tracing::info!(ip = %ip, "detected vm ipv4");
462475 }
463476 }
464477 }
@@ -466,7 +479,7 @@ fn spawn_ssh_io(
466479
467480 if cleaned.contains("VIBEBOX_SSH_READY") {
468481 ssh_ready_for_output.store(true, Ordering::SeqCst);
469− eprintln!("[vibebox] sshd ready");
482+ tracing::info!("sshd ready");
470483 }
471484 }
472485 };
@@ -517,7 +530,7 @@ fn spawn_ssh_io(
517530 break;
518531 }
519532
520− eprintln!("[vibebox] starting ssh to {ssh_user}@{ip}");
533+ tracing::info!(user = %ssh_user, ip = %ip, "starting ssh");
521534 io_control_for_thread.request_terminal_restore();
522535 io_control_for_thread.set_forward_output(false);
523536 io_control_for_thread.set_forward_input(false);
@@ -527,13 +540,19 @@ fn spawn_ssh_io(
527540 loop {
528541 attempts += 1;
529542 if !ssh_port_open(&ip) {
530− eprintln!(
531− "[vibebox] waiting for ssh port on {ip} (attempt {attempts}/{SSH_CONNECT_RETRIES})"
543+ tracing::info!(
544+ attempts,
545+ ip = %ip,
546+ "waiting for ssh port ({}/{})",
547+ attempts,
548+ SSH_CONNECT_RETRIES
532549 );
533550 if attempts >= SSH_CONNECT_RETRIES {
534551 ssh_connected_for_thread.store(false, Ordering::SeqCst);
535− eprintln!(
536− "[vibebox] ssh port not ready after {SSH_CONNECT_RETRIES} attempts"
552+ tracing::warn!(
553+ attempts,
554+ "ssh port not ready after {} attempts",
555+ SSH_CONNECT_RETRIES
537556 );
538557 break;
539558 }
@@ -541,8 +560,13 @@ fn spawn_ssh_io(
541560 continue;
542561 }
543562
544− eprintln!(
545− "[vibebox] starting ssh to {ssh_user}@{ip} (attempt {attempts}/{SSH_CONNECT_RETRIES})"
563+ tracing::info!(
564+ attempts,
565+ user = %ssh_user,
566+ ip = %ip,
567+ "starting ssh ({}/{})",
568+ attempts,
569+ SSH_CONNECT_RETRIES
546570 );
547571 let status = Command::new("ssh")
548572 .args([
@@ -577,18 +601,20 @@ fn spawn_ssh_io(
577601 match status {
578602 Ok(status) if status.success() => {
579603 ssh_connected_for_thread.store(false, Ordering::SeqCst);
580− eprintln!("[vibebox] ssh exited with status: {status}");
604+ tracing::info!(status = %status, "ssh exited");
581605 if let Some(tx) = input_tx_holder_for_thread.lock().unwrap().clone() {
582606 let _ = tx.send(VmInput::Bytes(b"systemctl poweroff\n".to_vec()));
583607 }
584608 break;
585609 }
586610 Ok(status) if status.code() == Some(255) => {
587− eprintln!("[vibebox] ssh connection failed: {status}");
611+ tracing::warn!(status = %status, "ssh connection failed");
588612 if attempts >= SSH_CONNECT_RETRIES {
589613 ssh_connected_for_thread.store(false, Ordering::SeqCst);
590− eprintln!(
591− "[vibebox] ssh failed after {SSH_CONNECT_RETRIES} attempts"
614+ tracing::warn!(
615+ attempts,
616+ "ssh failed after {} attempts",
617+ SSH_CONNECT_RETRIES
592618 );
593619 break;
594620 }
@@ -597,7 +623,7 @@ fn spawn_ssh_io(
597623 }
598624 Ok(status) => {
599625 ssh_connected_for_thread.store(false, Ordering::SeqCst);
600− eprintln!("[vibebox] ssh exited with status: {status}");
626+ tracing::info!(status = %status, "ssh exited");
601627 if let Some(tx) = input_tx_holder_for_thread.lock().unwrap().clone() {
602628 let _ = tx.send(VmInput::Bytes(b"systemctl poweroff\n".to_vec()));
603629 }
@@ -605,7 +631,7 @@ fn spawn_ssh_io(
605631 }
606632 Err(err) => {
607633 ssh_connected_for_thread.store(false, Ordering::SeqCst);
608− eprintln!("[vibebox] failed to start ssh: {err}");
634+ tracing::error!(error = %err, "failed to start ssh");
609635 break;
610636 }
611637 }