Table of Contents
Build and Run
Build & iterate
There is no host Rust/C toolchain (deliberate — no rustup, and the old
dev toolbox is retired, 2026-09). Everything builds in a rust:bookworm
container via rootless podman — glibc 2.36 baseline, so the binaries run on
any newer host (Fedora atomic included):
cd ~/Code/bubblebox
podman run --rm -v "$PWD":/src -v "$HOME/.cache/bubblebox/portable-cargo":/cargo \
-w /src -e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/src/target-bookworm \
docker.io/library/rust:bookworm cargo build --release --workspace
# unit tests: the same invocation with `cargo test --workspace`
scripts/build-portable.shwraps exactly this (plus staging + tarball for distribution). It passes--userns=keep-id, which works from real login shells; from non-login contexts (agent sandboxes, some CI runners — crun fails withreadlink '') drop the flag: rootless podman's default mapping already maps container-root to the invoking user, so artifacts come out correctly owned either way.- Binaries land side-by-side in
target-bookworm/release/{bubblebox, bubblebox-fuse}; the engine resolves a FUSE server next to the binary, so dev runs need noBUBBLEBOX_FUSE_BIN. fuse-mountbuilds in the same container — thecomposefs-fusecrate is pure Rust; the old "no composefs C headers in the toolbox" constraint is gone. (default-members = ["cli"]still keeps a barecargo buildCLI-only.)- Warm rebuilds are fast (~25 s); the cargo registry cache persists under
~/.cache/bubblebox/portable-cargo. cargo test --releasedoes not relinkrelease/bubblebox— run an explicitcargo build --releasewhen the binary itself is the deliverable.- After engine changes the bee nightly CI should exercise, refresh its pair:
cp target-bookworm/release/bubblebox{,-fuse} ~/.local/share/bubblebox-ci/bin/. - Sanity-check the host with
bubblebox doctorbefore blaming the code.
The release binary is what ~/.local/bin wrappers and cjust invoke.
Wrappers exec the path, so rebuilds are picked up automatically — no
re-export unless the path changes. Portable distribution tarballs come
from scripts/build-portable.sh (consumed by tools/nightly.sh on the CI
runner as ENGINE_TAR).
bubblebox init rebuilds the FUSE server via podman run rust:bookworm cargo build --release (no host toolchain) — the one bootstrap step for consumer
installs. Run it after changing fuse-mount/ there.
Global verbosity (the chatter from log.rs's info!/warn! macros): by
default a normal run is silent on info — it prints only the command's result
line, warn!s (misconfig/footgun — always shown), and fatal errors. To see
progress/informational output (network chatter, remount notices, gc's "roots:…"
context and orphan path list), pass -v/--verbose or set BUBBLEBOX_VERBOSE=1.
-q/--quiet forces info off and wins over -v/BUBBLEBOX_VERBOSE (kept as
an explicit "be quiet" knob). Verbosity never affects a tool's own output under
run (bwrap passthrough). Note gc dropped its own --verbose subcommand flag
in favor of this global one.
Running
BB=~/Code/bubblebox/target/release/bubblebox
$BB init # one-time: build the FUSE server
$BB build rg # build a package -> descriptor (Containerfile resolved from the registered source)
$BB import docker.io/library/alpine:3.20 --binary /bin/sh # turn any OCI image into a package (name/binary derived if omitted)
$BB mount rg # mount its composefs rootfs (rootless FUSE)
$BB run rg -- --version # run under bwrap (auto-mounts if needed)
$BB export rg # write ~/.local/bin/rg wrapper
$BB shell --base rust-dev # enter a reusable base-shell devshell
$BB shell # enter the project devshell (./bubblebox.toml)
run resolves an entrypoint (TOML) or, if no TOML exists, synthesizes a default
{ package = <name> }. Multi-package entrypoints (compose = [...]) compose at
runtime via overlay-in-userns (see run.rs::run_composed / compose_inner).
Sources & caches (specs vs binaries):
$BB source add personal ~/Code/bubblebox-pkgs # local source (specs)
$BB source add community git+https://git.example.com/chan # git source (specs)
$BB source update community # git pull --ff-only
$BB source list # registered sources
$BB source packages # what the sources offer (metadata cards)
$BB source search ripgrep # name+description match, both --json
$BB cache add mycache https://cache.example.com # binary cache
$BB cache list # registered + source channel caches
$BB install rg # pull + deps + default entrypoint + export
$BB install https://cache.example.com/rg --no-export # full-URL form (self-contained)
$BB install mycache:rg # registered-nickname form
$BB config cat rg # show effective (default ⊕ override) entrypoint
$BB config edit rg # seed an override from the default, open $EDITOR
$BB list # current generation's packages (what's applied)
$BB info rg # per-pkg state: digest, origin, mount, footprint
$BB push rg --entrypoint ~/Code/bubblebox-pkgs/rg/entrypoint.toml # publish the default entrypoint
A source is a directory of <pkg>/Containerfile + <pkg>/entrypoint.toml
(the default entrypoint, co-located with the build; a "virtual entrypoint" that
reuses another package's image ships an entrypoint.toml with no Containerfile)
(+ optional channel.toml declaring substituent caches). It's either a local
path (/path, ~/..., file:///path) or a git clone (git+https://...);
source add infers which from the location. build resolves Containerfiles
from registered sources; run/config resolve default entrypoints from them.
A cache serves binaries (blobs/descriptor/index/default-entrypoint) over
HTTPS/S3. A source's channel.toml may declare caches that participate in
pull/install without being registered separately. install fetches the
package's default entrypoint into remote-defaults/; provenance (origin =
the cache/source) is recorded by the declarative layer — apply writes it
into the generation manifest, which is the single "what I have" record
(installed/ is RETIRED; imperative build/install packages are GC-rooted
by their pkg bindings until they enter a generation).
The user's override deep-merges over the default at run time (rules in
Override layering & deep-merge below). Editing
an override can't affect binary caching (the entrypoint is runtime metadata,
merged in memory; the store is content-addressed by the OCI image).
Env vars (all optional; defaults shown):
BUBBLEBOX_DIR(/Code/bubblebox),/.local/share/bubblebox),BUBBLEBOX_DATA_DIR(BUBBLEBOX_RUN_DIR($XDG_RUNTIME_DIR/bubblebox),BUBBLEBOX_CONFIG_DIR(~/.config/bubblebox — user overrides + sources.toml + caches.toml),BUBBLEBOX_FUSE_BIN.BUBBLEBOX_CACHE_URLis honored as a singledefaultcache only when nosources.tomlAND nocaches.tomlexist (legacy back-compat).Common::loadexports the resolvedBUBBLEBOX_*vars back to the environment so entrypoint TOML$VARexpansion and devshell init scripts see them (the old bash got this free by sourcingbubblebox-common).