| 1 |
# The OxygenFlow build image |
| 2 |
|
| 3 |
A root file system for Rust and Node jobs: |
| 4 |
|
| 5 |
- Rust 1.97.1 with rustfmt and clippy. |
| 6 |
- Node 24.21.0, checked against its published SHA-256. |
| 7 |
- git, make and pkg-config. |
| 8 |
- Coverage: llvm-tools and cargo-llvm-cov 0.9.1, so a job can write an LCOV report for the hub's Coverage tab. |
| 9 |
- Release cross-building: the Windows, Linux arm64 and riscv64, and macOS targets, zig 0.16.0 (checked against its SHA-256), cargo-zigbuild and MinGW, with zip, so `scripts/dist.sh` runs in a job. |
| 10 |
- Every crate that oxygen, oxygenhub and oxygenflow use, vendored in `/opt/vendor`. |
| 11 |
|
| 12 |
OxygenFlow jobs have no network (`network "isolated"`). So cargo is pointed at the vendored crates by `/.cargo/config.toml`, which cargo reads for any directory under `/`, including `/src`. `CARGO_HOME` stays at its default, `$HOME/.cargo`, which is under `/tmp` and writable. The toolchain's binaries are linked into `/usr/local/bin`, because the guest's `PATH` is fixed and nothing at run time needs rustup. |
| 13 |
|
| 14 |
## Building and importing |
| 15 |
|
| 16 |
``` |
| 17 |
images/rust/build.sh /path/to/oxygenflow-build.tar |
| 18 |
scp /path/to/oxygenflow-build.tar claude@earth:/home/claude/flow/dl/ |
| 19 |
ssh claude@earth 'cd ~/flow && FLOW_HOME=~/flow bin/flow rootfs import dl/oxygenflow-build.tar' |
| 20 |
``` |
| 21 |
|
| 22 |
`build.sh` vendors the crates of the three workspaces next to this repository with `cargo vendor --locked`, then builds the image. Docker is used only on the machine that builds it, as a way to assemble a file system: the image is exported as a plain tarball. On the runner, `flow rootfs import` makes an ext4 image pinned by BLAKE3, which Firecracker boots read-only. |
| 23 |
|
| 24 |
Rebuild and re-import whenever a `Cargo.lock` of those projects changes, then pin the new hash in the pipelines that use the image. A crate missing from the image fails the job with cargo's "no matching package" error, not with a network error. |
| 25 |
|
| 26 |
## Using it |
| 27 |
|
| 28 |
```kdl |
| 29 |
sandbox "rust" { |
| 30 |
runtime "microvm" |
| 31 |
rootfs "cas://blob/<hash printed by flow rootfs import>" |
| 32 |
memory-limit "8GiB" |
| 33 |
cpu-limit 4 |
| 34 |
network "loopback" // tests that serve on 127.0.0.1 need it; nothing leaves the guest |
| 35 |
} |
| 36 |
|
| 37 |
job "test" { |
| 38 |
sandbox "rust" |
| 39 |
mount { |
| 40 |
source "ephemeral://disk" |
| 41 |
target "/src/target" |
| 42 |
size "24GiB" |
| 43 |
} |
| 44 |
env { |
| 45 |
CARGO_INCREMENTAL "0" |
| 46 |
} |
| 47 |
exec "cargo" "test" "--workspace" |
| 48 |
} |
| 49 |
``` |
| 50 |
|
| 51 |
Measured on Earth (4 cores, the job given all of them and 8 GiB): oxygen's `cargo fmt --check`, `clippy -D warnings` and `cargo test --workspace` from a cold build take 135 s. |
| 52 |
|
| 53 |
The build tree goes on an `ephemeral://disk` mount. The workspace itself has only 512 MiB to spare, and a `tmpfs` would take the build tree out of the job's memory. |