Packages & languages
Packages
Section titled “Packages”packages: - name: zellij # the state identity; not conditional from: cargo # one manager, or a fallback list version: latest needs: [build-essential] only: linux path: ["{{ home }}/.cargo/bin"]from: may be a list, and the first manager that will exist wins:
from: [brew, apt, zypper]If none of them will exist on this machine, that is a plan-time error naming the package and what it asked for — not a failure twenty minutes into an apply.
name: is deliberately not conditional. It is the state-file identity, and
a name that varies by machine is an identity that varies by machine, at which
point uninstall stops working.
version: and what latest means
Section titled “version: and what latest means”latest means install if absent, never upgrade. That is what keeps plan
deterministic, offline and fast, and it is what makes the plan you reviewed the
plan that applies. A pinned version diffs against what is installed and maps to
each manager’s own syntax (jq=1.7 for apt, jq@1.7 for brew,
--version 1.7 for cargo).
needs:
Section titled “needs:”Build prerequisites nothing can infer. Nothing in from: cargo says a package
wants a C toolchain:
- name: build-essential from: apt only: linux- name: zellij from: cargo needs: [build-essential]A needs: naming a package that only: pruned simply drops the edge — the
prerequisite genuinely does not exist here. A needs: naming a package that
was never declared is an error.
script:
Section titled “script:”For a thing no manager packages. Tailscale’s installer registers an apt
repository and starts a daemon; neither is a from:.
packages: - name: tailscale only: linux script: | curl -fsSL https://tailscale.com/install.sh | shUse it instead of from:, never with it. Presence is the binary being on
PATH, so the script runs once and not again.
Bedouin cannot uninstall what a script installed, so it does not claim to
own it: dropping the entry forgets it rather than removing it. Everything else
— only:, needs:, rc:, aliases: — works as usual.
Languages
Section titled “Languages”languages: - name: rust version: "1.80" installer: rustup # optional -- see belowinstaller: is optional. Left out, a language installs with its own tool:
rust uses rustup, which is how Rust is meant to arrive and what
rustup component add and toolchain pinning expect. Anything else uses mise,
which fetches upstream builds too — the source, just not a first-party script.
A package with from: cargo and no rust language gets one added implicitly,
with a warning. The implication is stated, never silent.
Toolchains record their own bin directories — rustup records
~/.cargo/bin, mise its shims, Go ~/go/bin — which is what lets the cargo
step in the same run find cargo. Those directories also go into the generated
PATH file, so what a toolchain installs is on your PATH too, not just the
run’s. You never configure that.
Package managers
Section titled “Package managers”package_managers: macos: [brew] default: [apt, mise]You rarely need this block. Declaring installer: rustup or from: cargo
already declares that you need that manager, and it gets bootstrapped
whether or not it is listed here — listing it was once mandatory, and
forgetting meant a fresh machine ran rustup toolchain install against a
rustup nothing had installed. Use package_managers: to name a manager
nothing references yet, or to pin which one a bare from: should resolve to.
Bedouin bootstraps brew, mise, rustup and cargo if they are missing.
On Linux, brew’s bootstrap installs its own prerequisites first
(build-essential procps curl file git, plus unzip for casks): Homebrew
needs git to clone itself, and the package phase that would provide it runs
later than the manager phase.
It does not install apt, zypper or dnf — those are the distro’s, and one
that is merely referenced is never added implicitly for the same reason: a
package asking for a manager this machine lacks already errors by name. A
declared manager that cannot exist on this OS is dropped from the plan rather
than attempted.
What apply actually does
Section titled “What apply actually does”The order is the dependency order:
managers → languages → the shell's own package → packages → files → rc blocks → PATHThe shell is pulled ahead of the general package stage so the run that installs
zsh can write into ~/.zshrc.d. Within a stage, declaration order — the order
you can see.
Each step is spawned with an environment Bedouin builds: PATH from the bin
directories recorded in state, plus a minimal system base. Never your shell’s.