Skip to content

install-deps

install-deps installs system packages from a declarative TOML file. It detects the running OS and package manager automatically, installs all dependency groups by default, and runs ephemerally via jbx — no local installation required.

jbx install-deps                  # install all groups (default)
jbx install-deps -g runtime       # runtime packages only
jbx install-deps -n               # dry run — print commands, don't run

See inspect to query what versions are currently installed.


File auto-discovery

When no file argument is given, install-deps searches the current working directory for a deps file in this order:

Priority Source When to use
1 explicit path arg jbx install-deps myfile.toml
2 jb-deps.toml standalone deps-only file
3 bootstrap.toml combined tool + deps manifest (recommended)
4 stdin piped input, remote URLs

Recommended: put deps directly in bootstrap.toml alongside your tool declarations — one file covers everything jb needs.

# bootstrap.toml
[project]
name    = "my_project"
version = "0.1.0"

[tools.install-deps]
source = "just-bashit:install-deps"
groups = ["runtime", "dev"]       # opt-out of docs group

[tools.inspect]
source = "just-bashit:inspect"
groups = ["runtime", "dev"]

[runtime.apt]
packages = ["libzmq3-dev"]

[runtime.pacman]
packages = ["zeromq"]

[dev.apt]
packages = ["build-essential", "cmake"]

[dev.pacman]
packages = ["base-devel", "cmake"]

[docs.apt]
packages = ["doxygen", "graphviz"]

[docs.pacman]
packages = ["doxygen", "graphviz"]

Use a standalone jb-deps.toml only when the project doesn't have a bootstrap.toml or you need to keep deps separate.


The deps.toml format

Dependencies are declared using dotted TOML section headers of the form [GROUP.PACKAGE_MANAGER]. Each section has either a packages array or a cmd escape hatch.

[runtime.apt]
packages = [
    "libzmq3-dev",
    "libfftw3-dev",
]

[runtime.pacman]
packages = ["zeromq", "fftw"]

[dev.apt]
packages = [
    "build-essential",
    "cmake",
    "python3-dev",
]

[dev.pacman]
packages = ["base-devel", "cmake", "python"]

Groups (runtime, dev, or any name you choose) let you install only what a given context needs. You can have as many groups as your project requires — test, ci, docs, etc.

Supported package managers:

Section key Package manager Platform
apt apt-get Debian, Ubuntu
pacman pacman Arch, CachyOS, Manjaro
brew Homebrew macOS
dnf dnf Fedora, RHEL, Rocky, Alma
zypper zypper openSUSE
apk apk Alpine
msys2 pacman (UCRT64) Windows / MSYS2

Both multiline and inline array syntax are supported:

# multiline
[runtime.apt]
packages = [
    "cmake",
    "libzmq3-dev",
]

# inline
[runtime.apt]
packages = ["cmake", "libzmq3-dev"]

Version pinning

Package strings are passed verbatim to the package manager. Use the PM's native version syntax to pin when needed:

[runtime.apt]
packages = ["libzmq3-dev=4.3.4-1"]   # apt: pkg=version

[runtime.dnf]
packages = ["zeromq-devel-4.3.4"]    # dnf: pkg-version

pacman has no version-pin syntax

pacman -S pkg always installs the current repo version. Use cmd (below) to install directly from the package cache.


Custom commands (cmd)

When packages isn't expressive enough, replace the entire install command for a section with cmd. The array is executed verbatim; packages is ignored for that group/section:

# pin via package cache on Arch. No "sudo" element: cmd is run verbatim,
# so hardcoding it here breaks the same file in a root container.
[runtime.pacman]
cmd = ["pacman", "-U", "--noconfirm",
       "/var/cache/pacman/pkg/zeromq-4.3.5-3-x86_64.pkg.tar.zst"]

# post-install hook on Debian
[runtime.apt]
cmd = ["bash", "-c", "apt-get install -y libzmq3-dev && ldconfig"]

# install from a private registry
[internal.brew]
cmd = ["brew", "install", "--HEAD", "my-org/tap/mylib"]

cmd and packages can coexist across different sections of the same group — e.g. [runtime.pacman] uses cmd while [runtime.apt] uses packages.

Tip

jbx inspect notes cmd sections in jb.versions but cannot query their installed versions. Use the output to confirm the command ran.


Default groups

