Skip to content

growler/rdebootstrap.rs

Repository files navigation

rdebootstrap

rdebootstrap is a manifest-driven Debian/Ubuntu bootstrapper written in Rust. It resolves packages from user-defined APT archives, locks the full dependency graph, stages arbitrary artifacts, and builds a root filesystem tree inside a sandbox so maintainer scripts run in a controlled environment. The same engine is exposed as the debrepo library for embedding in other tooling.

Highlights

  • Declarative inputManifest.toml lists archives, optional imports, specs, staged files, local .debs, and metadata while Manifest.<arch>.lock captures 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 buildsbuild expands packages inside an isolated helper namespace; 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).
  • System packages needed by cargo plus gpgme/libgpg-error for Release verification.
  • Optional sudo when you need ownership preserved inside the target rootfs.

Installation

# clone this repo
cargo build --release

# or install into ~/.cargo/bin
cargo install --path .

# nix users
nix build .#rdebootstrap

The resulting binary lives at target/release/rdebootstrap (or in ~/.cargo/bin when installed).

Typical Workflow

  1. 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
  2. Iterate on specs
    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)'
  3. Reuse a locked base manifest (optional)
    rdebootstrap import ../system/Manifest.toml --spec base --spec bootable-base
  4. Update and lock
    rdebootstrap update --snapshot 20241007T030925Z (or --snapshot now)
    This downloads Release/Packages data, solves the specs, and writes Manifest.<arch>.lock.
  5. Build a filesystem tree
    sudo rdebootstrap build --spec desktop --path ./out
    The resulting tree may be used directly with podman (requires the full path because podman enters its own mount namespace): 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:

[import]
path = "../system/Manifest.toml"
hash = "blake3-..."
specs = ["base"]

[[archive]]
url = "https://ftp.debian.org/debian/"
suites = ["trixie"]
components = ["main"]
snapshots = "https://snapshot.debian.org/archive/debian/@SNAPSHOTID@/"

[artifact."motd"]
type = "text"
target = "/etc/motd"
text = "hello from rdebootstrap\n"

[spec.frontend]
extends = "base"
include = ["ca-certificates", "openssh-server"]
stage = ["motd"]

[[local]]
path = "target/debian/mytool_0.1.0_amd64.deb"
hash = "sha256-..."

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. path and hash are required when present; specs is optional and only needed when exporting imported parent specs for downstream extends.
  • [[local]] — Local .deb files 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 via extends.

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/rdebootstrap or ~/.cache/rdebootstrap if XDG_CACHE_HOME is unset.
  • Use --cache-dir <dir> to point elsewhere or --no-cache to 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.

  • Artifact type is one of: file, tar, dir, text.
  • Hashes are serialized in SRI form: <algo>-<base64> (for example blake3-..., sha256-...).
  • When rdebootstrap computes an artifact hash (for example via artifact add), it uses blake3.
  • TARGET_PATH is 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/target
  • dir /path/target(/?) → copied under /path/target
  • Filename resolution for {file|text} artifacts happens during staging; manifests keep the raw target value.
  • Auto-unpack: tar archives and compressed files (.gz, .xz, .bz2, .zst, .zstd) are unpacked by default; use --no-unpack to keep them as-is.
  • Safety: tar unpacking rejects absolute paths, .. traversal, and special entries like device nodes.
  • Inline text artifacts (type = "text") embed a text value in the manifest and write it to target during staging. rdebootstrap artifact add @file creates 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 both dpkg --configure and build-script.
  • build-script — a bash script executed after package configuration. Scripts from extends are 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 (include / exclude remain aliases).
  • remove – remove requirements or constraints (drop remains an alias).
  • artifact add, stage, unstage – define, add, or remove staged artifacts.
  • update – refresh metadata, solve dependencies, and rewrite the lock file (supports --snapshot, --locals refreshes local packages and local artifacts, and refreshes 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/--auth selects the auth source: omit for optional auth.toml next to the manifest, use file:/path/to/auth.toml (or just a path), or vault:<mount>/<path> to read secrets from Vault.

Do not commit auth.toml to version control.

  • Auth file (auth.toml) supports per-host entries:
[[auth]]
host = "deb.example.com"
login = "user"
password = "inline"                # or password.env / password.cmd

[[auth]]
host = "deb.other.com"
token = "token-string"

[[auth]]
host = "deb.tls.com"
cert = "relative/cert.pem"         # relative paths are resolved from the auth file directory
key = "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 example vault:secret/data/repos). Each host lives at <mount>/<path>/<host> and contains JSON like:
{ "type": "basic", "login": "user", "password": "secret" }
{ "type": "token", "token": "token-string" }
{ "type": "mtls", "cert": "PEM string", "key": "PEM key (decrypted)" }

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-cache adjust caching.
  • -k/--insecure disables TLS certificate and hostname verification (not recommended).

Verification controls (scoped):

  • --no-verify (on init, add archive, update) skips InRelease signature verification (not recommended).
  • -K/--allow-insecure (on archive definitions for init and add archive, or allow-insecure = true in the manifest) fetches Release instead of InRelease.

Run rdebootstrap <command> --help for exhaustive usage information.

Known Rough Edges

  • Staging/unpacking happens concurrently; this makes rdebootstrap incompatible with dpkg-divert workflows.
  • -q/--quiet and -d/--debug currently affect only rdebootstrap output, not the output of dpkg --configure or build-script.

Development

  • cargo fmt, cargo clippy --all-targets, and cargo test keep the codebase healthy.
  • cargo bench -p debrepo version (and other benches under benches/) run Criterion benchmarks.
  • The crate can also be embedded directly by depending on debrepo and driving Manifest/HostCache from your own host tooling.

License

Licensed under the MIT License.

About

A Rust library to work with Debian repositories and a tool to boostrap debian trees

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages