on main help sign in
feat: added better info log
ac67e54robcholz
src/instance.rs+13−2
@@ -27,6 +27,7 @@ use crate::{
27
27
28const SSH_KEY_NAME: &str = "ssh_key";
28const SSH_KEY_NAME: &str = "ssh_key";
29pub(crate) const VM_ROOT_LOG_NAME: &str = "vm_root.log";
29pub(crate) const VM_ROOT_LOG_NAME: &str = "vm_root.log";
30+pub(crate) const STATUS_FILE_NAME: &str = "status.txt";
30pub(crate) const DEFAULT_SSH_USER: &str = "vibecoder";
31pub(crate) const DEFAULT_SSH_USER: &str = "vibecoder";
31const SSH_CONNECT_RETRIES: usize = 30;
32const SSH_CONNECT_RETRIES: usize = 30;
32const SSH_CONNECT_DELAY_MS: u64 = 500;
33const SSH_CONNECT_DELAY_MS: u64 = 500;
@@ -247,6 +248,8 @@ fn wait_for_vm_ipv4(
247 let start = Instant::now();
248 let start = Instant::now();
248 let mut next_log_at = start + Duration::from_secs(10);
249 let mut next_log_at = start + Duration::from_secs(10);
249 tracing::info!("waiting for vm ipv4");
250 tracing::info!("waiting for vm ipv4");
251+ let status_path = instance_dir.join(STATUS_FILE_NAME);
252+ let mut last_status: Option<String> = None;
250 let mut once_hint = false;
253 let mut once_hint = false;
251 loop {
254 loop {
252 let config = load_or_create_instance_config(instance_dir)?;
255 let config = load_or_create_instance_config(instance_dir)?;
@@ -260,11 +263,19 @@ fn wait_for_vm_ipv4(
260 let waited = start.elapsed();
263 let waited = start.elapsed();
261 if waited.as_secs() > 15 && !once_hint {
264 if waited.as_secs() > 15 && !once_hint {
262 tracing::info!(
265 tracing::info!(
263− "if vibebox is just initialized in this directory, it might take up to 1 minutes depending on your machine, and then you can enjoy the speed vibecoding! go pack!"
266+ "if vibebox is just initialized in this directory, it might take up to 1 minute depending on your machine, and then you can enjoy secure & speed vibecoding! go pack!"
264 );
267 );
265 once_hint = true;
268 once_hint = true;
266 }
269 }
270+ if let Ok(status) = fs::read_to_string(&status_path) {
271+ let status = status.trim().to_string();
272+ if !status.is_empty() && last_status.as_deref() != Some(status.as_str()) {
273+ tracing::info!("[background]: {}", status);
274+ last_status = Some(status);
275+ }
276+ } else {
267− tracing::info!("still waiting for vm ipv4, {}s elapsed", waited.as_secs(),);
277+ tracing::info!("still waiting for vm ipv4, {}s elapsed", waited.as_secs(),);
278+ }
268 next_log_at += Duration::from_secs(10);
279 next_log_at += Duration::from_secs(10);
269 }
280 }
270 thread::sleep(Duration::from_millis(200));
281 thread::sleep(Duration::from_millis(200));
src/vm.rs+66−2
@@ -1,3 +1,4 @@
1+use crate::instance::STATUS_FILE_NAME;
1use crate::session_manager::{GLOBAL_CACHE_DIR_NAME, INSTANCE_DIR_NAME};
2use crate::session_manager::{GLOBAL_CACHE_DIR_NAME, INSTANCE_DIR_NAME};
2use std::{
3use std::{
3 env, fs,
4 env, fs,
@@ -38,6 +39,39 @@ const DEFAULT_RAM_MB: u64 = 2048;
38const DEFAULT_RAM_BYTES: u64 = DEFAULT_RAM_MB * BYTES_PER_MB;
39const DEFAULT_RAM_BYTES: u64 = DEFAULT_RAM_MB * BYTES_PER_MB;
39const START_TIMEOUT: Duration = Duration::from_secs(60);
40const START_TIMEOUT: Duration = Duration::from_secs(60);
40const LOGIN_EXPECT_TIMEOUT: Duration = Duration::from_secs(120);
41const LOGIN_EXPECT_TIMEOUT: Duration = Duration::from_secs(120);
42+
43+struct StatusFile {
44+ path: PathBuf,
45+ cleared: AtomicBool,
46+}
47+
48+impl StatusFile {
49+ fn new(path: PathBuf) -> Self {
50+ Self {
51+ path,
52+ cleared: AtomicBool::new(false),
53+ }
54+ }
55+
56+ fn update(&self, message: &str) {
57+ let _ = fs::write(&self.path, message);
58+ }
59+
60+ fn clear(&self) {
61+ if !self.cleared.swap(true, Ordering::SeqCst) {
62+ let _ = fs::remove_file(&self.path);
63+ }
64+ }
65+}
66+
67+impl Drop for StatusFile {
68+ fn drop(&mut self) {
69+ if !self.cleared.load(Ordering::SeqCst) {
70+ let _ = fs::remove_file(&self.path);
71+ self.cleared.store(true, Ordering::SeqCst);
72+ }
73+ }
74+}
41const PROVISION_SCRIPT: &str = include_str!("provision.sh");
75const PROVISION_SCRIPT: &str = include_str!("provision.sh");
42const PROVISION_SCRIPT_NAME: &str = "provision.sh";
76const PROVISION_SCRIPT_NAME: &str = "provision.sh";
43const DEFAULT_RAW_NAME: &str = "default.raw";
77const DEFAULT_RAW_NAME: &str = "default.raw";
@@ -170,6 +204,8 @@ where
170 let guest_mise_cache = cache_dir.join(".guest-mise-cache");
204 let guest_mise_cache = cache_dir.join(".guest-mise-cache");
171
205
172 let instance_dir = project_root.join(INSTANCE_DIR_NAME);
206 let instance_dir = project_root.join(INSTANCE_DIR_NAME);
207+ let status_file = StatusFile::new(instance_dir.join(STATUS_FILE_NAME));
208+ status_file.update("preparing VM image...");
173
209
174 let basename_compressed = DEBIAN_COMPRESSED_DISK_URL.rsplit('/').next().unwrap();
210 let basename_compressed = DEBIAN_COMPRESSED_DISK_URL.rsplit('/').next().unwrap();
175 let base_compressed = cache_dir.join(basename_compressed);
211 let base_compressed = cache_dir.join(basename_compressed);
@@ -193,8 +229,9 @@ where
193 &base_compressed,
229 &base_compressed,
194 &default_raw,
230 &default_raw,
195 std::slice::from_ref(&mise_directory_share),
231 std::slice::from_ref(&mise_directory_share),
232+ Some(&status_file),
196 )?;
233 )?;
197− ensure_instance_disk(&instance_raw, &default_raw)?;
234+ ensure_instance_disk(&instance_raw, &default_raw, Some(&status_file))?;
198 let disk_path = instance_raw;
235 let disk_path = instance_raw;
199
236
200 let mut login_actions = Vec::new();
237 let mut login_actions = Vec::new();
@@ -239,6 +276,7 @@ where
239 &directory_shares[..],
276 &directory_shares[..],
240 args.cpu_count,
277 args.cpu_count,
241 args.ram_bytes,
278 args.ram_bytes,
279+ Some(&status_file),
242 io_handler,
280 io_handler,
243 )
281 )
244}
282}
@@ -421,6 +459,7 @@ impl IoControl {
421fn ensure_base_image(
459fn ensure_base_image(
422 base_raw: &Path,
460 base_raw: &Path,
423 base_compressed: &Path,
461 base_compressed: &Path,
462+ status: Option<&StatusFile>,
424) -> Result<(), Box<dyn std::error::Error>> {
463) -> Result<(), Box<dyn std::error::Error>> {
425 if base_raw.exists() {
464 if base_raw.exists() {
426 return Ok(());
465 return Ok(());
@@ -429,6 +468,9 @@ fn ensure_base_image(
429 if !base_compressed.exists()
468 if !base_compressed.exists()
430 || std::fs::metadata(base_compressed).map(|m| m.len())? < DEBIAN_COMPRESSED_SIZE_BYTES
469 || std::fs::metadata(base_compressed).map(|m| m.len())? < DEBIAN_COMPRESSED_SIZE_BYTES
431 {
470 {
471+ if let Some(status) = status {
472+ status.update("downloading base image...");
473+ }
432 tracing::info!("downloading base image");
474 tracing::info!("downloading base image");
433 let status = Command::new("curl")
475 let status = Command::new("curl")
434 .args([
476 .args([
@@ -449,6 +491,9 @@ fn ensure_base_image(
449
491
450 // Check SHA
492 // Check SHA
451 {
493 {
494+ if let Some(status) = status {
495+ status.update("verifying base image...");
496+ }
452 let input = format!("{} {}\n", DEBIAN_COMPRESSED_SHA, base_compressed.display());
497 let input = format!("{} {}\n", DEBIAN_COMPRESSED_SHA, base_compressed.display());
453
498
454 let mut child = Command::new("/usr/bin/shasum")
499 let mut child = Command::new("/usr/bin/shasum")
@@ -470,6 +515,9 @@ fn ensure_base_image(
470 }
515 }
471 }
516 }
472
517
518+ if let Some(status) = status {
519+ status.update("decompressing base image...");
520+ }
473 tracing::info!("decompressing base image");
521 tracing::info!("decompressing base image");
474 let status = Command::new("tar")
522 let status = Command::new("tar")
475 .args([
523 .args([
@@ -492,13 +540,17 @@ fn ensure_default_image(
492 base_compressed: &Path,
540 base_compressed: &Path,
493 default_raw: &Path,
541 default_raw: &Path,
494 directory_shares: &[DirectoryShare],
542 directory_shares: &[DirectoryShare],
543+ status: Option<&StatusFile>,
495) -> Result<(), Box<dyn std::error::Error>> {
544) -> Result<(), Box<dyn std::error::Error>> {
496 if default_raw.exists() {
545 if default_raw.exists() {
497 return Ok(());
546 return Ok(());
498 }
547 }
499
548
500− ensure_base_image(base_raw, base_compressed)?;
549+ ensure_base_image(base_raw, base_compressed, status)?;
501
550
551+ if let Some(status) = status {
552+ status.update("configuring base image...");
553+ }
502 tracing::info!("configuring base image");
554 tracing::info!("configuring base image");
503 fs::copy(base_raw, default_raw)?;
555 fs::copy(base_raw, default_raw)?;
504
556
@@ -509,6 +561,7 @@ fn ensure_default_image(
509 directory_shares,
561 directory_shares,
510 DEFAULT_CPU_COUNT,
562 DEFAULT_CPU_COUNT,
511 DEFAULT_RAM_BYTES,
563 DEFAULT_RAM_BYTES,
564+ None,
512 )?;
565 )?;
513
566
514 Ok(())
567 Ok(())
@@ -517,11 +570,15 @@ fn ensure_default_image(
517fn ensure_instance_disk(
570fn ensure_instance_disk(
518 instance_raw: &Path,
571 instance_raw: &Path,
519 template_raw: &Path,
572 template_raw: &Path,
573+ status: Option<&StatusFile>,
520) -> Result<(), Box<dyn std::error::Error>> {
574) -> Result<(), Box<dyn std::error::Error>> {
521 if instance_raw.exists() {
575 if instance_raw.exists() {
522 return Ok(());
576 return Ok(());
523 }
577 }
524
578
579+ if let Some(status) = status {
580+ status.update("creating instance disk...");
581+ }
525 tracing::info!(path = %template_raw.display(), "creating instance disk");
582 tracing::info!(path = %template_raw.display(), "creating instance disk");
526 std::fs::create_dir_all(instance_raw.parent().unwrap())?;
583 std::fs::create_dir_all(instance_raw.parent().unwrap())?;
527 fs::copy(template_raw, instance_raw)?;
584 fs::copy(template_raw, instance_raw)?;
@@ -980,6 +1037,7 @@ fn run_vm_with_io<F>(
980 directory_shares: &[DirectoryShare],
1037 directory_shares: &[DirectoryShare],
981 cpu_count: usize,
1038 cpu_count: usize,
982 ram_bytes: u64,
1039 ram_bytes: u64,
1040+ status: Option<&StatusFile>,
983 io_handler: F,
1041 io_handler: F,
984) -> Result<(), Box<dyn std::error::Error>>
1042) -> Result<(), Box<dyn std::error::Error>>
985where
1043where
@@ -1042,6 +1100,10 @@ where
1042 return Err("Timed out waiting for VM to start".into());
1100 return Err("Timed out waiting for VM to start".into());
1043 }
1101 }
1044
1102
1103+ if let Some(status) = status {
1104+ status.update("vm booting... go vibecoder!");
1105+ status.clear();
1106+ }
1045 tracing::info!("vm booting");
1107 tracing::info!("vm booting");
1046
1108
1047 let output_monitor = Arc::new(OutputMonitor::default());
1109 let output_monitor = Arc::new(OutputMonitor::default());
@@ -1142,6 +1204,7 @@ fn run_vm(
1142 directory_shares: &[DirectoryShare],
1204 directory_shares: &[DirectoryShare],
1143 cpu_count: usize,
1205 cpu_count: usize,
1144 ram_bytes: u64,
1206 ram_bytes: u64,
1207+ status: Option<&StatusFile>,
1145) -> Result<(), Box<dyn std::error::Error>> {
1208) -> Result<(), Box<dyn std::error::Error>> {
1146 run_vm_with_io(
1209 run_vm_with_io(
1147 disk_path,
1210 disk_path,
@@ -1149,6 +1212,7 @@ fn run_vm(
1149 directory_shares,
1212 directory_shares,
1150 cpu_count,
1213 cpu_count,
1151 ram_bytes,
1214 ram_bytes,
1215+ status,
1152 spawn_vm_io,
1216 spawn_vm_io,
1153 )
1217 )
1154}
1218}