Skip to content

Bootstrapping Tools

github-actions[bot] edited this page Apr 23, 2026 · 6 revisions

Bootstrapping Tools

tuck bootstrap installs CLI tools on a fresh machine from a declarative catalog. Think of it as the orchestration half of "new-machine setup" — dotfiles come from tuck apply / tuck restore; the CLIs those dotfiles expect come from tuck bootstrap.

Quick start

# Interactive picker — detected tools pre-checked
tuck bootstrap

# Plan without running anything
tuck bootstrap --all --dry-run

# Install specific tools
tuck bootstrap --tools neovim,pet

# Install a named bundle
tuck bootstrap --bundle kali

See the full flag list in Command Reference — tuck bootstrap.

Built-in catalog

These come with tuck — you don't need a bootstrap.toml to use them:

Tool Source Notes
fzf apt package Fuzzy finder. updateVia: "system" (apt manages updates)
eza apt package (Debian trixie / Ubuntu 24.04+) Modern ls replacement. updateVia: "system"
bat apt package + batcat → bat symlink Symlinks to /usr/local/bin if sudo cached, else ~/.local/bin. updateVia: "system"
fd apt package (fd-find) + fdfind → fd symlink Same symlink pattern as bat. updateVia: "system"
ripgrep apt package Fast recursive grep (rg). updateVia: "system"
zsh apt package + optional chsh helper updateVia: "system"
neovim curl from GitHub stable release tar.gz; extract to /opt/nvim or ~/.local/opt/nvim + symlink Tracks upstream stable (apt lags the plugin ecosystem). Re-run with --rerun neovim after upstream ships a new release — tuck pins stable so there's no auto-update signal.
neovim-plugins nvim --headless with lazy.nvim sync + treesitter parser install Requires neovim. First install is heavy (cold compile); update is Lazy! sync which is fast.
pet curl from GitHub release .deb + dpkg -i Version-pinned (knqyf263/pet). Snippet manager.
yazi curl from GitHub release zip; extract to /usr/local/bin or ~/.local/bin Version-pinned (sxyazi/yazi). Terminal file manager.

updateVia: "system" behavior: tuck bootstrap update and tuck update defer these tools to the system package manager (apt) rather than running their update script. If you want to force an update anyway, use tuck bootstrap update --tools <id>.

Disabling or overriding built-ins:

  • Disable a built-in with [registry] disabled = ["bat", "fd"] in your bootstrap.toml.
  • Override by defining your own [[tool]] with the same id — user tools always win.

Your own bootstrap.toml

bootstrap.toml is optional — if the file doesn't exist, tuck bootstrap just uses the built-in catalog above. Create one when you want to add your own tools, define bundles, disable built-ins, or override their behavior.

Two annotated templates ship with tuck:

# Minimal starter — field reference + a couple of canned examples
cp "$(npm root -g)/@prnv/tuck/templates/bootstrap.toml.example" ~/.tuck/bootstrap.toml

# Full Debian/Ubuntu/Kali dev-workstation setup — bulk apt tier, Node
# toolchain, tealdeer, ZimFW, zsh-fzf-history-search, chsh helper, plus
# ready-to-use `kali`, `full`, and `minimal` bundles
cp "$(npm root -g)/@prnv/tuck/templates/bootstrap.toml.full.example" ~/.tuck/bootstrap.toml

Minimal [[tool]] shape:

[[tool]]
id = "ripgrep"
description = "recursive grep"
category = "shell"
requires = []                              # other tool ids this one needs first
check = "command -v rg >/dev/null 2>&1"    # exit 0 = already installed, skip
install = "sudo apt-get install -y ripgrep"
update = "sudo apt-get install -y --only-upgrade ripgrep"
detect = { paths = [], rcReferences = ["rg"] }   # picker detection hints

Extended example with variable interpolation:

[[tool]]
id = "my-custom-tool"
description = "something local"
version = "2.1.0"                           # interpolated as ${VERSION}
install = """
curl -fsSL https://example.com/tool-v${VERSION}-${OS}-${ARCH}.tar.gz | tar -xz -C /tmp
mv /tmp/tool /usr/local/bin/
"""
update = "@install"                         # @install or omitted → re-run install
updateVia = "self"                          # or "system" to defer to OS package manager

[bundles]
kali      = ["ripgrep", "fzf", "pet", "neovim", "neovim-plugins"]
minimal   = ["ripgrep", "fzf"]

Tool fields

Field Type Purpose
id string (required) Unique id; used by --tools, requires, bundle lists
description string Shown in the picker
category string Grouping label in the picker
version string Interpolated as ${VERSION} in install/update/check
requires string[] Other tool ids that must be installed first (transitive)
check string (shell) Exit 0 = already installed, skip install. Exit non-zero = proceed.
install string (shell, required) Install command. Runs in sh (or pwsh on Windows).
update string (shell) or @install Update command. @install or omitted → re-run install.
updateVia "self" | "system" "system" defers to OS package manager (see below). Default "self".
detect.paths string[] Glob paths that, if present, mark the tool as "detected" in the picker
detect.rcReferences string[] Substrings to look for in tracked shell dotfiles (rc/alias/function refs)
associatedConfig string[] Globs of config paths this tool owns. Used by tuck restore to offer a post-restore install prompt when those paths get written but the tool isn't installed.

