rto_exec/runtime_pins.rs
1// The pinned sandbox-runtime archives, and the host-platform selection.
2//
3// # Why this file is `include!`d rather than imported
4//
5// These pins are needed in two places that cannot share a crate graph: this
6// library, which provisions and verifies the archive, and `build.rs`, which
7// refuses to build `exec-boxlite` against anything else. A build script cannot
8// depend on the crate it builds, so the single source of truth is this file and
9// `build.rs` pulls it in with `include!`.
10//
11// That constrains what may appear here: **no `use`, no `crate::` paths, no
12// references to anything outside this file.** It must compile standalone.
13//
14// # What is pinned, and why it has to be
15//
16// `boxlite` does not build a hypervisor when it is compiled from crates.io. Its
17// three `-sys` crates each detect a published package (`.cargo_vcs_info.json`)
18// and disable themselves, and `libkrun-sys` excludes the sources they would
19// otherwise build. What actually runs is a prebuilt tarball that `boxlite`'s own
20// `build.rs` fetches with a bare `curl -fsSL`, `include_bytes!`s into the rlib,
21// and extracts and executes at run time.
22//
23// That fetch has **no expected digest of any kind** — searched for one four
24// ways (`expected|_SHA256|checksum|digest`; `sha256|integrity|signature|cosign`;
25// and two 64-hex-literal patterns over `build.rs` and `src/`), all NOT FOUND —
26// and its URL is overridable through `BOXLITE_RUNTIME_URL`. Two builds of the
27// same crate version can therefore embed different bytes, undetectably.
28//
29// Roteiro will not ship that. The digests below were computed from the real
30// v0.9.7 release assets, and are what makes the embedded runtime reproducible:
31// `roteiro security prefetch --allow-download` fetches and verifies the archive
32// against them, and `build.rs` then refuses to build unless `BOXLITE_RUNTIME_URL`
33// points at a local file whose bytes match. `boxlite`'s `curl` never reaches the
34// network, because the `file://` URL it is given is already on disk.
35//
36// Bump these together with the `boxlite` pin in `Cargo.toml`; a version skew is
37// caught by `build.rs` rather than discovered at run time.
38
39/// One platform's prebuilt sandbox-runtime archive.
40///
41/// The `target` names are `boxlite`'s own, from its `runtime_target()` — they
42/// are what appears in the release asset's filename, so they are the identifiers
43/// that can actually be checked against upstream.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct PinnedArchive {
46 /// The platform, as the upstream release names it.
47 pub target: &'static str,
48 /// Where the archive is published.
49 pub url: &'static str,
50 /// Lowercase hex SHA-256 of the archive. Verified before it is installed and
51 /// again before it is built against.
52 pub sha256: &'static str,
53 /// Its exact size. Redundant with the digest, and kept because a truncated
54 /// body is the common failure and "expected 26520984 bytes, got 1043" says
55 /// so far more clearly than two digests that differ.
56 pub bytes: u64,
57}
58
59/// The `boxlite` release these archives belong to.
60///
61/// Checked against `boxlite`'s own `CARGO_PKG_VERSION` at build time, so a
62/// dependency bump that forgets these pins fails the build instead of silently
63/// pairing a new library with an old runtime.
64pub const RUNTIME_VERSION: &str = "0.9.7";
65
66/// The asset id the archive is provisioned under.
67pub const RUNTIME_ASSET: &str = "boxlite-runtime";
68
69/// The file name the archive is installed as.
70pub const RUNTIME_FILE: &str = "boxlite-runtime.tar.gz";
71
72/// Every platform Roteiro pins a sandbox runtime for.
73///
74/// These are the three `boxlite` publishes. A host outside this list cannot
75/// build `exec-boxlite`, and is told so by name rather than by a link error.
76pub const RUNTIME_ARCHIVES: &[PinnedArchive] = &[
77 PinnedArchive {
78 target: "darwin-arm64",
79 url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.9.7/boxlite-runtime-v0.9.7-darwin-arm64.tar.gz",
80 sha256: "7f64529978cd2af420411ddfd4cc3b5799ca20234d90346c887cb596d52f8d4e",
81 bytes: 26_520_984,
82 },
83 PinnedArchive {
84 target: "linux-x64-gnu",
85 url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.9.7/boxlite-runtime-v0.9.7-linux-x64-gnu.tar.gz",
86 sha256: "9ae495f55d363e6af04640ab55025ac80b4bf4762e38fa0b8ac80c7604e3148c",
87 bytes: 24_957_005,
88 },
89 PinnedArchive {
90 target: "linux-arm64-gnu",
91 url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.9.7/boxlite-runtime-v0.9.7-linux-arm64-gnu.tar.gz",
92 sha256: "78e978d6398d5a78dc76d675941cb05287e8c70b1b647e98a479058a9652be28",
93 bytes: 28_737_386,
94 },
95];
96
97/// The upstream target name for an `(os, arch)` pair, or `None` for a platform
98/// with no published runtime.
99///
100/// This mirrors `boxlite`'s own `runtime_target()`. It is spelled out rather
101/// than derived so that a platform upstream adds later is a deliberate pin here,
102/// not an automatic one.
103#[must_use]
104pub fn runtime_target(os: &str, arch: &str) -> Option<&'static str> {
105 match (os, arch) {
106 ("macos", "aarch64") => Some("darwin-arm64"),
107 ("linux", "x86_64") => Some("linux-x64-gnu"),
108 ("linux", "aarch64") => Some("linux-arm64-gnu"),
109 _ => None,
110 }
111}
112
113/// The pinned archive for an `(os, arch)` pair.
114#[must_use]
115pub fn archive_for(os: &str, arch: &str) -> Option<&'static PinnedArchive> {
116 let target = runtime_target(os, arch)?;
117 let mut index = 0;
118 // A plain loop rather than an iterator: this file is `include!`d into a
119 // build script, where keeping the surface to the language core is the point.
120 while index < RUNTIME_ARCHIVES.len() {
121 if str_eq(RUNTIME_ARCHIVES[index].target, target) {
122 return Some(&RUNTIME_ARCHIVES[index]);
123 }
124 index += 1;
125 }
126 None
127}
128
129/// Byte equality for two `&str`, usable in the `const`-flavoured context above.
130fn str_eq(a: &str, b: &str) -> bool {
131 a.as_bytes() == b.as_bytes()
132}