Skip to content

Latest commit

 

History

History
266 lines (209 loc) · 12.2 KB

bootstrap.md

File metadata and controls

266 lines (209 loc) · 12.2 KB

Bootstrap reference

Seven scripts. One runs on the Bazzite host. One runs inside each supported development environment. None of them writes a credential.

Script Where it runs
bootstrap/host.sh The Bazzite host
bootstrap/web-dev.sh Inside web-dev
bootstrap/python-dev.sh Inside python-dev
bootstrap/rust-dev.sh Inside rust-dev
bootstrap/golang-dev.sh Inside golang-dev
bootstrap/dotnet-dev.sh Inside dotnet-dev
bootstrap/android-dev.sh Inside android-dev
bootstrap/godot-dev.sh Inside godot-dev

Together, these scripts converge the whole machine. To install one part, use the component installer in components.md:

./install.sh --components devbox

The host bootstrap and the environment bootstraps converge the same state, because each step is one function in bootstrap/lib/, and the callers use it:

Library Step
lib/common.sh The shared idempotency, backup and dry-run helpers
lib/component.sh The preamble every component operation script uses
lib/platform.sh The platform adapter lookup, which answers how this machine supplies a capability
lib/host-packages.sh Homebrew formulae and casks, Flatpak applications
lib/devbox.sh The router, the agent host shims, the router configuration
lib/environments.sh Creation of one Distrobox development environment
lib/agent-home.sh The host agent policy, status line and preferences
lib/sandcastle.sh The agentbox CLI and its credential file location
lib/agentq.sh The agentq host shim and the container-side runtime

Shared behaviour

Both scripts use bootstrap/lib/common.sh, which gives them the same guarantees:

  • Idempotent. Each step checks the current state and changes only what does not match.
  • Non-destructive. Anything a script replaces is copied into ~/.agents/backups/<timestamp>/ first.
  • Additive for configuration this repository does not own. Claude and Codex defaults add missing keys and preserve existing values. Codex also migrates the previous toolkit policy when both old values are present. user hooks and state that a client wrote for itself survive.
  • --dry-run. Prints what a run would change, and writes nothing.

Both scripts print a summary at the end: how many steps changed something, how many were already correct, where the backups are, and which manual steps remain.

bootstrap/host.sh

bootstrap/host.sh [--dry-run] [--skip-brew] [--skip-flatpak]

It refuses to run inside a container.

Step Idempotency check
Directories test -d
Homebrew taps, formulae, casks compared against brew list and brew tap
Flatpak applications compared against flatpak list --app
Router, verify suite, wrappers symlink already points at the checkout
claude and codex shims generated by devbox new-shim only when absent
grok shim generated by devbox new-shim; a non-shim file at that path is replaced
agentq shim generated by devbox new-shim --env --map-path, and refreshed when the text differs
Router configuration symlink already points at the checkout
repos.tsv installed from the template only when the file is absent
The container of every supported environment module podman container exists; an existing container is never touched
Host Codex policy and status line symlink and item-list comparison

The shims are generated by the router itself rather than copied from this repository. There is then one source of truth for the shim, and it cannot drift from the router that consumes it.

Two of them are treated differently, on purpose:

  • claude and codex carry nothing but a tool name, so the bootstrap creates one only when it is absent and never touches a hand edit.
  • grok is the same kind of shim. A file that already carries the shim header is left alone. A file that does not is the upstream installer link, and the bootstrap replaces it. The copy goes to the backup directory. The bootstrap also removes the upstream PATH block from the host shell rc. That block would run the host binary instead of the shim. The host binary under ~/.grok/bin stays on disk.
  • agentq carries the environment that owns the runtime, read from AGENTQUEUE_ENVIRONMENT in manifests/agentqueue.env. That is configuration, and stale configuration must not survive a bootstrap, so the file is compared and rewritten when it differs. The copy it replaces goes to the backup directory first.

devbox new-shim <tool> --env <environment> writes a pinned shim:

exec "$router" exec <environment> --cwd "$PWD" -- <tool> "$@"

Use it for a command that is installed in exactly one environment. The working directory then decides which repository the command works on, and never where the command runs. devbox new-shim <tool> --print writes the same text to standard output, which is how a bootstrap compares before it writes.

--map-path <option> adds one more thing to the route line:

exec "$router" exec <environment> --cwd "$PWD" --map-path --repo -- <tool> "$@"

The user types a host path in that option, and the tool reads it inside the container. The router then translates the value the way it translates the working directory. An absolute value maps directly, a relative one resolves against the host working directory first, and a leading ~ expands against the host home. The option is repeatable, and it needs --env, because only a pinned shim knows the destination.

bootstrap/web-dev.sh

bootstrap/web-dev.sh [--dry-run] [--skip-skills]

It refuses to run on the host.

Step Idempotency check
Distribution packages rpm -q, from manifests/web-dev-packages.txt
nvm test -s $NVM_DIR/nvm.sh
Shell integration skipped when ~/.bashrc already loads nvm
Node nvm version <v>
Corepack, pnpm version comparison against manifests/toolchain.env
Claude Code, Codex, Grok test -x on the binary. Grok's binary is ~/.grok/bin/grok
agentq runtime symlink target comparison
~/.agents, ~/.claude, ~/.codex wiring symlink target comparison
Skills bin/install-skills compares content against each source
Client preferences absent keys only
Status line install.sh, which compares before it writes

