use std::env;
use std::fs;
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitCode};
use microsandbox_utils::{
LIBKRUNFW_ABI, MSB_BINARY, PREBUILT_VERSION, bundle_download_url, libkrunfw_filename,
};
fn main() -> ExitCode {
let msb = match resolve_or_install() {
Ok(p) => p,
Err(code) => return code,
};
let args: Vec<_> = env::args_os().skip(1).collect();
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let err = Command::new(&msb).args(&args).exec();
eprintln!("microsandbox: failed to exec {}: {err}", msb.display());
ExitCode::from(127)
}
#[cfg(not(unix))]
{
match Command::new(&msb).args(&args).status() {
Ok(status) => ExitCode::from(status.code().unwrap_or(1) as u8),
Err(err) => {
eprintln!("microsandbox: failed to run {}: {err}", msb.display());
ExitCode::from(127)
}
}
}
}
fn resolve_or_install() -> Result<PathBuf, ExitCode> {
if let Ok(path) = env::var("MSB_PATH") {
let p = PathBuf::from(&path);
if p.is_file() {
return Ok(p);
}
eprintln!("microsandbox: MSB_PATH points at nonexistent {path}");
return Err(ExitCode::from(127));
}
let Some(home) = env::var("HOME").ok().map(PathBuf::from) else {
eprintln!("microsandbox: HOME is not set; cannot locate msb");
return Err(ExitCode::from(127));
};
let base_dir = home.join(".microsandbox");
let bin_dir = base_dir.join("bin");
let lib_dir = base_dir.join("lib");
let msb_path = bin_dir.join(MSB_BINARY);
if msb_path.is_file() {
return Ok(msb_path);
}
let mut stderr = std::io::stderr();
let ansi = stderr.is_terminal();
if ansi {
let _ = stderr.write_all(b"\x1b[s");
}
let _ = writeln!(
stderr,
"microsandbox: preparing runtime v{PREBUILT_VERSION} (first run only)..."
);
if let Err(err) = install_runtime(&bin_dir, &lib_dir) {
let _ = writeln!(stderr, "microsandbox: failed to download runtime: {err}");
let _ = writeln!(
stderr,
"microsandbox: retry, or set MSB_PATH to an existing msb binary, \
or install manually: curl -fsSL https://install.microsandbox.dev | sh"
);
return Err(ExitCode::from(127));
}
if msb_path.is_file() {
if ansi {
let _ = stderr.write_all(b"\x1b[u\x1b[J");
} else {
let _ = writeln!(stderr, "microsandbox: runtime ready.");
}
Ok(msb_path)
} else {
let _ = writeln!(
stderr,
"microsandbox: install reported success but {} is still missing",
msb_path.display()
);
Err(ExitCode::from(127))
}
}
fn install_runtime(bin_dir: &Path, lib_dir: &Path) -> Result<(), String> {
fs::create_dir_all(bin_dir).map_err(|e| format!("mkdir {}: {e}", bin_dir.display()))?;
fs::create_dir_all(lib_dir).map_err(|e| format!("mkdir {}: {e}", lib_dir.display()))?;
let os = if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "linux") {
"linux"
} else {
return Err("unsupported operating system".into());
};
let arch = match env::consts::ARCH {
"aarch64" => "aarch64",
"x86_64" => "x86_64",
other => return Err(format!("unsupported architecture: {other}")),
};
let libkrunfw_name = libkrunfw_filename(os);
let url = bundle_download_url(PREBUILT_VERSION, arch, os);
let tmp_dir = env::temp_dir().join(format!(
"microsandbox-install-{}-{}",
std::process::id(),
PREBUILT_VERSION,
));
let _ = fs::remove_dir_all(&tmp_dir);
fs::create_dir_all(&tmp_dir).map_err(|e| format!("mkdir tmp: {e}"))?;
let tarball = tmp_dir.join("bundle.tar.gz");
let curl_status = Command::new("curl")
.args(["-fSL", "--progress-bar", "-o"])
.arg(&tarball)
.arg(&url)
.status()
.map_err(|e| format!("curl is required for runtime download but was not found: {e}"))?;
if !curl_status.success() {
return Err(format!("curl failed ({curl_status}) downloading {url}"));
}
let tar_status = Command::new("tar")
.args(["xzf"])
.arg(&tarball)
.arg("-C")
.arg(&tmp_dir)
.status()
.map_err(|e| format!("tar not found: {e}"))?;
if !tar_status.success() {
return Err(format!("tar failed ({tar_status})"));
}
for entry in fs::read_dir(&tmp_dir).map_err(|e| format!("read {}: {e}", tmp_dir.display()))? {
let entry = entry.map_err(|e| format!("read entry: {e}"))?;
let src = entry.path();
if !src.is_file() {
continue;
}
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.ends_with(".tar.gz") {
continue;
}
let dest = if name_str.starts_with("libkrunfw") {
lib_dir.join(&*name_str)
} else {
bin_dir.join(&*name_str)
};
let tmp_dest = dest.with_extension(format!("tmp-{}", std::process::id()));
fs::copy(&src, &tmp_dest).map_err(|e| format!("copy {}: {e}", src.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&tmp_dest, fs::Permissions::from_mode(0o755))
.map_err(|e| format!("chmod {}: {e}", tmp_dest.display()))?;
}
fs::rename(&tmp_dest, &dest).map_err(|e| format!("rename to {}: {e}", dest.display()))?;
}
#[cfg(unix)]
{
let symlinks: Vec<(String, String)> = if os == "macos" {
vec![("libkrunfw.dylib".into(), libkrunfw_name.clone())]
} else {
let soname = format!("libkrunfw.so.{LIBKRUNFW_ABI}");
vec![
(soname.clone(), libkrunfw_name.clone()),
("libkrunfw.so".into(), soname),
]
};
for (link_name, target) in &symlinks {
let link_path = lib_dir.join(link_name);
if link_path.exists() || link_path.is_symlink() {
let _ = fs::remove_file(&link_path);
}
std::os::unix::fs::symlink(target, &link_path)
.map_err(|e| format!("symlink {link_name} -> {target}: {e}"))?;
}
}
let _ = fs::remove_dir_all(&tmp_dir);
Ok(())
}