Skip to main content

microsandbox_utils/
lib.rs

1//! Shared constants and utilities for the microsandbox project.
2
3pub mod copy;
4pub mod log_text;
5pub mod size;
6pub mod ttl_reverse_index;
7pub mod wake_pipe;
8
9//--------------------------------------------------------------------------------------------------
10// Constants: Directory Layout
11//--------------------------------------------------------------------------------------------------
12
13/// Name of the microsandbox home directory (relative to user's home).
14pub const BASE_DIR_NAME: &str = ".microsandbox";
15
16/// Subdirectory for shared libraries (libkrunfw).
17pub const LIB_SUBDIR: &str = "lib";
18
19/// Subdirectory for helper binaries.
20pub const BIN_SUBDIR: &str = "bin";
21
22/// Subdirectory for the database.
23pub const DB_SUBDIR: &str = "db";
24
25/// Subdirectory for OCI layer cache.
26pub const CACHE_SUBDIR: &str = "cache";
27
28/// Subdirectory for per-sandbox state.
29pub const SANDBOXES_SUBDIR: &str = "sandboxes";
30
31/// Subdirectory for named volumes.
32pub const VOLUMES_SUBDIR: &str = "volumes";
33
34/// Subdirectory for snapshot artifacts.
35pub const SNAPSHOTS_SUBDIR: &str = "snapshots";
36
37/// Subdirectory for logs.
38pub const LOGS_SUBDIR: &str = "logs";
39
40/// Subdirectory for secrets.
41pub const SECRETS_SUBDIR: &str = "secrets";
42
43/// Subdirectory for TLS certificates.
44pub const TLS_SUBDIR: &str = "tls";
45
46/// Subdirectory for SSH keys.
47pub const SSH_SUBDIR: &str = "ssh";
48
49/// Subdirectory for ephemeral runtime artifacts that should not be backed up.
50pub const RUN_SUBDIR: &str = "run";
51
52/// Subdirectory under `run` for metrics-related diagnostic artifacts.
53pub const METRICS_RUN_SUBDIR: &str = "metrics";
54
55/// Filename of the optional registry-name diagnostic file under `run/metrics`.
56pub const METRICS_REGISTRY_NAME_FILENAME: &str = "registry-v1.name";
57
58/// Prefix used when constructing the POSIX shared-memory object name for the
59/// live metrics registry. Combined with a stable hash of `GlobalConfig::home()`
60/// so concurrent `MSB_HOME`-isolated environments do not collide.
61///
62/// Kept short because macOS limits `shm_open` names to ~31 bytes including the
63/// leading slash; the final form is `<prefix>-<hex16>-v1` (28 bytes).
64pub const METRICS_SHM_PREFIX: &str = "/msb-met";
65
66//--------------------------------------------------------------------------------------------------
67// Constants: Binary Names
68//--------------------------------------------------------------------------------------------------
69
70/// Guest agent binary name.
71pub const AGENTD_BINARY: &str = "agentd";
72
73/// CLI binary name.
74pub const MSB_BINARY: &str = "msb";
75
76//--------------------------------------------------------------------------------------------------
77// Constants: Versions
78//--------------------------------------------------------------------------------------------------
79
80/// Version for downloading prebuilt release artifacts.
81///
82/// This tracks the published crate/package version so the SDK and the
83/// downloaded runtime bundle stay aligned.
84pub const PREBUILT_VERSION: &str = env!("CARGO_PKG_VERSION");
85
86/// libkrunfw release version. Keep in sync with justfile.
87pub const LIBKRUNFW_VERSION: &str = "5.2.1";
88
89/// libkrunfw ABI version (soname major). Keep in sync with justfile.
90pub const LIBKRUNFW_ABI: &str = "5";
91
92//--------------------------------------------------------------------------------------------------
93// Constants: Filenames
94//--------------------------------------------------------------------------------------------------
95
96/// Database filename.
97pub const DB_FILENAME: &str = "msb.db";
98
99/// Global configuration filename.
100pub const CONFIG_FILENAME: &str = "config.json";
101
102/// Project-local sandbox configuration filename.
103pub const SANDBOXFILE_NAME: &str = "Sandboxfile";
104
105//--------------------------------------------------------------------------------------------------
106// Constants: GitHub
107//--------------------------------------------------------------------------------------------------
108
109/// GitHub organization.
110pub const GITHUB_ORG: &str = "superradcompany";
111
112/// Main repository name.
113pub const MICROSANDBOX_REPO: &str = "microsandbox";
114
115//--------------------------------------------------------------------------------------------------
116// Functions
117//--------------------------------------------------------------------------------------------------
118
119/// Derive a short, stable identifier from a path.
120///
121/// Used to build a POSIX shared-memory object name that depends only on the
122/// resolved home directory, so two processes pointed at the same `MSB_HOME`
123/// agree on a single registry without leaking the absolute path through a
124/// public name.
125pub fn stable_hash_path(path: &std::path::Path) -> String {
126    // Avoid pulling sha2 into the utils crate for one filename; a stable
127    // 64-bit FNV-1a over the OS-bytes is plenty for collision-resistance at
128    // this scale (one entry per concurrent MSB_HOME on a host).
129    let bytes = path.as_os_str().as_encoded_bytes();
130    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
131    for byte in bytes {
132        hash ^= u64::from(*byte);
133        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
134    }
135    format!("{hash:016x}")
136}
137
138/// Resolve the microsandbox home directory.
139///
140/// Order of resolution:
141/// 1. `MSB_HOME` env var (used as-is, no `.microsandbox` suffix appended)
142/// 2. `~/.microsandbox/` (i.e. `dirs::home_dir().join(BASE_DIR_NAME)`)
143/// 3. `./.microsandbox/` if no home is available
144///
145/// `MSB_HOME` lets CI and integration tests isolate microsandbox state
146/// (db, sandboxes, cache, logs) per process without disturbing other
147/// `$HOME`-rooted tooling.
148pub fn resolve_home() -> std::path::PathBuf {
149    if let Some(path) = std::env::var_os("MSB_HOME") {
150        return std::path::PathBuf::from(path);
151    }
152    dirs::home_dir()
153        .unwrap_or_else(|| std::path::PathBuf::from("."))
154        .join(BASE_DIR_NAME)
155}
156
157/// Returns the platform-specific libkrunfw filename.
158pub fn libkrunfw_filename(os: &str) -> String {
159    if os == "macos" {
160        format!("libkrunfw.{LIBKRUNFW_ABI}.dylib")
161    } else {
162        format!("libkrunfw.so.{LIBKRUNFW_VERSION}")
163    }
164}
165
166/// Returns the GitHub release download URL for libkrunfw.
167pub fn libkrunfw_download_url(version: &str, arch: &str, os: &str) -> String {
168    let (target_os, ext) = if os == "macos" {
169        ("darwin", "dylib")
170    } else {
171        ("linux", "so")
172    };
173
174    format!(
175        "https://github.com/{GITHUB_ORG}/{MICROSANDBOX_REPO}/releases/download/v{version}/libkrunfw-{target_os}-{arch}.{ext}"
176    )
177}
178
179/// Returns the GitHub release download URL for the agentd binary.
180pub fn agentd_download_url(version: &str, arch: &str) -> String {
181    format!(
182        "https://github.com/{GITHUB_ORG}/{MICROSANDBOX_REPO}/releases/download/v{version}/{AGENTD_BINARY}-{arch}"
183    )
184}
185
186/// Returns the GitHub release download URL for the microsandbox bundle tarball.
187pub fn bundle_download_url(version: &str, arch: &str, os: &str) -> String {
188    let target_os = if os == "macos" { "darwin" } else { "linux" };
189    format!(
190        "https://github.com/{GITHUB_ORG}/{MICROSANDBOX_REPO}/releases/download/v{version}/{MICROSANDBOX_REPO}-{target_os}-{arch}.tar.gz"
191    )
192}
193
194/// Returns an HTTP client configured for release asset downloads.
195#[cfg(feature = "http-client")]
196pub fn http_client() -> ureq::Agent {
197    ureq::Agent::config_builder()
198        .tls_config(
199            ureq::tls::TlsConfig::builder()
200                .root_certs(ureq::tls::RootCerts::PlatformVerifier)
201                .build(),
202        )
203        .build()
204        .new_agent()
205}
206
207/// Returns true when a user-provided text value should be interpreted as a
208/// local filesystem path rather than a named resource or OCI reference.
209pub fn looks_like_local_path_text(s: &str) -> bool {
210    s == "." || s == ".." || s.starts_with('/') || s.starts_with("./") || s.starts_with("../")
211}
212
213//--------------------------------------------------------------------------------------------------
214// Tests
215//--------------------------------------------------------------------------------------------------
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    /// `MSB_HOME` is honoured verbatim (no `.microsandbox` suffix appended)
222    /// so callers can isolate state per process without disturbing tooling
223    /// that reads `$HOME` (npm cache, ssh keys, etc.).
224    ///
225    /// Uses a unique env var per test process to avoid clashing with other
226    /// parallel tests that read `MSB_HOME`.
227    #[test]
228    fn test_resolve_home_respects_env_override() {
229        // SAFETY: This test sets a process-global env var. Vitest-style
230        // single-test isolation isn't available; rely on the test being
231        // the sole reader of `MSB_HOME` in this binary.
232        let custom = std::path::PathBuf::from("/tmp/msb-home-resolve-test-12345");
233        unsafe { std::env::set_var("MSB_HOME", &custom) };
234        let resolved = resolve_home();
235        unsafe { std::env::remove_var("MSB_HOME") };
236        assert_eq!(resolved, custom);
237    }
238}