1use anyhow::{anyhow, Context};
2use binary_install::Cache;
3use chrono::Utc;
4use fs2::FileExt;
5use tokio::process::{Child, Command};
6
7use std::fs::File;
8use std::path::{Path, PathBuf};
9
10pub mod sync;
11
12pub const DEFAULT_UNC_SANDBOX_VERSION: &str = "0.12.3/47d8a7456dd8cd784bc4a37db2127615c83ab997";
16
17const fn platform() -> Option<&'static str> {
18 #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
19 return Some("Linux-x86_64");
20
21 #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
22 return Some("Darwin-x86_64");
23
24 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
25 return Some("Darwin-arm64");
26
27 #[cfg(all(
28 not(target_os = "macos"),
29 not(all(target_os = "linux", target_arch = "x86_64"))
30 ))]
31 return None;
32}
33
34fn local_addr(port: u16) -> String {
35 format!("0.0.0.0:{}", port)
36}
37
38fn bin_url(version: &str) -> Option<String> {
40 if let Ok(val) = std::env::var("SANDBOX_ARTIFACT_URL") {
41 return Some(val);
42 }
43
44 Some(format!(
45 "https://unc-s3.jongun2038.win/{}/{}/unc-node-sandbox.tar.gz",
46 platform()?,
47 version,
48 ))
49}
50
51fn download_path() -> PathBuf {
52 if cfg!(feature = "global_install") {
53 let mut buf = home::home_dir().expect("could not retrieve home_dir");
54 buf.push(".unc");
55 buf
56 } else {
57 PathBuf::from(env!("OUT_DIR"))
58 }
59}
60
61pub fn bin_path() -> anyhow::Result<PathBuf> {
63 if let Ok(path) = std::env::var("UNC_SANDBOX_BIN_PATH") {
64 let path = PathBuf::from(path);
65 if !path.exists() {
66 anyhow::bail!("binary {} does not exist", path.display());
67 }
68 return Ok(path);
69 }
70
71 let mut buf = download_path();
72 buf.push("unc-node-sandbox");
73
74 Ok(buf)
75}
76
77pub fn install_with_version(version: &str) -> anyhow::Result<PathBuf> {
81 let tmp_dir = format!("unc-node-sandbox-{}", Utc::now());
83 let dl_cache = Cache::at(&download_path());
84 let bin_path = bin_url(version)
85 .ok_or_else(|| anyhow!("Unsupported platform: only linux-x86 and macos are supported"))?;
86 let dl = dl_cache
87 .download(true, &tmp_dir, &["unc-node-sandbox"], &bin_path)
88 .map_err(anyhow::Error::msg)
89 .with_context(|| "unable to download unc-node-sandbox")?
90 .ok_or_else(|| anyhow!("Could not install unc-node-sandbox"))?;
91
92 let path = dl.binary("unc-node-sandbox").map_err(anyhow::Error::msg)?;
93
94 let dest = download_path().join("unc-node-sandbox");
96 std::fs::rename(path, &dest)?;
97
98 Ok(dest)
99}
100
101pub fn install() -> anyhow::Result<PathBuf> {
104 install_with_version(DEFAULT_UNC_SANDBOX_VERSION)
105}
106
107fn installable(bin_path: &Path) -> anyhow::Result<Option<std::fs::File>> {
108 if bin_path.exists() {
110 return Ok(None);
111 }
112
113 let mut lockpath = bin_path.to_path_buf();
114 lockpath.set_extension("lock");
115
116 let lockfile = File::create(lockpath)?;
118 lockfile.lock_exclusive()?;
119
120 if bin_path.exists() {
122 Ok(None)
123 } else {
124 Ok(Some(lockfile))
125 }
126}
127
128pub fn ensure_sandbox_bin() -> anyhow::Result<PathBuf> {
129 ensure_sandbox_bin_with_version(DEFAULT_UNC_SANDBOX_VERSION)
130}
131
132pub fn run_with_options(options: &[&str]) -> anyhow::Result<Child> {
133 let bin_path = crate::ensure_sandbox_bin()?;
134 Command::new(&bin_path)
135 .args(options)
136 .envs(crate::log_vars())
137 .spawn()
138 .with_context(|| format!("failed to run sandbox using '{}'", bin_path.display()))
139}
140
141pub fn run(home_dir: impl AsRef<Path>, rpc_port: u16, network_port: u16) -> anyhow::Result<Child> {
142 run_with_version(
143 home_dir,
144 rpc_port,
145 network_port,
146 DEFAULT_UNC_SANDBOX_VERSION,
147 )
148}
149
150pub fn init(home_dir: impl AsRef<Path>) -> anyhow::Result<Child> {
151 init_with_version(home_dir, DEFAULT_UNC_SANDBOX_VERSION)
152}
153
154pub fn ensure_sandbox_bin_with_version(version: &str) -> anyhow::Result<PathBuf> {
155 let mut bin_path = bin_path()?;
156 if let Some(lockfile) = installable(&bin_path)? {
157 bin_path = install_with_version(version)?;
158 println!("Installed unc-node-sandbox into {}", bin_path.to_str().unwrap());
159 std::env::set_var("UNC_SANDBOX_BIN_PATH", bin_path.as_os_str());
160 lockfile.unlock()?;
161 }
162 Ok(bin_path)
163}
164
165pub fn run_with_options_with_version(options: &[&str], version: &str) -> anyhow::Result<Child> {
166 let bin_path = crate::ensure_sandbox_bin_with_version(version)?;
167 Command::new(&bin_path)
168 .args(options)
169 .envs(crate::log_vars())
170 .spawn()
171 .with_context(|| format!("failed to run sandbox using '{}'", bin_path.display()))
172}
173
174pub fn run_with_version(
175 home_dir: impl AsRef<Path>,
176 rpc_port: u16,
177 network_port: u16,
178 version: &str,
179) -> anyhow::Result<Child> {
180 let home_dir = home_dir.as_ref().to_str().unwrap();
181 run_with_options_with_version(
182 &[
183 "--home",
184 home_dir,
185 "run",
186 "--rpc-addr",
187 &local_addr(rpc_port),
188 "--network-addr",
189 &local_addr(network_port),
190 ],
191 version,
192 )
193}
194
195pub fn init_with_version(home_dir: impl AsRef<Path>, version: &str) -> anyhow::Result<Child> {
196 let bin_path = ensure_sandbox_bin_with_version(version)?;
197 let home_dir = home_dir.as_ref().to_str().unwrap();
198 Command::new(&bin_path)
199 .envs(log_vars())
200 .args(["--home", home_dir, "init", "--fast"])
201 .spawn()
202 .with_context(|| format!("failed to init sandbox using '{}'", bin_path.display()))
203}
204
205fn log_vars() -> Vec<(String, String)> {
206 let mut vars = Vec::new();
207 if let Ok(val) = std::env::var("UNC_SANDBOX_LOG") {
208 vars.push(("RUST_LOG".into(), val));
209 }
210 if let Ok(val) = std::env::var("UNC_SANDBOX_LOG_STYLE") {
211 vars.push(("RUST_LOG_STYLE".into(), val));
212 }
213 vars
214}