Bundles

[bundles] is a table where each key is a bundle name and the value is an array of tool ids:

[bundles]
kali    = ["ripgrep", "fzf", "pet", "neovim", "neovim-plugins"]
minimal = ["ripgrep", "fzf"]
devbox  = ["neovim", "neovim-plugins", "tealdeer", "pet"]

Install a bundle with tuck bootstrap --bundle kali. Edit bundles without hand-editing TOML via tuck bootstrap bundle — watch the comment-stripping caveat if your file has load-bearing comments.

Variable interpolation

Exactly five tokens are substituted in check, install, and update strings:

Token Value
${VERSION} The tool's version field. Throws if referenced and unset.
${ARCH} amd64 / arm64 / armhf (Debian-style, mapped from os.arch())
${OS} linux / darwin / windows
${HOME} User home directory
${TUCK_DIR} Absolute path to the tuck data directory

Anything else — ${PATH}, $(uname -m), $HOME — passes through untouched so the shell expands it at run time. tuck deliberately does not reach through arbitrary env vars; expand them in the script itself if you need to.

Dependencies and order

requires targets are resolved transitively. Pick neovim-plugins and neovim is pulled in automatically, installed first, and tagged (dep) in the output. Cycles and unknown ids fail fast with the participating tool names in the error.

Example output:

Resolving install order…
  neovim (dep)
  neovim-plugins
  pet
  ripgrep

Installing…
  ✓ neovim (detected v0.10.2, updating to v0.10.3)
  ✓ neovim-plugins (Lazy! sync: 142 plugins synced)
  ✓ pet (v0.4.2 installed via dpkg -i)
  ✓ ripgrep (already installed, skipped)

4 installed, 0 failed, 0 skipped

State and drift detection

Successful installs are recorded in ~/.tuck/.bootstrap-state.json (per-host; never synced) with a SHA-256 hash of the normalized tool definition. If the definition changes in a later tuck release (or in your bootstrap.toml), the picker surfaces the tool as "outdated."

Re-run with --rerun <id> to force a reinstall ignoring the check probe:

tuck bootstrap --rerun neovim

State file schema (don't edit by hand — it's a machine file):

{
  "neovim": {
    "installedAt": "2026-04-22T14:30:22Z",
    "version": "0.10.3",
    "definitionHash": "sha256:abc123…"
  }
}

updateVia: "system"

Tools installed via a system package manager (apt, brew, dnf, nix) already receive updates through that package manager's own flow. Re-running tuck's update for those tools is redundant at best and racy at worst (two package operations fighting for the apt lock).

Mark such tools with updateVia = "system":

[[tool]]
id = "ripgrep"
install = "sudo apt-get install -y ripgrep"
update = "sudo apt-get install -y --only-upgrade ripgrep"
updateVia = "system"                        # defer to apt

Behavior:

  • tuck bootstrap --all and the picker still INSTALL the tool (install is the "first-time setup" path).
  • tuck bootstrap update --all and the picker skip the tool with a Deferred to system package manager info log. The tool doesn't appear selectable in the picker.
  • tuck bootstrap update --check excludes the tool from both the pending payload AND the exit-code signal, so CI that runs tuck bootstrap update --check won't fail just because apt has a newer ripgrep available.
  • tuck bootstrap update --tools <id> is the escape hatch — explicit naming always runs the update script.

The six apt-managed built-ins (bat, eza, fd, fzf, ripgrep, zsh) are tagged updateVia: "system" by default. pet uses dpkg -i from a GitHub release, so it stays self (apt doesn't know about it).

The pattern generalizes beyond apt — use "system" for any tool where something else (brew, nix, manual install, etc.) owns updates.

Sudo handling

Every sudo <cmd> line prompts interactively as usual. Under --yes, tuck pre-checks sudo -n true whenever the script contains sudo — if credentials aren't cached, you get one clear error ("run sudo -v first, or configure NOPASSWD") instead of a mystery hang deep inside a multi-line install command.

Failure containment

A single tool's install failing does not abort the run. Dependents of a failed tool are marked skipped-dep-failed and the loop continues. The final summary reports N installed, M failed, K skipped with per-tool detail, and tuck exits non-zero if anything failed so CI pipelines catch it.

Example:

Installing…
  ✓ ripgrep
  ✗ custom-tool (exit 42: curl: (6) could not resolve host example.com)
  ⊘ custom-tool-wrapper (skipped-dep-failed: custom-tool)
  ✓ fzf

2 installed, 1 failed, 1 skipped

Failed tools:
  custom-tool — network error, re-run when connectivity is restored

tuck update — the umbrella

tuck update chains self-updatepullrestorebootstrap update in one shot. When the self-update phase applies a new version, tuck update re-execs the freshly-installed binary so the remaining phases run under the new code (with a TUCK_UPDATE_RESUMED=1 loop guard). --install-deps is passed implicitly to the restore phase so umbrella refreshes install any missing tools as part of the same sweep.

See Command Reference — tuck update for flags, and Recipes — Run tuck update on a schedule for a cron / systemd-timer recipe.

See also

Clone this wiki locally