diff --git a/build/build-rust-package.nix b/build/build-rust-package.nix index 6a774b75..ea95b92e 100644 --- a/build/build-rust-package.nix +++ b/build/build-rust-package.nix @@ -172,7 +172,7 @@ let ''; CODCHI_WSL_VERSION_MIN = "2.0.14"; - CODCHI_WSL_VERSION_MAX = "2.4.8"; + CODCHI_WSL_VERSION_MAX = "2.4.11"; }; linux = rec { diff --git a/codchi/src/cli.rs b/codchi/src/cli.rs index a8e797b7..d2b776e6 100644 --- a/codchi/src/cli.rs +++ b/codchi/src/cli.rs @@ -511,6 +511,25 @@ See the following docs on how to register the completions with your shell: #[command(subcommand)] #[clap(about = "Utilities for interacting with the `codchistore` container.")] Store(StoreCmd), + + /// Try to recover the file system of a code machine + #[clap(hide = true)] + #[cfg(target_os = "windows")] + Recover { + /// Name of the code machine + name: String, + }, + + /// Edit a file of a code machine without starting it. + #[clap(hide = true)] + #[cfg(target_os = "windows")] + Visudo { + /// Name of the code machine + name: String, + + /// File to edit + file: String, + }, } mod secrets { diff --git a/codchi/src/main.rs b/codchi/src/main.rs index 1013d6d6..1fd3aeac 100644 --- a/codchi/src/main.rs +++ b/codchi/src/main.rs @@ -90,17 +90,6 @@ Thank you kindly!"# // preload config let cfg = CodchiConfig::get(); - if !matches!(cli.command, Some(Cmd::Tray {})) && cfg.tray.autostart { - thread::spawn(|| { - // prevent race condition when initializing codchistore - thread::sleep(Duration::from_millis(1000)); - Driver::host() - .start_tray(false) - .trace_err("Failed starting codchi's tray") - .ignore(); - }); - } - // process commands without the store commands match &cli.command { Some(Cmd::Tar { name, target_file }) => { @@ -119,9 +108,30 @@ Thank you kindly!"# exit(0); } }, + #[cfg(target_os = "windows")] + Some(Cmd::Visudo { name, file }) => { + Machine::by_name(name, false)?.visudo(&file.to_string())?; + exit(0); + } + #[cfg(target_os = "windows")] + Some(Cmd::Recover { name }) => { + platform::machine_recover(&name.to_string())?; + exit(0); + } _ => {} } + if !matches!(cli.command, Some(Cmd::Tray {})) && cfg.tray.autostart { + thread::spawn(|| { + // prevent race condition when initializing codchistore + thread::sleep(Duration::from_millis(1000)); + Driver::host() + .start_tray(false) + .trace_err("Failed starting codchi's tray") + .ignore(); + }); + } + CLI_ARGS .set(cli.clone()) .expect("Only main is allowed to set CLI_ARGS."); @@ -364,6 +374,12 @@ secret. Is this OK? [y/n]", Cmd::Tar { .. } => unreachable!(), Cmd::Store(_) => unreachable!(), + + #[cfg(target_os = "windows")] + Cmd::Visudo { .. } => unreachable!(), + + #[cfg(target_os = "windows")] + Cmd::Recover { .. } => unreachable!(), } if CodchiConfig::get().tray.autostart { Driver::host() diff --git a/codchi/src/platform/machine.rs b/codchi/src/platform/machine.rs index bf84fa74..a8bb2a55 100644 --- a/codchi/src/platform/machine.rs +++ b/codchi/src/platform/machine.rs @@ -46,6 +46,9 @@ pub trait MachineDriver: Sized { /// Duplicate the container backing this machine fn duplicate_container(&self, target: &Machine) -> Result<()>; + + /// Open the file without starting the machine + fn visudo(&self, file: &str) -> Result<()>; } #[derive(Debug, Clone)] diff --git a/codchi/src/platform/mod.rs b/codchi/src/platform/mod.rs index a8d27640..8e39f697 100644 --- a/codchi/src/platform/mod.rs +++ b/codchi/src/platform/mod.rs @@ -23,6 +23,8 @@ pub use platform::store_debug_shell; #[cfg(target_os = "windows")] pub use platform::store_recover; +#[cfg(target_os = "windows")] +pub use platform::machine_recover; #[cfg(target_os = "windows")] pub use platform::start_wsl_vpnkit; diff --git a/codchi/src/platform/windows/mod.rs b/codchi/src/platform/windows/mod.rs index c842e7b3..0b35120f 100644 --- a/codchi/src/platform/windows/mod.rs +++ b/codchi/src/platform/windows/mod.rs @@ -3,7 +3,7 @@ use super::{ Driver, LinuxCommandTarget, LinuxUser, Machine, MachineDriver, NixDriver, PlatformStatus, Store, }; use crate::progress_scope; -use crate::util::{with_tmp_file, LinuxPath, PathExt, ResultExt, UtilExt}; +use crate::util::{try_n_times, with_tmp_file, LinuxPath, PathExt, ResultExt, UtilExt}; use crate::{ cli::DEBUG, config::CodchiConfig, @@ -140,30 +140,25 @@ daemonize -e /var/log/wsl-vpnkit -o /var/log/wsl-vpnkit /bin/nix run 'nixpkgs#ws Ok(()) } +pub fn store_recover() -> Result<()> { + wsl::recover_instance( + consts::files::STORE_ROOTFS_NAME, + consts::CONTAINER_STORE_NAME, + ) +} + +pub fn machine_recover(machine_name: &str) -> Result<()> { + wsl::recover_instance( + consts::files::MACHINE_ROOTFS_NAME, + &machine::machine_name(machine_name), + ) +} + pub fn stop_wsl_vpnkit(store: &impl Store) -> Result<()> { store.cmd().run("pkill", &["wsl-vpnkit"]).wait_ok()?; Ok(()) } -pub fn win_path_to_wsl(path: &PathBuf) -> anyhow::Result { - wsl_command() - .args([ - "-d", - &consts::CONTAINER_STORE_NAME, - "--system", - "--user", - "root", - ]) - .args([ - "wslpath", - "-u", - &path.display().to_string().replace("\\", "/"), - ]) - .output_utf8_ok() - .map(|path| LinuxPath(path.trim().to_owned())) - .with_context(|| format!("Failed to run 'wslpath' with path {path:?}.")) -} - pub fn store_debug_shell() -> anyhow::Result<()> { LinuxCommandDriver { instance_name: consts::CONTAINER_STORE_NAME.to_string(), @@ -173,53 +168,6 @@ pub fn store_debug_shell() -> anyhow::Result<()> { Ok(()) } -pub fn store_recover() -> anyhow::Result<()> { - use wsl::extract_from_msix; - - extract_from_msix(files::STORE_ROOTFS_NAME, |store_tar| { - let tar_from_wsl = win_path_to_wsl(store_tar)?; - extract_from_msix("busybox", |busybox| { - let busybox_from_wsl = win_path_to_wsl(busybox)?; - wsl_command() - .args([ - "-d", - &consts::CONTAINER_STORE_NAME, - "--system", - "--user", - "root", - ]) - .args(["mount", "-o", "remount,rw", "/mnt/wslg/distro"]) - .wait_ok()?; - wsl_command() - .args([ - "-d", - &consts::CONTAINER_STORE_NAME, - "--system", - "--user", - "root", - ]) - .args([ - &busybox_from_wsl.0, - "tar", - "-C", - "/mnt/wslg/distro", - "-xzf", - &tar_from_wsl.0, - ]) - .wait_ok()?; - - let _ = wsl::wsl_command() - .arg("--terminate") - .arg(consts::CONTAINER_STORE_NAME) - .wait_ok() - .trace_err("Failed stopping store container"); - - log::info!("Restored file system of `codchistore`."); - Ok(()) - }) - }) -} - impl MachineDriver for Machine { fn cmd(&self) -> impl LinuxCommandTarget { LinuxCommandDriver { @@ -345,9 +293,23 @@ tail -f "{log_file}" }); // Machine is started by issuing a command - self.cmd() - .script(r#"systemctl is-system-running | grep -E "running|degraded""#.to_string()) - .retry_until_ok(); + if let Err(e) = try_n_times(Duration::from_millis(500), 20, || { + self.cmd() + .script(r#"systemctl is-system-running | grep -E "running|degraded""#.to_string()) + .wait_ok() + }) { + log::error!("{}", e); + log::error!("Failed starting machine. Trying to recover..."); + machine_recover(&self.config.name)?; + try_n_times(Duration::from_millis(500), 20, || { + self.cmd() + .script( + r#"systemctl is-system-running | grep -E "running|degraded""#.to_string(), + ) + .wait_ok() + }) + .with_context(|| format!("Failed starting WSL instance..."))?; + } cancel_tx .send(()) @@ -416,7 +378,7 @@ tail -f "{log_file}" let mut cmd = wsl_command(); cmd.args([ "-d", - &consts::machine::machine_name(&self.config.name), + &machine::machine_name(&self.config.name), "--system", "--user", "root", @@ -466,7 +428,7 @@ tail -f "{log_file}" self.write_env_file(etc_dir.join("codchi-env"))?; - let tmp_in_wsl = win_path_to_wsl(&tmp_dir)?; + let tmp_in_wsl = wsl::win_path_to_wsl(&tmp_dir)?; wsl_cmd() .args(["cp", "-r", &format!("{}/*", tmp_in_wsl.0), merged]) @@ -475,7 +437,7 @@ tail -f "{log_file}" Ok(()) })?; - let wsl_path = win_path_to_wsl(&target_absolute)?; + let wsl_path = wsl::win_path_to_wsl(&target_absolute)?; wsl_cmd() .args([ "/mnt/wslg/distro/bin/tar", @@ -513,7 +475,7 @@ tail -f "{log_file}" } fn duplicate_container(&self, target: &Machine) -> Result<()> { - let target_name = consts::machine::machine_name(&target.config.name); + let target_name = machine::machine_name(&target.config.name); if Self::read_platform_status(&self.config.name)? == PlatformStatus::Running { if io::stdin().is_terminal() && inquire::Confirm::new(&format!( @@ -556,6 +518,33 @@ state. Is this OK? [y/n]", } Ok(()) } + + fn visudo(&self, file: &str) -> Result<()> { + wsl_command() + .args([ + "-d", + &machine::machine_name(&self.config.name), + "--system", + "--user", + "root", + ]) + .args(["mount", "-o", "remount,rw", "/mnt/wslg/distro"]) + .wait_ok()?; + wsl_command() + .args([ + "-d", + &machine::machine_name(&self.config.name), + "--cd", + "/mnt/wslg/distro", + "--system", + "--user", + "root", + ]) + .args(["./bin/vi", &format!("./{file}")]) + .wait_inherit()?; + + Ok(()) + } } #[derive(Debug, Clone)] diff --git a/codchi/src/platform/windows/wsl.rs b/codchi/src/platform/windows/wsl.rs index 114e8ff0..d5087822 100644 --- a/codchi/src/platform/windows/wsl.rs +++ b/codchi/src/platform/windows/wsl.rs @@ -207,3 +207,69 @@ impl LinuxPath { .join(self.0.clone()) } } + +pub fn win_path_to_wsl(path: &PathBuf) -> anyhow::Result { + wsl_command() + .args([ + "-d", + &consts::CONTAINER_STORE_NAME, + "--system", + "--user", + "root", + ]) + .args([ + "wslpath", + "-u", + &path.display().to_string().replace("\\", "/"), + ]) + .output_utf8_ok() + .map(|path| LinuxPath(path.trim().to_owned())) + .with_context(|| format!("Failed to run 'wslpath' with path {path:?}.")) +} + + +pub fn recover_instance(rootfs: &str, instance_name: &str) -> anyhow::Result<()> { + extract_from_msix(rootfs, |rootfs_tar| { + let tar_from_wsl = win_path_to_wsl(rootfs_tar)?; + extract_from_msix("busybox", |busybox| { + let busybox_from_wsl = win_path_to_wsl(busybox)?; + wsl_command() + .args([ + "-d", + instance_name, + "--system", + "--user", + "root", + ]) + .args(["mount", "-o", "remount,rw", "/mnt/wslg/distro"]) + .wait_ok()?; + wsl_command() + .args([ + "-d", + instance_name, + "--system", + "--user", + "root", + ]) + .args([ + &busybox_from_wsl.0, + "tar", + "-C", + "/mnt/wslg/distro", + "-xzf", + &tar_from_wsl.0, + ]) + .wait_ok()?; + + let _ = wsl_command() + .arg("--terminate") + .arg(instance_name) + .wait_ok() + .trace_err("Failed stopping WSL instance"); + + log::info!("Restored file system of `{instance_name}`."); + Ok(()) + }) + }) +} + diff --git a/codchi/src/tray/mod.rs b/codchi/src/tray/mod.rs index 4f6c08a3..f0674f9b 100644 --- a/codchi/src/tray/mod.rs +++ b/codchi/src/tray/mod.rs @@ -216,7 +216,7 @@ pub fn run() -> Result<()> { settings.push(mk_checkbox( "Isolate the network of each code machine (requires restarting machines)", |config| config.enable_wsl_netns, - |cfg, enabled| cfg.enable_wsl_vpnkit(enabled), + |cfg, enabled| cfg.enable_wsl_netns(enabled), )); settings.push(mk_checkbox( "Enable wsl-vpnkit", diff --git a/codchi/src/util.rs b/codchi/src/util.rs index 3b5430b1..5f723a3b 100644 --- a/codchi/src/util.rs +++ b/codchi/src/util.rs @@ -42,17 +42,20 @@ impl UtilExt for A { fn ignore(self) {} } -pub fn try_n_times(interval: Duration, n: usize, f: F) -> Result +pub fn try_n_times(interval: Duration, n: usize, f: F) -> Result where - F: Fn() -> Result, + F: Fn() -> Result, { - for _ in 0..n { - if f()? { - return Ok(true); + let mut i = 1; + loop { + match f() { + Ok(x) => return Ok(x), + Err(e) if i == n => return Err(e), + _ => {} } + i = i + 1; thread::sleep(interval); } - Ok(false) } pub fn make_writeable_if_exists>(path: P) -> io::Result<()> { diff --git a/nix/container/machine/wsl.nix b/nix/container/machine/wsl.nix index 53b04eed..5450fdc9 100644 --- a/nix/container/machine/wsl.nix +++ b/nix/container/machine/wsl.nix @@ -109,9 +109,8 @@ in echo "Using IP $VPEER_ADDR for namespace $NS" >&2 if [ ! -d /var/run/netns ]; then - mkdir -p /var/run - mount -t tmpfs -o size=10m tmpfs /var/run - mkdir /var/run/netns || true + mkdir -p /var/run/netns + mount -t tmpfs -o size=10m tmpfs /var/run/netns fi # setup namespace diff --git a/nix/nixos/driver/wsl/default.nix b/nix/nixos/driver/wsl/default.nix index c7b7e981..6365868f 100644 --- a/nix/nixos/driver/wsl/default.nix +++ b/nix/nixos/driver/wsl/default.nix @@ -93,6 +93,7 @@ in unset WAYLAND_DISPLAY export GDK_BACKEND=x11 else + export DISPLAY=:0 export GDK_BACKEND=wayland fi '';