use std::path::{Path, PathBuf};
use anyhow::Context as _;
use crate::cache_root;
const PINNED: Option<&str> = match option_env!("VIRTX_UVM_PINNED_VERSION") {
Some(version) if !version.is_empty() => Some(version),
_ => None,
};
const DIST_URL: &str = "https://virtx-dist.s3.us-east-1.amazonaws.com";
pub async fn ensure_virtx() -> anyhow::Result<PathBuf> {
let root = cache_root();
let bin = root.join("bin");
let server = format!("virtx-uvm{}", std::env::consts::EXE_SUFFIX);
if bin.join(&server).is_file() {
return Ok(bin);
}
let platform = format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH);
let base = std::env::var("VIRTX_DIST_URL")
.ok()
.filter(|url| !url.is_empty())
.unwrap_or_else(|| DIST_URL.to_string());
let release = std::env::var("VIRTX_UVM_VERSION")
.ok()
.filter(|v| !v.is_empty())
.or_else(|| PINNED.map(str::to_string))
.unwrap_or_else(|| "latest".to_string());
let url = format!(
"{}/virtx-uvm/{release}/virtx-uvm-{platform}.tar.gz",
base.trim_end_matches('/')
);
let archive = fetch(&url).await.with_context(|| {
format!("no virtx-uvm release is published for {platform} as `{release}`")
})?;
tokio::task::spawn_blocking(move || unpack(&archive, &root, &server))
.await
.context("unpacking virtx-uvm panicked")??;
Ok(bin)
}
async fn fetch(url: &str) -> anyhow::Result<Vec<u8>> {
let response = reqwest::get(url)
.await
.with_context(|| format!("fetching {url}"))?;
let status = response.status();
anyhow::ensure!(status.is_success(), "fetching {url}: {status}");
let body = response
.bytes()
.await
.with_context(|| format!("reading {url}"))?;
Ok(body.to_vec())
}
fn unpack(archive: &[u8], root: &Path, server: &str) -> anyhow::Result<()> {
let part = root.join(format!(".bin.{}.part", std::process::id()));
let _ = std::fs::remove_dir_all(&part);
std::fs::create_dir_all(&part).with_context(|| format!("creating {}", part.display()))?;
let moved = (|| {
tar::Archive::new(flate2::read::GzDecoder::new(archive))
.unpack(&part)
.context("unpacking the virtx-uvm archive")?;
anyhow::ensure!(
part.join(server).is_file(),
"the virtx-uvm archive has no {server}"
);
let bin = root.join("bin");
std::fs::create_dir_all(&bin).with_context(|| format!("creating {}", bin.display()))?;
for entry in std::fs::read_dir(&part)? {
let entry = entry?;
if entry.file_name() != server {
replace(&entry.path(), &bin.join(entry.file_name()))?;
}
}
replace(&part.join(server), &bin.join(server))
})();
let _ = std::fs::remove_dir_all(&part);
moved
}
fn replace(from: &Path, to: &Path) -> anyhow::Result<()> {
if from.is_dir() && to.is_dir() {
std::fs::remove_dir_all(to).with_context(|| format!("removing {}", to.display()))?;
}
std::fs::rename(from, to)
.with_context(|| format!("moving {} to {}", from.display(), to.display()))
}