By default install-deps installs all groups defined in the deps file. This matches the uv sync convention — zero config, everything included.

To restrict which groups are installed when no -g flag is given, declare groups under [tools.install-deps] in bootstrap.toml:

[tools.install-deps]
source = "just-bashit:install-deps"
groups = ["runtime", "dev"]   # docs and test groups excluded by default

The -g flag always overrides this, regardless of the toml setting.


Installing dependencies

All groups (default)

jbx install-deps

Installs every group in the file (or the groups listed in [tools.install-deps].groups if set).

Specific groups

jbx install-deps -g runtime
jbx install-deps -g dev
jbx install-deps -g runtime,dev,docs

From stdin

curl -fsSL https://example.com/bootstrap.toml | jbx install-deps

Options

Flag Long form Description
-h --help Show help and exit
-n --dry-run Print commands without executing
-v --verbose Print section, groups, and packages before acting
-s SECTION --section SECTION Override auto-detected package manager
-g GROUP --groups GROUP Comma-separated groups to install (overrides all defaults)
--no-sudo Never prefix sudo (root containers, images without it)
--sudo Always prefix sudo, even when already root
--proxy URL Proxy for package downloads; overrides the environment
--template [PATH] Write scaffold deps.toml to PATH, or stdout if omitted

Privileges — one file for the workstation and for CI

install-deps derives whether a sudo prefix is needed rather than assuming one. A command is prefixed only when all three hold:

  1. the package manager needs root (brew never does),
  2. the caller is not already root, and
  3. sudo resolves on PATH.

That is what lets a single bootstrap.toml drive both a developer's machine and CI. A container image runs as root and usually has no sudo package at all, so the same invocation works in both places with no flag:

# .github/workflows/ci.yml — inside a debian:latest container
- name: Bootstrap checkout prerequisites
  run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git

- uses: actions/checkout@v7

- name: Install dependencies (bootstrap.toml)
  run: bash src/just_bashit/install-deps.sh

Only git (and a TLS trust store, on images that ship none) is installed inline — enough for actions/checkout. Everything else comes from bootstrap.toml, so the dependency list exists exactly once.

Force the decision either way when the derivation cannot see the whole picture — an unprivileged user whose sudo lives outside PATH, or a rootless container that must not escalate:

jbx install-deps --no-sudo   # never escalate
jbx install-deps --sudo      # always escalate

If you are not root and sudo is not found, the command runs unprivileged and the package manager reports the permission failure itself, after a warning on stderr. Nothing else is tried — guessing at another escalation tool would only hide the real cause.

cmd arrays are run verbatim

The derivation applies to the packages form only. A cmd array is executed exactly as written, so a hardcoded "sudo" in one will fail in a root container that has no sudo binary. Leave it out and the command works in both places:

[pinned.apt]
cmd = ["apt-get", "install", "-y", "libzmq3-dev=4.3.4-1"]

Proxies

Every supported package manager fetches over libcurl or reads the standard proxy variables directly, so install-deps does not translate a proxy into seven different manager-specific flags. It makes sure the variables actually arrive:

Variable Set by --proxy Carried from the environment
http_proxy / HTTP_PROXY yes yes
https_proxy / HTTPS_PROXY yes yes
all_proxy / ALL_PROXY no yes
no_proxy / NO_PROXY no yes

Both spellings are set because the managers disagree: apt, apk and libcurl read the lowercase names, Homebrew's Ruby reads the uppercase ones. A single URL says nothing about which hosts to bypass or how to reach a SOCKS relay, so all_proxy and no_proxy are only ever carried through from the environment — never synthesised.

Why setting the variable yourself was not enough

sudo resets the environment. Exporting http_proxy in your shell and then running a command that escalates drops it on the far side, which is why

http_proxy=http://proxy.internal:3128 make install-deps   # used to be ignored

appeared to do nothing. The assignments are now re-applied after the sudo binary rather than before it, so they survive env_reset without depending on the sudoers env_keep list:

jbx install-deps --dry-run --proxy http://proxy.internal:3128 -s apt
# sudo env http_proxy=http://proxy.internal:3128 https_proxy=… apt-get update
# sudo env http_proxy=http://proxy.internal:3128 https_proxy=… apt-get install …

Order matters. env http_proxy=… sudo apt-get would set the variable for sudo itself and have it reset away again — the failure this arrangement exists to avoid.

