setup-system¶
setup-system takes a freshly installed machine to a working one: system
packages, an opinionated bash configuration, ssh, git defaults, and dev
tooling. It runs ephemerally via jbx — no local
installation required.
jbx setup-system --dry-run # see the whole plan, change nothing
jbx setup-system # do it
jbx setup-system -s shell # just the bash configuration
Every step is idempotent. Re-running setup-system is how you pick up a newer
template on a machine you set up months ago, not a mistake.
Steps¶
| Step | What it does |
|---|---|
deps |
Installs system packages from jb.toml / jb-deps.toml in the current directory, via install-deps. Skipped when there is no deps file. |
shell |
Installs the bash configuration to ~/.config/just-bashit/ and adds one source line to ~/.bashrc and ~/.profile. |
ssh |
Fixes ~/.ssh permissions and creates an ed25519 key named after this host if there is no key at all. Prints the public key. |
git |
Sets global git defaults that are not already set. Never touches user.name or user.email. |
tools |
Installs uv if missing; installs pre-commit hooks when the current directory is a repo with .pre-commit-config.yaml. |
claude |
Installs Claude Code if the claude command is missing. |
They always run in that order, whatever order you list them in — packages
land before the steps that need git, curl and ssh-keygen.
jbx setup-system -s shell,ssh # only these
jbx setup-system -x claude,deps # everything except these
To change the default set for a project, declare it in jb.toml:
-s always overrides that, regardless of the toml setting.
The shell step¶
Your ~/.bashrc is yours. The shell step never rewrites it — it installs
two files of its own and appends one line that sources each:
~/.config/just-bashit/
bashrc.sh interactive settings — readline, history, aliases, prompt
profile.sh environment — PATH, EDITOR, ssh-agent
bashrc.d/ your additions; sourced last, never overwritten
# appended to ~/.bashrc
if [ -r "$HOME/.config/just-bashit/bashrc.sh" ]; then . "$HOME/.config/just-bashit/bashrc.sh"; fi
# appended to ~/.profile (and ~/.bash_profile, when that file exists)
if [ -r "$HOME/.config/just-bashit/profile.sh" ]; then . "$HOME/.config/just-bashit/profile.sh"; fi
Uninstalling is deleting those lines. Re-running with a newer just-bashit
replaces bashrc.sh and profile.sh in place, keeping the copy it replaced
as bashrc.sh.bak.
Why two files¶
Every distro's stock ~/.bashrc opens with an interactive-only guard:
Anything exported below that line reaches interactive terminals and nothing
else. ~/.profile runs once per login session, and everything descended
from that session inherits its exports — scripts, language servers, agents,
GUI apps — whether or not a terminal was involved. A PATH or
SSH_AUTH_SOCK set only in .bashrc therefore works when you type it and is
missing from anything that did not come from a terminal.
So environment lives in profile.sh, read by login shells, and interactive
settings live in bashrc.sh. Because terminal emulators start non-login
shells that never read ~/.profile, bashrc.sh sources profile.sh itself
when no login shell has already done so. Both paths converge; neither
duplicates the other.
profile.sh is written in POSIX sh, not bash, because ~/.profile is also
read by dash and by desktop session managers.
Put your own settings in bashrc.d/
~/.config/just-bashit/bashrc.d/*.sh is sourced last, so anything there
wins over the template and survives the next setup-system run.
What you get¶
Arrow keys search history. Type ssh and press Up: you get your last
ssh command, not your last command. Each key is bound in both normal
(\e[A) and application (\eOA) cursor modes, so it works in tmux and
screen as well as a bare terminal.
An ssh-agent that already has your keys. profile.sh adopts an inherited
agent if there is one — forwarded, systemd socket-activated, gnome-keyring,
1Password — and only starts its own if nothing is listening, on a fixed
socket path so the next login reuses it instead of leaking an agent per
shell. bashrc.sh then adds any ~/.ssh/NAME that has a matching NAME.pub
and is not already loaded, which is why passphrase prompts happen once per
key per boot rather than once per terminal.
History that survives. 100 000 entries, deduplicated, timestamped, appended rather than overwritten, and flushed at every prompt — closing one terminal never discards what another one recorded.
A prompt worth the columns. [✗2] ~/code/project (main*) $ — exit status
only after a failure, user@host only when the session is remote or
containerised, git branch from symbolic-ref and a tracked-file diff rather
than git status, which stalls the prompt in large repos.
Plus: colour ls/grep/diff probed rather than assumed (GNU takes
--color=auto, BSD takes -G), ll/la/../..., mkcd, interactive
guards on rm/cp/mv, bash 4 shell options behind a version check so
macOS's bash 3.2 does not error, and bash-completion loaded from whichever
of five paths this distro or Homebrew prefix uses.
Opt-outs¶
Set any of these in ~/.bashrc before the source line:
| Variable | Effect |
|---|---|
JB_SSH_AGENT=0 |
Do not start or adopt an ssh-agent |
JB_SSH_AUTOADD=0 |
Agent yes, but do not add ~/.ssh keys to it |
JB_PATH=0 |
Leave PATH alone |
JB_PROMPT=0 |
Leave PS1 alone |
JB_PROMPT_GIT=0 |
Prompt without the git branch |
JB_PROMPT_COLOR=0 |
Prompt without colour |
JB_READLINE=0 |
No key bindings |
JB_ALIASES=0 |
No aliases |
JB_SAFE_ALIASES=0 |
Aliases, but no interactive rm/cp/mv guards |
JB_COMPLETION=0 |
Do not load bash-completion |
The ssh step¶
Creates a key only when ~/.ssh holds no private key with a matching
.pub — a machine that already has an identity does not get another one.
The key is ed25519, named after the host, and the public half is printed
with the gh command to register it:
jbx setup-system -s ssh
# generating ed25519 key /home/you/.ssh/workstation
# ...
# gh ssh-key add /home/you/.ssh/workstation.pub --title 'workstation'
--key-name NAME overrides the filename.
--yes creates the key with an empty passphrase
Without --yes, ssh-keygen prompts for a passphrase as usual. With
--yes there is nothing to prompt on, so the key is written unprotected —
appropriate for a throwaway VM or a container, not for a laptop.
The git step¶
Sets these only when the key has no value already, so nothing you have chosen is overwritten:
| Key | Value |
|---|---|
init.defaultBranch |
main |
pull.rebase |
true |
rebase.autoStash |
true |
push.default |
simple |
push.autoSetupRemote |
true |
fetch.prune |
true |
diff.colorMoved |
zebra |
color.ui |
auto |
core.editor |
$EDITOR |
user.name and user.email are deliberately absent: identity belongs to
the repository, not the machine. Set it per repo with
git config user.email you@example.com.
Options¶
| Flag | Long form | Description |
|---|---|---|
-h |
--help |
Show help and exit |
-n |
--dry-run |
Print the plan; change nothing |
-v |
--verbose |
Print extra detail |
-y |
--yes |
Never prompt (see the ssh warning above) |
-s STEPS |
--steps STEPS |
Comma-separated steps to run |
-x STEPS |
--skip STEPS |
Comma-separated steps to leave out |
--prefix DIR |
Config directory (default ~/.config/just-bashit) |
|
--key-name NAME |
ssh key filename (default: this hostname) | |
--template [PATH] |
Write the bashrc template to PATH, or stdout | |
--template-profile [PATH] |
Write the profile template to PATH, or stdout |
Examples¶
Set up a new machine from scratch¶
Read the templates without installing anything¶
jbx setup-system --template # the interactive half
jbx setup-system --template-profile # the environment half
Take the shell config and nothing else¶
Provision a CI runner or container¶
Use it as your project's onboarding command¶
# jb.toml
[tools.setup-system]
source = "just-bashit:setup-system"
steps = ["deps", "tools"]
[dev.apt]
packages = ["build-essential", "cmake"]
[dev.pacman]
packages = ["base-devel", "cmake"]
Dry run¶
--dry-run prints every command it would run and writes nothing at all —
no files, no config, no packages:
just-bashit setup-system
dry run — nothing will be changed
==> shell — bash configuration
would run: mkdir -p /home/you/.config/just-bashit ...
creating: /home/you/.config/just-bashit/bashrc.sh
would append to /home/you/.bashrc: if [ -r "$HOME/... "; fi
==> git — global defaults
git config --global init.defaultBranch main
...
summary
shell: ok (/home/you/.config/just-bashit)
git: ok (9 default(s) set)
Shell compatibility¶
setup-system.sh needs bash 3.2 or later — the system bash on macOS
qualifies. The installed bashrc.sh degrades on the same footing: bash 4
shell options are behind a version check, and readline settings that need a
newer readline fail silently rather than erroring.
profile.sh is POSIX sh and is safe to source from dash.