on main help sign in
refactor: organize all the params.
0d7c4a7robcholz
docs/tasks.md+5−1
@@ -43,7 +43,11 @@
436. [x] VM should be separated by a per-session VM daemon process (only accepts if to shut down vm and itself).
436. [x] VM should be separated by a per-session VM daemon process (only accepts if to shut down vm and itself).
447. [x] setup vibebox commands
447. [x] setup vibebox commands
458. [ ] setup cli commands.
458. [ ] setup cli commands.
46+ 1. [x] Organize all the params.
47+ 2. [ ] Remove old cli.
48+ 3. [ ] add an actual config file.
49+ 4. [ ] set up the cli.
46−9. [ ] fix ui overlap.
50+9. [ ] fix ui overlap, and consistency issue.
4710. [ ] intensive integration test.
5110. [ ] intensive integration test.
48
52
49## Publish
53## Publish
src/instance.rs+5−5
@@ -20,14 +20,14 @@ use uuid::Uuid;
20
20
21use crate::{
21use crate::{
22 commands,
22 commands,
23− session_manager::{INSTANCE_DIR_NAME, INSTANCE_TOML_FILENAME},
23+ session_manager::{INSTANCE_DIR_NAME, INSTANCE_FILENAME},
24 tui::AppState,
24 tui::AppState,
25 vm::{self, LoginAction, VmInput},
25 vm::{self, LoginAction, VmInput},
26};
26};
27
27
28const SSH_KEY_NAME: &str = "ssh_key";
28const SSH_KEY_NAME: &str = "ssh_key";
29#[allow(dead_code)]
29#[allow(dead_code)]
30−const SERIAL_LOG_NAME: &str = "serial.log";
30+pub(crate) const SERIAL_LOG_NAME: &str = "serial.log";
31const DEFAULT_SSH_USER: &str = "vibecoder";
31const DEFAULT_SSH_USER: &str = "vibecoder";
32const SSH_CONNECT_RETRIES: usize = 30;
32const SSH_CONNECT_RETRIES: usize = 30;
33const SSH_CONNECT_DELAY_MS: u64 = 500;
33const SSH_CONNECT_DELAY_MS: u64 = 500;
@@ -126,7 +126,7 @@ pub(crate) fn ensure_ssh_keypair(
126pub(crate) fn load_or_create_instance_config(
126pub(crate) fn load_or_create_instance_config(
127 instance_dir: &Path,
127 instance_dir: &Path,
128) -> Result<InstanceConfig, Box<dyn std::error::Error>> {
128) -> Result<InstanceConfig, Box<dyn std::error::Error>> {
129− let config_path = instance_dir.join(INSTANCE_TOML_FILENAME);
129+ let config_path = instance_dir.join(INSTANCE_FILENAME);
130 let mut config = if config_path.exists() {
130 let mut config = if config_path.exists() {
131 let raw = fs::read_to_string(&config_path)?;
131 let raw = fs::read_to_string(&config_path)?;
132 toml::from_str::<InstanceConfig>(&raw)?
132 toml::from_str::<InstanceConfig>(&raw)?
@@ -167,7 +167,7 @@ pub fn touch_last_active(instance_dir: &Path) -> Result<(), Box<dyn std::error::
167 let mut config = load_or_create_instance_config(instance_dir)?;
167 let mut config = load_or_create_instance_config(instance_dir)?;
168 let now = OffsetDateTime::now_utc().format(&Rfc3339)?;
168 let now = OffsetDateTime::now_utc().format(&Rfc3339)?;
169 config.last_active = Some(now);
169 config.last_active = Some(now);
170− write_instance_config(&instance_dir.join(INSTANCE_TOML_FILENAME), &config)?;
170+ write_instance_config(&instance_dir.join(INSTANCE_FILENAME), &config)?;
171 Ok(())
171 Ok(())
172}
172}
173
173
@@ -379,7 +379,7 @@ fn spawn_ssh_io(
379 let ssh_ready = Arc::new(AtomicBool::new(false));
379 let ssh_ready = Arc::new(AtomicBool::new(false));
380 let input_tx_holder: Arc<Mutex<Option<Sender<VmInput>>>> = Arc::new(Mutex::new(None));
380 let input_tx_holder: Arc<Mutex<Option<Sender<VmInput>>>> = Arc::new(Mutex::new(None));
381
381
382− let instance_path = instance_dir.join(INSTANCE_TOML_FILENAME);
382+ let instance_path = instance_dir.join(INSTANCE_FILENAME);
383 let config_for_output = config.clone();
383 let config_for_output = config.clone();
384 let log_for_output = log_file.clone();
384 let log_for_output = log_file.clone();
385 let ssh_connected_for_output = ssh_connected.clone();
385 let ssh_connected_for_output = ssh_connected.clone();
src/session_manager.rs+20−16
@@ -11,8 +11,7 @@ use crate::config::CONFIG_FILENAME;
11pub const INSTANCE_DIR_NAME: &str = ".vibebox";
11pub const INSTANCE_DIR_NAME: &str = ".vibebox";
12pub const GLOBAL_CACHE_DIR_NAME: &str = "vibebox";
12pub const GLOBAL_CACHE_DIR_NAME: &str = "vibebox";
13pub const GLOBAL_DIR_NAME: &str = ".vibebox";
13pub const GLOBAL_DIR_NAME: &str = ".vibebox";
14−pub const INSTANCE_TOML_FILENAME: &str = "instance.toml";
14+pub const INSTANCE_FILENAME: &str = "instance.toml";
15−pub const SESSION_TEMP_PREFIX: &str = "sessions";
16pub const SESSION_TOML_SUFFIX: &str = ".toml";
15pub const SESSION_TOML_SUFFIX: &str = ".toml";
17const SESSIONS_DIR_NAME: &str = "sessions";
16const SESSIONS_DIR_NAME: &str = "sessions";
18
17
@@ -100,7 +99,8 @@ impl SessionManager {
100 } else {
99 } else {
101 tracing::warn!(
100 tracing::warn!(
102 directory = %directory.display(),
101 directory = %directory.display(),
102+ file = INSTANCE_FILENAME,
103− "missing session id in instance.toml"
103+ "missing session id in instance file"
104 );
104 );
105 }
105 }
106 }
106 }
@@ -158,7 +158,7 @@ impl SessionManager {
158 }
158 }
159
159
160 fn session_path_for(&self, id: &str) -> PathBuf {
160 fn session_path_for(&self, id: &str) -> PathBuf {
161− let filename = format!("{id}.toml");
161+ let filename = format!("{id}{SESSION_TOML_SUFFIX}");
162 self.sessions_dir.join(filename)
162 self.sessions_dir.join(filename)
163 }
163 }
164
164
@@ -225,9 +225,7 @@ fn read_session_file(path: &Path) -> Result<SessionEntry, SessionError> {
225}
225}
226
226
227fn read_instance_metadata(directory: &Path) -> Result<InstanceMetadata, SessionError> {
227fn read_instance_metadata(directory: &Path) -> Result<InstanceMetadata, SessionError> {
228− let instance_path = directory
228+ let instance_path = directory.join(INSTANCE_DIR_NAME).join(INSTANCE_FILENAME);
229− .join(INSTANCE_DIR_NAME)
230− .join(INSTANCE_TOML_FILENAME);
231 if !instance_path.exists() {
229 if !instance_path.exists() {
232 return Ok(InstanceMetadata::default());
230 return Ok(InstanceMetadata::default());
233 }
231 }
@@ -256,7 +254,7 @@ fn atomic_write(path: &Path, content: &[u8]) -> io::Result<()> {
256
254
257 fs::create_dir_all(parent)?;
255 fs::create_dir_all(parent)?;
258 let mut temp = tempfile::Builder::new()
256 let mut temp = tempfile::Builder::new()
259− .prefix(SESSION_TEMP_PREFIX)
257+ .prefix(SESSIONS_DIR_NAME)
260 .suffix(SESSION_TOML_SUFFIX)
258 .suffix(SESSION_TOML_SUFFIX)
261 .tempfile_in(parent)?;
259 .tempfile_in(parent)?;
262 temp.write_all(content)?;
260 temp.write_all(content)?;
@@ -285,7 +283,7 @@ mod tests {
285 let instance_dir = project_dir.join(INSTANCE_DIR_NAME);
283 let instance_dir = project_dir.join(INSTANCE_DIR_NAME);
286 fs::create_dir_all(&instance_dir).unwrap();
284 fs::create_dir_all(&instance_dir).unwrap();
287 let content = format!("id = \"{id}\"\nlast_active = \"{last_active}\"\n");
285 let content = format!("id = \"{id}\"\nlast_active = \"{last_active}\"\n");
288− fs::write(instance_dir.join(INSTANCE_TOML_FILENAME), content).unwrap();
286+ fs::write(instance_dir.join(INSTANCE_FILENAME), content).unwrap();
289 }
287 }
290
288
291 #[test]
289 #[test]
@@ -306,9 +304,10 @@ mod tests {
306 assert_eq!(dirs[0], project_dir.canonicalize().unwrap());
304 assert_eq!(dirs[0], project_dir.canonicalize().unwrap());
307 assert!(mgr.index_path().exists());
305 assert!(mgr.index_path().exists());
308
306
309− let session_path = mgr
307+ let session_path = mgr.index_path().join(format!(
310− .index_path()
311− .join("019bf290-cccc-7c23-ba1d-dce7e6d40693.toml");
308+ "019bf290-cccc-7c23-ba1d-dce7e6d40693{}",
309+ SESSION_TOML_SUFFIX
310+ ));
312 assert!(session_path.exists());
311 assert!(session_path.exists());
313 }
312 }
314
313
@@ -329,9 +328,10 @@ mod tests {
329 let sessions = mgr.list_sessions().unwrap();
328 let sessions = mgr.list_sessions().unwrap();
330 assert!(sessions.is_empty());
329 assert!(sessions.is_empty());
331
330
332− let session_path = mgr
331+ let session_path = mgr.index_path().join(format!(
333− .index_path()
334− .join("019bf290-cccc-7c23-ba1d-dce7e6d40693.toml");
332+ "019bf290-cccc-7c23-ba1d-dce7e6d40693{}",
333+ SESSION_TOML_SUFFIX
334+ ));
335 assert!(!session_path.exists());
335 assert!(!session_path.exists());
336 }
336 }
337
337
@@ -340,7 +340,11 @@ mod tests {
340 let temp = TempDir::new().unwrap();
340 let temp = TempDir::new().unwrap();
341 let mgr = manager(&temp);
341 let mgr = manager(&temp);
342 fs::create_dir_all(mgr.index_path()).unwrap();
342 fs::create_dir_all(mgr.index_path()).unwrap();
343+ fs::write(
343− fs::write(mgr.index_path().join("bad.toml"), "not toml").unwrap();
344+ mgr.index_path().join(format!("bad{SESSION_TOML_SUFFIX}")),
345+ "not toml",
346+ )
347+ .unwrap();
344
348
345 let err = mgr.list_sessions().unwrap_err();
349 let err = mgr.list_sessions().unwrap_err();
346 assert!(matches!(err, SessionError::TomlDe(_)));
350 assert!(matches!(err, SessionError::TomlDe(_)));
src/vm.rs+12−9
@@ -43,6 +43,10 @@ const START_TIMEOUT: Duration = Duration::from_secs(60);
43const DEFAULT_EXPECT_TIMEOUT: Duration = Duration::from_secs(30);
43const DEFAULT_EXPECT_TIMEOUT: Duration = Duration::from_secs(30);
44const LOGIN_EXPECT_TIMEOUT: Duration = Duration::from_secs(120);
44const LOGIN_EXPECT_TIMEOUT: Duration = Duration::from_secs(120);
45const PROVISION_SCRIPT: &str = include_str!("provision.sh");
45const PROVISION_SCRIPT: &str = include_str!("provision.sh");
46+const PROVISION_SCRIPT_NAME: &str = "provision.sh";
47+const DEFAULT_RAW_NAME: &str = "default.raw";
48+const INSTANCE_RAW_NAME: &str = "instance.raw";
49+const BASE_DISK_RAW_NAME: &str = "disk.raw";
46
50
47#[derive(Clone)]
51#[derive(Clone)]
48pub(crate) enum LoginAction {
52pub(crate) enum LoginAction {
@@ -158,8 +162,8 @@ where
158 basename_compressed.trim_end_matches(".tar.xz")
162 basename_compressed.trim_end_matches(".tar.xz")
159 ));
163 ));
160
164
161− let default_raw = cache_dir.join("default.raw");
165+ let default_raw = cache_dir.join(DEFAULT_RAW_NAME);
162− let instance_raw = instance_dir.join("instance.raw");
166+ let instance_raw = instance_dir.join(INSTANCE_RAW_NAME);
163
167
164 // Prepare system-wide directories
168 // Prepare system-wide directories
165 fs::create_dir_all(&cache_dir)?;
169 fs::create_dir_all(&cache_dir)?;
@@ -212,11 +216,6 @@ where
212 for share in [
216 for share in [
213 DirectoryShare::new(home.join(".codex"), "/usr/local/codex".into(), false),
217 DirectoryShare::new(home.join(".codex"), "/usr/local/codex".into(), false),
214 DirectoryShare::new(home.join(".claude"), "/usr/local/claude".into(), false),
218 DirectoryShare::new(home.join(".claude"), "/usr/local/claude".into(), false),
215− DirectoryShare::new(
216− "/Users/zhangjie/Documents/Code/CompletePrograms/vibebox/.ssh".into(),
217− "/usr/local/ssh".into(),
218− true,
219− ),
220 ]
219 ]
221 .into_iter()
220 .into_iter()
222 .flatten()
221 .flatten()
@@ -629,7 +628,11 @@ fn ensure_base_image(
629
628
630 println!("Decompressing base image...");
629 println!("Decompressing base image...");
631 let status = Command::new("tar")
630 let status = Command::new("tar")
631+ .args([
632+ "-xOf",
632− .args(["-xOf", &base_compressed.to_string_lossy(), "disk.raw"])
633+ &base_compressed.to_string_lossy(),
634+ BASE_DISK_RAW_NAME,
635+ ])
633 .stdout(std::fs::File::create(base_raw)?)
636 .stdout(std::fs::File::create(base_raw)?)
634 .status()?;
637 .status()?;
635
638
@@ -655,7 +658,7 @@ fn ensure_default_image(
655 println!("Configuring base image...");
658 println!("Configuring base image...");
656 fs::copy(base_raw, default_raw)?;
659 fs::copy(base_raw, default_raw)?;
657
660
658− let provision_command = script_command_from_content("provision.sh", PROVISION_SCRIPT)?;
661+ let provision_command = script_command_from_content(PROVISION_SCRIPT_NAME, PROVISION_SCRIPT)?;
659 run_vm(
662 run_vm(
660 default_raw,
663 default_raw,
661 &[Send(provision_command)],
664 &[Send(provision_command)],
src/vm_manager.rs+7−5
@@ -15,16 +15,18 @@ use std::{
15};
15};
16
16
17use crate::{
17use crate::{
18+ instance::SERIAL_LOG_NAME,
18 instance::{
19 instance::{
19 InstanceConfig, build_ssh_login_actions, ensure_instance_dir, ensure_ssh_keypair,
20 InstanceConfig, build_ssh_login_actions, ensure_instance_dir, ensure_ssh_keypair,
20 extract_ipv4, load_or_create_instance_config, write_instance_config,
21 extract_ipv4, load_or_create_instance_config, write_instance_config,
21 },
22 },
22− session_manager::GLOBAL_DIR_NAME,
23+ session_manager::{GLOBAL_DIR_NAME, INSTANCE_FILENAME},
23 vm::{self, DirectoryShare, LoginAction, VmInput},
24 vm::{self, DirectoryShare, LoginAction, VmInput},
24};
25};
25
26
26const VM_MANAGER_SOCKET_NAME: &str = "vm.sock";
27const VM_MANAGER_SOCKET_NAME: &str = "vm.sock";
27const VM_MANAGER_LOCK_NAME: &str = "vm.lock";
28const VM_MANAGER_LOCK_NAME: &str = "vm.lock";
29+const VM_MANAGER_LOG_NAME: &str = "vm_manager.log";
28
30
29pub fn ensure_manager(
31pub fn ensure_manager(
30 raw_args: &[std::ffi::OsString],
32 raw_args: &[std::ffi::OsString],
@@ -132,7 +134,7 @@ fn spawn_manager_process(
132 cmd.env("VIBEBOX_LOG_NO_COLOR", "1");
134 cmd.env("VIBEBOX_LOG_NO_COLOR", "1");
133 cmd.env("VIBEBOX_AUTO_SHUTDOWN_MS", auto_shutdown_ms.to_string());
135 cmd.env("VIBEBOX_AUTO_SHUTDOWN_MS", auto_shutdown_ms.to_string());
134 tracing::info!(auto_shutdown_ms, "vm manager process spawn requested");
136 tracing::info!(auto_shutdown_ms, "vm manager process spawn requested");
135− let log_path = instance_dir.join("vm_manager.log");
137+ let log_path = instance_dir.join(VM_MANAGER_LOG_NAME);
136 let log_file = fs::OpenOptions::new()
138 let log_file = fs::OpenOptions::new()
137 .create(true)
139 .create(true)
138 .write(true)
140 .write(true)
@@ -280,7 +282,7 @@ fn spawn_manager_io(
280 vm_output_fd: std::os::unix::io::OwnedFd,
282 vm_output_fd: std::os::unix::io::OwnedFd,
281 vm_input_fd: std::os::unix::io::OwnedFd,
283 vm_input_fd: std::os::unix::io::OwnedFd,
282) -> vm::IoContext {
284) -> vm::IoContext {
283− let log_path = instance_dir.join("serial.log");
285+ let log_path = instance_dir.join(SERIAL_LOG_NAME);
284 let log_file = fs::OpenOptions::new()
286 let log_file = fs::OpenOptions::new()
285 .create(true)
287 .create(true)
286 .write(true)
288 .write(true)
@@ -289,7 +291,7 @@ fn spawn_manager_io(
289 .ok()
291 .ok()
290 .map(|file| Arc::new(Mutex::new(file)));
292 .map(|file| Arc::new(Mutex::new(file)));
291
293
292− let instance_path = instance_dir.join("instance.toml");
294+ let instance_path = instance_dir.join(INSTANCE_FILENAME);
293 let config_for_output = config.clone();
295 let config_for_output = config.clone();
294 let log_for_output = log_file.clone();
296 let log_for_output = log_file.clone();
295 let mut line_buf = String::new();
297 let mut line_buf = String::new();
@@ -465,7 +467,7 @@ fn run_manager_with(
465 let mut config = load_or_create_instance_config(&instance_dir)?;
467 let mut config = load_or_create_instance_config(&instance_dir)?;
466 if config.vm_ipv4.is_some() {
468 if config.vm_ipv4.is_some() {
467 config.vm_ipv4 = None;
469 config.vm_ipv4 = None;
468− write_instance_config(&instance_dir.join("instance.toml"), &config)?;
470+ write_instance_config(&instance_dir.join(INSTANCE_FILENAME), &config)?;
469 }
471 }
470 let config = Arc::new(Mutex::new(config));
472 let config = Arc::new(Mutex::new(config));
471
473