rdebootstrap
rdebootstrap is a manifest-driven Debian/Ubuntu bootstrapper written in Rust.
It resolves packages from user-defined APT repositories, locks the full dependency
graph, stages arbitrary artifacts, and builds a root filesystem tree inside a
sandbox so that maintainer scripts run in a controlled environment. The same
engine is exposed as the debrepo library for embedding in other tooling.
The project uses itself to build locked Debian and Ubuntu trees, and then uses those trees to build its own packages for those distributions.
Highlights
- Declarative input --
Manifest.tomllists archives, optional imports, specs, staged files, local.debs, and metadata whileManifest.<arch>.lockcaptures the fully resolved set for reproducible builds. - Deterministic resolution -- Release and Packages files are fetched with GPG verification, optional snapshot pinning, and a solver that locks each spec before anything is installed.
- Sandboxed builds --
buildexpands packages inside an isolated helper namespace or inside apodmancontainer; run as root for production ownership or unprivileged while iterating. - Rich spec tooling -- add/drop requirements and constraints per spec, stage local files or HTTP artifacts, include local packages that ship alongside the manifest, and reuse selected parent specs from another locked manifest.
- Fast, resumable downloads -- concurrent fetcher (
-n/--downloads) backed by a shared cache and optional transport relaxations for air-gapped or test environments.
Requirements
- Linux host with user namespaces enabled (required by the sandbox helper).
- Rust toolchain >= 1.89 (
rustup toolchain install 1.89.0). - A handful of libraries are required; see [debian-build.toml] for the list.
Installation
# clone this repo
# or install into ~/.cargo/bin
# nix users
The resulting binary lives at target/release/rdebootstrap (or in
~/.cargo/bin when installed).
Typical Workflow
- Create a manifest
rdebootstrap init debian --package ca-certificates --package vim
or bootstrap from another locked manifest:
rdebootstrap init --import ../system/Manifest.toml --spec base --package vim - Add requirements to a spec
rdebootstrap archive add https://mirror.example/debian --suite bookworm,bookworm-updates --components main,contrib
rdebootstrap require --spec desktop openssh-server network-manager
rdebootstrap forbid --spec desktop 'systemd-hwe (= 255.5-1)' - Reuse a locked base manifest (optional)
rdebootstrap import ../system/Manifest.toml --spec base --spec bootable-base - Update and lock
rdebootstrap update --snapshot 20241007T030925Z(or--snapshot today)
This downloads Release/Packages data, solves the specs, and writesManifest.<arch>.lock. - Build a filesystem tree
rdebootstrap build --spec desktop --path ./out
The resulting tree may be used directly with podman:podman run --rm -it --systemd=always --rootfs "$(pwd)/out" bash -l
build unpacks packages into the target directory, stages artifacts, and runs
maintainer scripts in the sandbox so the host stays clean.
Manifest Layout
Manifest.toml sits at the project root unless --manifest <path> is supplied.
The lock file is always written in the same directory as the selected
manifest. For a manifest named <name>.toml, the lock file path is
<name>.<arch>.lock.
A small example with an imported base spec:
[]
= "../system/Manifest.toml"
= "blake3-..."
= ["base"]
[[]]
= "https://security.debian.org/debian-security/"
= "https://snapshot.debian.org/archive/debian-security/@SNAPSHOTID@/"
= ["trixie-security"]
= ["main"]
[[]]
= "target/debian/mytool_0.1.0_amd64.deb"
= "sha256-..."
[]
= "text"
= "/etc/motd"
= "hello from rdebootstrap\n"
[]
= "base"
= ["ca-certificates", "openssh-server", "mytool"]
= ["motd"]
Key sections:
[[archive]]-- APT repositories with suites, components, optional snapshot templates, trusted keys, and priorities.[import]-- Reuse archives, local packages, and selected named parent specs from another manifest. Imported parent specs keep their own staged artifact references.pathandhashare required;specsis optional and only needed when exporting imported parent specs for downstreamextends.[[local]]-- Local.debfiles copied into the cache and treated like repo packages.[artifact."<name>"]-- Files or URLs to drop into the tree during staging.[spec]and[spec.<name>]-- Package requirements/constraints, staged artifacts, build-time environment/script, and metadata per spec. Specs can inherit from each other viaextends. Setmeta = ["apt-lists:stage"]on a spec when you want staging/build output to also includemanifest.sourcesand downloaded APT list files.
rdebootstrap import writes [import], pins the imported manifest bytes in
hash, and validates the selected named specs. Imported archives are prepended
to the effective archive list, imported [[local]] entries join the effective
package universe, and inherited stage entries from imported parent specs keep
resolving their own imported artifacts. Downstream-local stage entries still
only resolve artifacts defined in the downstream manifest. Imported local paths
stay anchored to the imported manifest directory.
The downstream lock keeps only downstream-local archives and locals, plus
an imported-universe fingerprint for imported lock state. rdebootstrap update refreshes stale import metadata, re-solves specs when the imported
manifest or imported lock changed, and build refuses to run if the resulting
lock is missing or stale.
Cache and Fetching
- By default caching is enabled and lives in
XDG_CACHE_HOME/rdebootstrapor~/.cache/rdebootstrapifXDG_CACHE_HOMEis unset. - Use
--cache-dir <dir>to point elsewhere or--no-cacheto disable it entirely. - Local artifacts are hashed relative to the manifest directory, so keeping manifests and artifacts in the same repository ensures stable paths.
- Content integrity is enforced via the hashes recorded in the lock file; disabling cache does not bypass verification.
Artifacts and Staging
Artifacts are declared at the top level as [artifact."<name>"] and referenced
from specs via stage = ["<name>", ...]. Use rdebootstrap artifact add to
define them and rdebootstrap stage to attach them to specs.
APT source metadata is not staged by default. rdebootstrap is commonly used
to produce OCI images and other read-only filesystem trees where apt-get update is not expected to work, so staged roots omit manifest.sources and
/var/lib/apt/lists unless the spec opts in with meta = ["apt-lists:stage"]
or rdebootstrap spec meta set apt-lists stage.
- Artifact
typeis one of:file,tar,dir,text. - Hashes are serialized in SRI form:
<algo>-<base64>(for exampleblake3-...,sha256-...). - When
rdebootstrapcomputes an artifact hash (for example viaartifact add), it usesblake3. TARGET_PATHis treated as an absolute path inside the target filesystem (non-absolute values are auto-prefixed with/during staging).{file|text}.ext /path/target→/path/target{file|text}.ext /path/target/→/path/target/file.ext
file.tar /path/target(/?)→ extracted under/path/targetdir /path/target(/?)→ copied under/path/target- Filename resolution for
{file|text}artifacts happens during staging; manifests keep the rawtargetvalue. - Auto-unpack: tar archives and compressed files (
.gz,.xz,.bz2,.zst,.zstd) are unpacked by default; use--no-unpackto keep them as-is. - Safety: tar unpacking rejects absolute paths,
..traversal, and special entries like device nodes. - Inline text artifacts (
type = "text") embed atextvalue in the manifest and write it totargetduring staging.rdebootstrap artifact add @filecreates a text artifact from a UTF-8 file (target path required).
Build Environment and Scripts
Specs can set:
build-env-- key/value environment variables applied to bothdpkg --configureandbuild-script.build-script-- a bash script executed after package configuration. Scripts fromextendsare executed in order (base -> derived).
Use rdebootstrap edit env / rdebootstrap edit script to edit these fields.
rdebootstrap build supports --executor sandbox (default) and
--executor podman. The executor matters mainly for rootless runs: sandbox
uses the built-in helper, while podman runs configuration inside
podman run --rootfs ... (which may require a working rootless podman
environment such as a valid XDG runtime directory).
CLI Tour
init-- bootstrap a manifest from vendor presets (debian,ubuntu,devuan), explicit archives, or--import <path>from another locked manifest.import-- add or replace[import]using another already-locked manifest and export selected named parent specs.edit-- edit the manifest (rdebootstrap edit) or spec metadata (edit env,edit script).archive add,deb add-- append repositories or register a local.deb.require/forbid-- add requirements or version constraints to a spec.remove-- remove requirements or constraints (dropremains an alias).artifact add-- define, add, or remove staged artifacts.spec artifact add/remove-- add or remove an artifact from the spec.update-- refresh metadata, solve dependencies, and rewrite the lock file (supports--snapshot;--localsrefreshes local packages, local artifacts, and stored import fingerprints when[import]is present).list,search,spec,package,source-- inspect resolved specs and package/source metadata.build-- expand a spec into a directory, running maintainer scripts within the sandbox helper.
Authentication
-a/--authselects the auth source: omit for optionalauth.tomlnext to the manifest, usefile:/path/to/auth.toml(or just a path), orvault:<mount>/<path>to read secrets from Vault.
Do not commit auth.toml to version control.
- Auth file (
auth.toml) supports per-host entries:
[[]]
= "deb.example.com"
= "user"
= "inline" # or password.env / password.cmd
[[]]
= "deb.other.com"
= "token-string"
[[]]
= "deb.tls.com"
= "relative/cert.pem" # relative paths are resolved from the auth file directory
= "relative/key.pem"
# password/env/cmd/file are also supported for passwords
password.env reads an env var, password.cmd runs a shell command (cwd = auth
file dir), and password.file/password.path load file content. Tokens and
cert/key accept the same source forms.
- Vault secrets: pass
--auth vault:<mount>/<path>(for examplevault:secret/data/repos). Each host lives at<mount>/<path>/<host>and contains JSON like:
VAULT_ADDR, VAULT_TOKEN, VAULT_CACERT, and VAULT_SKIP_VERIFY influence
the Vault client.
Global flags of note:
--manifest <path>selects an alternate manifest.--arch <arch>switches the target architecture (default: host arch).-n/--downloads <N>controls concurrent downloads (default: 20).--cache-dir/--no-cacheadjust caching.-k/--insecuredisables TLS certificate and hostname verification (not recommended).
Verification controls (scoped):
--no-verify(oninit,add archive,update) skips InRelease signature verification (not recommended).-K/--allow-insecure(on archive definitions forinitandadd archive, orallow-insecure = truein the manifest) fetchesReleaseinstead ofInRelease.
Run rdebootstrap <command> --help or man rdebootstrap for exhaustive usage
information.
Known Rough Edges
- Staging/unpacking happens concurrently; this makes
rdebootstrapincompatible withdpkg-divertworkflows. -q/--quietand-d/--debugcurrently affect onlyrdebootstrapoutput, not the output ofdpkg --configureorbuild-script.
A Necessary Explanation
The debrepo library is a foundation for an in-house CI/CD system (which is
still too ugly to release), and that explains certain design choices. For
example, debrepo requires libgpgme-dev and libcurl instead of pure-Rust
alternatives because that system uses them anyway, and having two GPG or HTTP
client implementations in the same binary seemed a bit much.
For the same reason, the library may feel a bit over-engineered (see the
StagingFileSystem trait, for example, or the rather corporatish repo
authentication). All these excesses are absolutely necessary.
Development
cargo fmt,cargo clippy, andcargo testkeep the codebase healthy.cargo bench -p debrepo version(and other benches underbenches/) run Criterion benchmarks.- The crate can also be embedded directly by depending on
debrepo.
License
Licensed under the MIT License.