Project Status: Alpha. Linux support is ready but documentation is missing.
bx is a single-binary developer-environment manager. One bx init and
one bx apply replace setting up mise, sccache, uv, git, ssh, gh, starship and
the rest one tool at a time. Your configuration lives in a git repo you own;
your machine converges to it.
It is additive: it never deletes or rewrites config you wrote, and it never
moves another tool's config, data or cache anywhere you did not declare. Remove
bx and every tool you manage with it still works exactly as before.
Linux ready. macOS coming soon. No Windows unless refuted.
curl -fsSL https://raw.githubusercontent.com/getkono/bx/master/install.sh | shA single static binary, no runtime dependencies — it installs onto a barebones
box with no toolchain, which is the point: bx is what you run before you
have anything else.
It also installs as userbox. Two-letter names are a scarce namespace with
no registry, so the long name is the one that is guaranteed to keep working:
if bx ever collides with something on your machine, drop the short name and
nothing else changes. (fd ships fdfind on Debian for the same reason.)
To upgrade, run bx self-upgrade: it runs this same installer over the
installed binary. bx self-upgrade --check only reports whether a newer
release exists, exiting 2 when one does, and bx self-upgrade --force
reinstalls the latest release even when yours is not older. Each release pins the installer's checksum, so if the installer
has changed since your release, bx self-upgrade refuses and sends you here.
Reinstall with the line above.
bx init # guided setup — first machine or fifth, same commandinit finds the tools and config already on the machine, asks which of them to
manage, asks for the handful of values that are yours alone, shows you the diff,
and applies it on confirmation.
It is idempotent and resumable. Run it again at any time: it says whether it
created the config repo or found yours, asks only for values that still have no
answer, offers whatever config is still unmanaged, and ends by saying where the
machine stands. On a machine that is already set up it writes nothing unless
you pick something it offers: the only question it puts is the offer of config
you have not chosen to manage, and picking nothing is a fine answer. To change an answer you already gave,
bx init --set NAME=VALUE.
Esc or Ctrl-C at any question stops it there. What it had already written stays
and has already been reported, and the next bx init picks up from that point.
Answers you typed in a run that stopped at a later value question are not kept.
There are eleven. You should not need a manual.
bx |
status: every managed target, and what is pending, in conflict or blocked |
bx init |
guided setup |
bx add PATH |
begin managing a config file, or a directory of them |
bx rm PATH |
stop managing it, and restore the original |
bx plan |
the diff apply would make |
bx apply |
converge this machine to the repo |
bx sync |
pull, apply, push — no git knowledge required |
bx secret list |
list declared secrets, and whether each decrypts here |
bx doctor |
missing tools, unanswered values, damaged state, and what else needs a look; changes nothing |
bx shell-init |
the one line for your shell rc — not built yet |
bx self-upgrade |
install the latest release over this one; --check only looks, --force reinstalls even when this one is not older |
init, apply and sync take --yes, and init takes --set NAME=VALUE
for each value it would ask for, so all three run without a terminal.
add and rm need the file or directory to act on; without one they refuse
and point you at bx init, which offers the config already on the machine.
bx secret list reads; nothing under bx secret writes. Who secrets are
encrypted to is the recipients list under [secrets] in bx.toml or a
module, edited by hand, and a secret is encrypted or re-encrypted to them with
age -e.
Six symbols, and none of them is "destroy". A tool that only adds never has one, so those slots go to the cases that actually matter for a tool that must not be invasive: something it does not own is in the way, the tool it is configuring for is not on this machine, and a file it wrote is no longer declared.
+ create it does not exist yet
~ modify bx owns it and the content differs
! conflict it exists, differs, and bx does not own it — or you edited
bx's output. Reported and skipped, never overwritten.
? blocked the tool this configures is not installed, or is installed
where you cannot run it. Reported and skipped until you
install it — writing the config anyway would break your
shell or your builds, not just that one tool.
* undeclared bx wrote it, and the configuration no longer declares it.
Left exactly as it is; `bx rm` releases it.
= unchanged already converged (hidden unless you ask)
plan and apply compute this with the same code, so apply can never do work
plan did not show you.
plan follows the diff convention, so it is usable from CI, a prompt segment,
or a login banner without anyone parsing its output:
0 |
converged — nothing to do |
1 |
error |
2 |
changes pending, or a conflict or blocked target needs a decision |
130 |
you left a question with Esc or Ctrl-C; what was on offer was not done |
The other commands use the same codes. bx add exits 2 when it refused a
path, bx rm when a conflict left something as it was, and
bx self-upgrade --check when a newer release exists.
bxnever deletes or rewrites a byte you wrote. Its writes land in delimited managed regions, or in files it owns because you said so.- Every integration attaches at the target tool's own documented extension
point — an
[include]in.gitconfig, anIncludein.ssh/config, mise's own config directory, onesourceline in.zshrc. bxnever points a tool at abx-owned directory, and never sets an environment variable that moves a tool's config, data, or cache outside a root you declared. It sets only variables it knows how to judge, and judges each value for what it is — a location, a list of locations, an anchor, a program, a command line, a tool's options, a search list, a socket or a setting: declare your scratch mount as a root and your toolchain caches may live there; declare nothing and no location, list of locations or anchor is allowed at all. The other kinds say what a tool runs, where it looks and what it connects to rather than where its files live, so they move nothing and need no root — and none of them, root or no root, may point inside a directorybxowns. It will write only a variable in bx's emit table, which grows with the generators that need it.- Uninstalling is a supported operation, not an afterthought.
- Running
applytwice changes nothing the second time. planshows a real diff before anything is written. Nothing is written without it being shown first.- Every write is journalled and recorded, including the original bytes of
whatever it replaced, so
bx rmrestores exactly those bytes and that file mode. It does not restore a replaced file's extended attributes, POSIX ACL, SELinux label, owner and group, or timestamps: replacing a file creates a new one, and none of these are recorded. - An interrupted
applyis detected on the next run and rolled back: a read-only command reports it, and the next writing command undoes it before it does anything else. No torn files, ever, and no half-applied plan left standing. - Two
bxprocesses cannot corrupt each other.
- One way to do each thing. Configuration is data, not a program: there is no template language, no conditionals, no loops, no scripting hooks that can diverge between machines.
- Content that must differ per machine is a declared option or a declared value — never a branch hidden inside a config file.
- Paths are stored portably and rendered per machine, so a config repo moves to
a machine with a different
$HOMEwithout edits. bxowns the order in which shell integrations load, declaratively. Ordering bugs between a version manager, a PATH edit, and a completion system are a solved problem, not yours.
- All configuration lives in an ordinary git repo you can read, edit, review,
and host wherever you like.
bxis not required to understand it. - Editing the repo by hand and running an
bxcommand are the same operation: commands edit the same files, preserving your comments and formatting. - Nothing user-specific is ever committed. Identity, hostnames, absolute paths, and per-machine choices are asked for at setup and stay on the machine.
- No cleartext secret is ever committed. Secrets are encrypted at rest with
a key that never enters the repo,
bxdecrypts one only into its target, andbx addrefuses the files at a fixed list of paths known to hold credentials. Nothing reads a file's content for one, and nothing scans what you commit by hand. - A secret is delivered into a private-mode file.
- Adopting existing config is lossless: your current files are taken verbatim, so a fresh machine reproduces the one you have, not a skeleton of it.
- A managed file edited by hand is a conflict:
planandapplyreport it, andapplyskips it, leaving your edit exactly as it is. doctorreports a declared tool that is not onPATH, a required value with no answer, a damaged state file, an interrupted or running session, a directory wider than a private file in it, a declared optional source that is not readable, a unit file systemd has not reloaded, enabled or loaded, or that has failed, a declared reference that is not on disk, and a temporary file an interrupted write left behind.
bxruns at every shell start and must be unmeasurable. Its budget is 5 ms and the budget is enforced by a benchmark in CI, not by good intentions.- The shell startup path spawns no process and parses no configuration file.
- Where
bxcan make your other tools start faster without changing their behaviour, it does.
Your config repo lives wherever you want it; bx init proposes
~/.config/bx. It contains a manifest of what you manage, the config content
bx owns, and your encrypted secrets. It is safe to make public.
Everything specific to you or to one machine — answers, the write record, caches, and the key that decrypts your secrets — stays outside the repo, on the machine.
MIT