Why the shell integration step is conditional

The stock Fedora ~/.bashrc already sources ~/.bashrc.d/*. The script installs 10-nvm.sh there, which is the tidy place for it. If ~/.bashrc already loads nvm inline, as it does on the machine this repository was captured from, the script leaves it alone. nvm is then never loaded twice, and an existing hand edit survives.

The router runs commands through the box login shell, because environments.d/web-dev.env sets login_shell = 1. That is what makes node and pnpm resolve for every routed command, with no init_file and no toolchain knowledge in the router.

Agent CLI versions

Claude Code, Codex and Grok are native installers, not npm packages, and each one self-updates. manifests/toolchain.env therefore records the verified version rather than a hard pin, and the bootstrap installs a CLI only when it is absent. It never downgrades one that is already there. Grok's installer writes ~/.grok/bin/grok. The bootstrap then links ~/.local/bin/grok to that binary inside the environment. The host path of the same name is the shim.

bootstrap/python-dev.sh

bootstrap/python-dev.sh [--dry-run] [--skip-skills]

It refuses to run on the host.

Step Idempotency check
Distribution packages rpm -q, from manifests/python-dev-packages.txt
Shell integration config/python-dev/bashrc.d/10-python-dev.sh, compared before it is written
uv version comparison against manifests/python-dev.env
Python ownership the manifest is refused when it pins a PYTHON_VERSION
Claude Code, Codex, Grok test -x on the binary. Grok's binary is ~/.grok/bin/grok
~/.agents, ~/.claude, ~/.codex wiring symlink target comparison
Skills bin/install-skills compares content against each source
Client preferences absent keys only
Status line install.sh, which compares before it writes

The environment pins uv and no interpreter. A repository owns its Python version through .python-version or requires-python, and uv installs that interpreter into the isolated container HOME. docs/python-dev.md says why.

bootstrap/rust-dev.sh

bootstrap/rust-dev.sh [--dry-run] [--skip-skills]

It refuses to run on the host.

Step Idempotency check
Distribution packages rpm -q, from manifests/rust-dev-packages.txt
Shell integration config/rust-dev/bashrc.d/10-rust-dev.sh, compared before it is written
rustup test -x $CARGO_HOME/bin/rustup. An existing rustup is never reinstalled
Baseline toolchain compared against rustup toolchain list
Claude Code, Codex, Grok test -x on the binary. Grok's binary is ~/.grok/bin/grok
~/.agents, ~/.claude, ~/.codex wiring symlink target comparison
Skills bin/install-skills compares content against each source
Client preferences absent keys only
Status line install.sh, which compares before it writes

manifests/rust-dev.env pins the toolchain as a baseline, not as a ceiling. It is what a repository gets when it pins nothing, and a repository that carries rust-toolchain.toml overrides it. docs/rust-dev.md says why.

bootstrap/golang-dev.sh, bootstrap/dotnet-dev.sh, bootstrap/android-dev.sh and bootstrap/godot-dev.sh

Each script runs inside its matching container and accepts --dry-run and --skip-skills.

Script Toolchain state Verification
golang-dev.sh Go from the distribution package; dependencies stay in go.mod and go.sum ./verify.sh --only 28
dotnet-dev.sh The .NET SDK pinned in dotnet-sdk.env; target frameworks stay in project files and global.json ./verify.sh --only 29
android-dev.sh JDK 25, Android command-line tools, Android Emulator and the SDK packages in android-dev.env ./verify.sh --only 27
godot-dev.sh The Godot engine .NET build pinned in godot-dev.env, plus the .NET SDK pinned in dotnet-sdk.env; project settings stay in project.godot and the project files ./verify.sh --only 21

All four also install the agent CLIs, shared agent configuration, third-party skills, client preferences and status line. The environment-specific manifests remain the source of truth.

dotnet-dev.sh and godot-dev.sh install the same .NET SDK, from manifests/dotnet-sdk.env, through ensure_dotnet_sdk in bootstrap/lib/dotnet.sh. The distribution package carries feature band 1xx only, and no global.json rollForward policy moves down a band, so a repository that pins another band needs the upstream SDK. docs/dotnet-dev.md says why.

Only web-dev carries the coordinator

bootstrap/web-dev.sh installs the agentq runtime. The other environment bootstraps do not. The coordinator is installed in one environment, which manifests/agentqueue.env names. docs/agentq.md says why.

Adding a new environment

An environment is a module under components/. The router, the manifests and the host bootstrap are already generic: they discover the modules, and they name no environment. docs/environments.md holds the full procedure, and dotnet-dev already exists as a module.

To make it real:

# 1. Describe the container.
cp distrobox/web-dev.ini distrobox/dotnet-dev.ini  # then edit image, home, packages
distrobox assemble create --file distrobox/dotnet-dev.ini

# 2. Describe the environment for the router.
devbox new-env dotnet-dev --box dotnet-dev --workspace ~/projects:/workspace
mv <generated file> config/devbox-router/environments.d/dotnet-dev.env

# 3. Declare both in the module, and set "status": "supported".
$EDITOR components/dotnet-dev/component.json

# 4. Nothing else. components/dotnet-dev/inference.tsv already routes global.json.
./verify.sh --only 17

bootstrap/host.sh then creates the container, and the router installation links the environment file, because both discover what the modules declare.