In a root container there is no sudo to escape, and the same command is correct there too:

jbx install-deps --dry-run --no-sudo --proxy http://proxy.internal:3128 -s apt
# env http_proxy=http://proxy.internal:3128 https_proxy=… apt-get update

Environment only

With no --proxy, whatever is already exported is used as-is — the same bootstrap.toml and the same command work behind a proxy and off it:

export http_proxy=http://proxy.internal:3128
export no_proxy=localhost,.internal
jbx install-deps

A no_proxy with no proxy alongside it is left alone: it describes exceptions to a configuration that does not exist, so it never wraps the install command.

--proxy overrides an ambient value rather than merging with it.

cmd arrays get the proxy too

The variables are exported into the script's own environment, not only injected into the managers' argv, so a verbatim cmd array inherits them like any other child process. This is the one thing that reaches a cmd without rewriting it.


Examples

Dry run — see what would be installed

--dry-run prints exactly what would have run, prefix included — the sudo below is the derived one, and is absent when run as root:

jbx install-deps --dry-run
# sudo pacman -Sy --needed --noconfirm zeromq fftw
# sudo pacman -Sy --needed --noconfirm base-devel cmake python

jbx install-deps --dry-run -g runtime
# sudo pacman -Sy --needed --noconfirm zeromq fftw

jbx install-deps --dry-run --no-sudo -g runtime
# pacman -Sy --needed --noconfirm zeromq fftw

Verbose output

jbx install-deps --dry-run --verbose
# section:  pacman
# groups:   runtime,dev
# packages: zeromq fftw
# sudo pacman -Sy --needed --noconfirm zeromq fftw
# packages: base-devel cmake python
# sudo pacman -Sy --needed --noconfirm base-devel cmake python

Override the detected package manager

Useful in containers or CI where auto-detection picks the wrong manager:

jbx install-deps --section apt
jbx install-deps -s dnf -g runtime

Scaffold a new deps.toml

jbx install-deps --template            # print to stdout
jbx install-deps --template deps.toml  # write to file

Pin to a specific script commit for reproducible CI

jbx gh:just-buildit/just-bashit/src/just_bashit/install-deps.sh@abc1234

Scaffold a new deps.toml

Generate a fully-populated template with all groups and package manager sections pre-filled with placeholder comments:

jbx install-deps --template            # stdout
jbx install-deps --template deps.toml  # write to file

The generated file includes usage instructions and example values for every supported section. Delete the sections you don't need, fill in the rest.


Windows / MSYS2

msys2 sections are never executed directly — the script always prints the equivalent pacman command for you to run manually in a UCRT64 shell:

[runtime.msys2]
packages = [
    "mingw-w64-ucrt-x86_64-zeromq",
    "mingw-w64-ucrt-x86_64-fftw",
]
install-deps.sh deps.toml
# Windows/MSYS2: open a UCRT64 shell and run:
#   pacman -S mingw-w64-ucrt-x86_64-zeromq mingw-w64-ucrt-x86_64-fftw

Use the UCRT64 shell, not MSYS

The MSYS POSIX compiler and the UCRT64 native compiler have incompatible headers. Always launch from the UCRT64 shortcut so /ucrt64/bin is first on PATH.


Platform detection

The package manager is inferred from /etc/os-release on Linux and uname -s elsewhere. The mapping:

ID / ID_LIKE contains Section
debian, ubuntu apt
arch, cachyos, manjaro pacman
fedora, rhel, centos, rocky, alma dnf
suse zypper
alpine apk
Darwin (uname) brew
MINGW / MSYS / CYGWIN (uname) msys2

If detection fails, use --section to specify the package manager explicitly.


Shell compatibility

install-deps.sh requires bash 3.2 or later — compatible with the system bash shipped on macOS (bash 3.2.57, GPL-2). No bash 4+ features are used: no mapfile/readarray, no associative arrays (declare -A), no [[ =~ ]] capture groups.

Shell Minimum version Notes
bash 3.2+ macOS default /bin/bash works
zsh not supported run via bash install-deps.sh explicitly
dash / sh not supported arrays and process substitution required

If your environment ships an older bash or a non-bash shell as /bin/sh, invoke the script directly:

bash install-deps.sh -g runtime,dev

or via jbx, which always runs scripts under bash:

jbx install-deps