From 4a03e42b9d5282fd390fe023e9c87207cb7b71c3 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sun, 28 Jun 2026 09:02:18 +0530 Subject: [PATCH] docs: Update documentation to incorporate for Installation Strategies Redesign --- docs/installation_strategies.md | 389 ++++++++++++++++++++++++++++++++ docs/rollback_design.md | 52 +++-- readme.md | 26 ++- 3 files changed, 441 insertions(+), 26 deletions(-) create mode 100644 docs/installation_strategies.md diff --git a/docs/installation_strategies.md b/docs/installation_strategies.md new file mode 100644 index 0000000..93e783a --- /dev/null +++ b/docs/installation_strategies.md @@ -0,0 +1,389 @@ +# Installation Strategies & Installer Lifecycle + +This document explains how Bootstrap categorizes tool installations, the anatomy of an installer script, the full execution lifecycle from invocation to registry recording, and the helper infrastructure available to installers. + +## 1. Installation Strategies + +Every installer declares an explicit **strategy** that describes how it provisions software onto the system. The strategy is recorded in two places: + +1. **At authoring time** — as a `# Strategy:` metadata comment in the installer script header. +2. **At runtime** — inside `$BOOTSTRAP_STATE_DIR/registry.json` via `register_tool`. + +### Strategy Types + +| Strategy | Description | Validation (`registry_check`) | +| :--- | :--- | :--- | +| `binary` | Tool is downloaded as a prebuilt binary (typically from a GitHub release) and placed into `$BOOTSTRAP_BIN`. | Confirms the registered binary path exists and is executable. | +| `managed` | Tool is installed through its own dedicated version manager or installer (e.g. NVM for Node.js, `rustup` for Rust). Bootstrap orchestrates the manager but doesn't own the binary directly. | Confirms the tool is available via `command -v`. | +| `system` | Tool is installed entirely through the system package manager (`pacman`, `apt`, `dnf`). | Validates recorded system packages using `pkg_check`, falling back to `command -v` when no dependencies are recorded. | + +### Which Strategy to Use + +- **Prefer `binary`** when the upstream project provides standalone Linux binaries on GitHub (e.g. `bat`, `lazygit`, `hyperfine`, `yazi`, `nvim`). +- **Use `managed`** when the tool has its own version/runtime manager that must be preserved for updates (e.g. `nvm` for Node.js, `rustup` for Rust, `zoxide` which uses its own install script). +- **Use `system`** when the tool is only available through the distro's package manager and there is no upstream binary distribution (e.g. `docker`, `yay`). + +## 2. Installer Script Anatomy + +Every installer lives at `installers/install_.sh` and follows a strict structure. + +### 2.1 Metadata Header + +The first lines of the file are structured comments parsed by `scripts/generate_registry.sh` to auto-generate `lib/registry.sh`: + +```bash +#!/usr/bin/env bash +# Tool: bat +# DisplayName: Bat +# Description: Install Bat (alternative to cat) and configure alias +# Strategy: binary +# +``` + +| Field | Purpose | +| :--- | :--- | +| `Tool` | The CLI key used to invoke the installer (`b bat`). Must be lowercase, no spaces. | +| `DisplayName` | Human-readable name shown in `b ware` listings and log messages. | +| `Description` | One-line description. The word "Install" at the start is stripped automatically by the registry generator. | +| `Strategy` | One of `binary`, `managed`, or `system`. | + +### 2.2 Script Body Structure + +```bash +set -euo pipefail + +# Temporary directory (auto-cleaned) +TMP_DIR="$(make_temp_dir)" +cleanup() { rm -rf "$TMP_DIR"; } +trap cleanup EXIT + +# Core installation function +install_() { + # 1. Check if already installed, offer reinstall/upgrade + # 2. Detect architecture, fetch latest version + # 3. Download, extract, install + # 4. Track files for rollback + # 5. Register in the JSON registry +} + +# Optional: shell environment configuration +configure_shell() { + # Write env/alias/completion snippets +} + +main() { + install_ + configure_shell # if needed + + echo + log_success " installation and configuration complete." +} + +main "$@" +``` + +### 2.3 Key Conventions + +- **No standalone execution guards**: Installers are always executed by the Bootstrap runtime, which sources all libraries before running the script. They must never check for or source libraries themselves. +- **No `curl` availability checks**: The runtime guarantees `curl` is available. +- **No direct `.bashrc` manipulation**: Use `write_env_snippet`, `write_alias_snippet`, and `write_completion_snippet` to write shell configuration fragments into the snippet directories (`env.d/`, `aliases.d/`, `completions.d/`). The loader sources these automatically. +- **Temp directories**: Use `make_temp_dir` (from `lib/common.sh`) for temporary work and clean up via a `trap cleanup EXIT`. + +## 3. Available Helper Functions + +Installers run inside a prepared environment where all Bootstrap libraries have been sourced. The following helper functions are available: + +### 3.1 Common Utilities (`lib/common.sh`) + +| Function | Purpose | +| :--- | :--- | +| `log_info`, `log_success`, `log_warn`, `log_error` | Coloured logging to stdout/stderr. | +| `confirm ""` | Interactive yes/no prompt. Reads from `/dev/tty` to support piped execution. | +| `has_command ` | Returns `0` if the command exists on `$PATH`. | +| `make_temp_dir` | Creates a `mktemp -d` temporary directory and prints its path. | +| `download_file ` | Cached, resumable download. Files are first stored in `$BOOTSTRAP_CACHE_DIR/downloads/` and then copied to ``. | +| `version_lt ` | Semver comparison. Returns `0` if `v1 < v2`. | + +### 3.2 Platform & Package Management (`lib/platform.sh`) + +| Function | Purpose | +| :--- | :--- | +| `detect_distro` | Returns `arch`, `debian`, `fedora`, or `unknown`. | +| `detect_arch` | Returns `x86_64` or `arm64`. | +| `pkg_install ...` | Installs system packages. Supports distro-specific mappings: `"arch:docker\|debian:docker.io\|fedora:docker"`. | +| `pkg_check ...` | Returns `0` if all specified packages are installed. | +| `pkg_remove ...` | Removes system packages. | + +### 3.3 GitHub Release Helpers (`lib/github.sh`) + +| Function | Purpose | +| :--- | :--- | +| `github_get_latest_release ` | Queries the GitHub API and prints the `tag_name` of the latest release. | +| `github_get_download_url ` | Finds the asset matching the regex pattern in the given release and prints its download URL. | +| `github_download_asset ` | Resolves and downloads the matching asset to ``. | + +**Why `github_get_latest_release` is separate from `github_download_asset`**: Asset filenames often embed the version string (e.g. `bat-v0.26.1-x86_64-unknown-linux-gnu.tar.gz`). The installer needs the concrete version *before* constructing the asset pattern and also passes it to `register_tool`. + +### 3.4 Shell Configuration (`lib/shell_config.sh`) + +| Function | Purpose | +| :--- | :--- | +| `write_env_snippet ` | Writes content to `$BOOTSTRAP_DIR/env.d/.sh`. Auto-registers a rollback command to delete the snippet. | +| `write_alias_snippet ` | Writes content to `$BOOTSTRAP_DIR/aliases.d/.sh`. Auto-registers rollback. | +| `write_completion_snippet ` | Writes content to `$BOOTSTRAP_DIR/completions.d/.sh`. Auto-registers rollback. | + +### 3.5 Rollback & File Tracking (`lib/rollback.sh`) + +| Function | Purpose | +| :--- | :--- | +| `track_file ` | Records `sudo rm -f ''` in the uninstaller manifest for the current tool. | +| `track_dir ` | Records `sudo rm -rf ''` in the uninstaller manifest. | +| `add_rollback_cmd ` | Prepends an arbitrary shell command to the manifest (LIFO order). | + +### 3.6 Registry Helpers (`lib/registry_helpers.sh`) + +| Function | Purpose | +| :--- | :--- | +| `register_tool [version] [source]` | Records the tool in `registry.json` with its strategy, version, source, timestamp, and (for `binary` strategy) its binary path. | +| `registry_add_sys_deps ...` | Appends system package dependency names to the tool's `system_dependencies` array in the registry (used for reference counting during uninstallation). | +| `registry_remove_tool ` | Deletes the tool's entire entry from `registry.json`. | +| `registry_check ` | Validates that the tool is correctly installed according to its declared strategy. | + +## 4. Execution Lifecycle + +This is the complete flow from when a user types `b ` to when the installation is recorded. + +### 4.1 Invocation + +``` +User types: b bat + │ + ▼ + b.sh (shell function) + │ + ├── Auto-update check (once per 24h) + │ + ▼ + bash routes.sh bat +``` + +`b.sh` is a shell function sourced from `~/.bashrc`. It delegates to `routes.sh` in a subshell. + +### 4.2 Library Loading + +`routes.sh` sources the core libraries in order: + +1. `lib/common.sh` — Logging, `download_file`, `make_temp_dir`, XDG path variables +2. `lib/rollback.sh` — Manifest system, `track_file`, `track_dir`, `init_rollback_system` +3. `lib/platform.sh` — Distro/arch detection, `pkg_install`/`pkg_remove` +4. `lib/shell_config.sh` — `write_env_snippet`, `write_alias_snippet`, etc. +5. `lib/registry.sh` — Auto-generated lookup tables (`INSTALLERS`, `INSTALLER_DISPLAYS`, `INSTALLER_STRATEGIES`, `INSTALLER_KEYS`) +6. `lib/registry_helpers.sh` — `register_tool`, `registry_add_sys_deps`, etc. +7. `lib/github.sh` — GitHub release API helpers +8. `lib/plugins.sh` — Plugin system + +All functions are exported to subshells so the installer script inherits them. + +### 4.3 Routing + +`routes.sh` resolves the command. If the argument matches a key in the `INSTALLERS` registry, it is treated as a tool name and routed to `run_ware`: + +``` +routes.sh receives "bat" + │ + ├── Is it a built-in command (help, rb, fall, con, up, gone, etc.)? → No + ├── Is it a registered tool key in INSTALLERS? → Yes + │ + ▼ +run_ware "bat" "-y" +``` + +### 4.4 Installer Resolution + +`run_ware` locates the installer script: + +1. **Local check**: Does `$BOOTSTRAP_DIR/installers/install_bat.sh` exist? If yes, use it directly. +2. **Remote download**: If not local, download from the primary Git server (`git.adityagupta.dev`). On failure, fall back to the GitHub mirror. + +### 4.5 Pre-Execution Setup + +Before running the installer, `run_ware` calls `setup_uninstaller_context "bat"`, which: + +- Sets `BOOTSTRAP_CURRENT_TOOL=bat` +- Sets `BOOTSTRAP_UNINSTALLER_CMDS=$BOOTSTRAP_STATE_DIR/uninstallers/bat.cmds` +- If a manifest already exists from a previous interrupted run, it preserves it (resume support). +- Otherwise, creates a fresh empty manifest file. + +Signal traps (`INT`, `TERM`) are installed to catch interruptions. + +### 4.6 Installer Execution + +The installer script runs in a `bash` subshell. During execution: + +- **`track_file`/`track_dir`** calls prepend removal commands to `bat.cmds` (LIFO order). +- **`write_env_snippet`/`write_alias_snippet`** automatically record their own rollback commands. +- **`pkg_install`** installs system packages. If the tool uses `system` strategy, the installer calls `registry_add_sys_deps` to register these packages for reference counting. +- **`register_tool "bat" "binary" "$version" "github:sharkdp/bat"`** writes the tool's metadata to `$BOOTSTRAP_STATE_DIR/registry.json` using thread-safe `flock`. + +### 4.7 Post-Execution + +If the installer exits successfully (`exit 0`), `run_ware`: + +1. Calls `mark_install_success "bat"` — appends `INSTALL: bat` to `$BOOTSTRAP_STATE_DIR/history.log`. +2. Re-sources `~/.bashrc` so new env/alias snippets take effect immediately. + +If the installer fails or is interrupted, the user is offered a choice: + +- **Rollback (`r`)**: Executes the manifest line-by-line to undo all recorded changes. +- **Keep (`k`)**: Preserves partial changes so the user can resume or debug. + +## 5. The Registry (`registry.json`) + +The JSON registry is the central source of truth for installed tool state. It lives at `$BOOTSTRAP_STATE_DIR/registry.json`. + +### 5.1 Schema + +```json +{ + "tools": { + "bat": { + "strategy": "binary", + "version": "v0.26.1", + "source": "github:sharkdp/bat", + "installed_at": "2025-06-28T03:15:00Z", + "bin": "/home/user/.local/share/bootstrap/bin/bat" + }, + "docker": { + "strategy": "system", + "installed_at": "2025-06-28T03:20:00Z", + "source": "os-package-manager", + "system_dependencies": [ + "arch:docker|debian:docker.io|fedora:docker" + ] + }, + "node": { + "strategy": "managed", + "version": "v0.40.5", + "source": "github:nvm-sh/nvm", + "installed_at": "2025-06-28T03:25:00Z" + } + } +} +``` + +### 5.2 Thread Safety + +All mutations go through `registry_set`, which acquires an exclusive file lock (`flock -x`) on `registry.json.lock` before applying a `jq` filter. This prevents race conditions if multiple installers run simultaneously. + +## 6. The Auto-Generated Registry (`lib/registry.sh`) + +`lib/registry.sh` is a **build-time artifact**, not a runtime state file. It is auto-generated by `scripts/generate_registry.sh`, which parses the metadata headers from every `installers/install_*.sh` file and produces four Bash data structures: + +| Variable | Type | Purpose | +| :--- | :--- | :--- | +| `INSTALLERS` | `declare -A` | Maps tool key → description | +| `INSTALLER_DISPLAYS` | `declare -A` | Maps tool key → human-readable display name | +| `INSTALLER_STRATEGIES` | `declare -A` | Maps tool key → strategy (`binary`/`managed`/`system`) | +| `INSTALLER_KEYS` | Array | Sorted list of all tool keys | + +These are used by `routes.sh` for command routing, by `b ware` (with no arguments) for listing available tools, and by `b help` for the help output. + +**Important**: `lib/registry.sh` must be regenerated whenever an installer is added, removed, or has its metadata header changed. Run: + +```bash +./scripts/generate_registry.sh +``` + +## 7. Uninstallation Flow + +### 7.1 Named Uninstall (`b rb `) + +``` +b rb bat + │ + ▼ +uninstall_tool "bat" + │ + ├── 1. execute_rollback "bat" + │ → Reads bat.cmds line-by-line (LIFO) + │ → Executes: rm binary, rm env snippet, rm alias snippet, etc. + │ → Deletes bat.cmds + │ + ├── 2. Reference-count system dependencies + │ → For each dep in registry_get_sys_deps("bat"): + │ → Count how many OTHER tools also list this dep + │ → If count == 0: pkg_remove the dep + │ → If count > 0: keep it + │ + ├── 3. registry_remove_tool "bat" + │ → Deletes .tools.bat from registry.json + │ + └── 4. Remove "INSTALL: bat" from history.log +``` + +### 7.2 Multi-Tool Uninstall (`b rb bat,yazi,zoxide`) + +The argument is split on commas and `uninstall_tool` is called for each tool sequentially. + +### 7.3 Bare Rollback (`b rb`) + +Reads the last line of `history.log`. If it is `INSTALL: `, calls `uninstall_tool` for that tool. + +### 7.4 Savepoint Rollback (`b rb `) + +If the argument doesn't match a registered tool, it is treated as a savepoint name. The system walks `history.log` backwards, uninstalling each tool encountered until it reaches the matching `SAVEPOINT:` entry. + +## 8. Strategy-Specific Patterns + +### 8.1 Binary Strategy Example (Bat) + +```bash +# Detect arch, fetch latest tag from GitHub +latest_tag=$(github_get_latest_release "sharkdp/bat") + +# Download the matching release asset +github_download_asset "sharkdp/bat" "$latest_tag" "bat-${latest_tag}-${target}\.tar\.gz" "$archive" + +# Extract, copy binary to $BOOTSTRAP_BIN, make executable +cp "$extract_dir/bat" "$BOOTSTRAP_BIN/bat" +chmod +x "$BOOTSTRAP_BIN/bat" +track_file "$BOOTSTRAP_BIN/bat" + +# Register +register_tool "bat" "binary" "$latest_tag" "github:sharkdp/bat" +``` + +### 8.2 Managed Strategy Example (Node.js via NVM) + +```bash +# Download and extract NVM into $BOOTSTRAP_RUNTIMES/nvm +download_file "$nvm_url" "$TMP_DIR/nvm.tar.gz" +tar -xzf "$TMP_DIR/nvm.tar.gz" -C "$BOOTSTRAP_RUNTIMES/nvm" --strip-components=1 +track_dir "$BOOTSTRAP_RUNTIMES/nvm" + +# Write env snippet so NVM loads on shell startup +write_env_snippet "node" "$content" + +# Use NVM to install Node LTS +nvm install --lts + +# Register (NVM manages the actual node binary) +register_tool "node" "managed" "$latest_tag" "github:nvm-sh/nvm" +``` + +### 8.3 System Strategy Example (Docker) + +```bash +# Install via system package manager +pkg_install "arch:docker|debian:docker.io|fedora:docker" + +# Register the dependency for reference counting +registry_add_sys_deps "docker" "arch:docker|debian:docker.io|fedora:docker" + +# Additional system configuration (groups, systemd) +sudo usermod -aG docker "$USER" +add_rollback_cmd "sudo gpasswd -d $USER docker || true" + +# Register +register_tool "docker" "system" "" "os-package-manager" +``` diff --git a/docs/rollback_design.md b/docs/rollback_design.md index 8a7652e..7ee5355 100644 --- a/docs/rollback_design.md +++ b/docs/rollback_design.md @@ -3,8 +3,10 @@ ## 1. Objective Provide a robust rollback mechanism by dynamically generating an uninstallation command list during the installation process. This avoids external parsers, keeps dependencies low, and leverages native Bash execution. It also includes a stateful savepoint system to revert complex environments. -## 2. Core Concept: Dynamic Command Manifests -Instead of tracking state in data files (like JSON), the system procedurally builds a **Command Manifest** (`~/.local/state/bootstrap/uninstallers/.cmds`) as the installation progresses. Every helper action records its inverse command as an independent line in this manifest. +## 2. Core Concept: Procedural Manifests & JSON Registry +The system uses a hybrid approach: +1. **Procedural Command Manifests**: As the installation progresses, a LIFO script manifest (`$BOOTSTRAP_STATE_DIR/uninstallers/.cmds`) is built by prepending the inverse commands for each helper action (files, directories, env/alias snippets). +2. **Centralized JSON Registry**: Metadata, strategy, and system-level dependencies are tracked in a thread-safe `$BOOTSTRAP_STATE_DIR/registry.json` using `jq`. During uninstallation, this registry is used to reference-count and safely remove shared system dependencies. ## 3. History & Savepoints (`b fall` and `b rb`) To allow rolling back multiple installations or returning to a known good state, the system maintains a chronological **History Log** acting as a stack. @@ -33,17 +35,19 @@ INSTALL: nvim ``` ### C. Bare Rollback (`b rb`) -When `b rb` is executed without arguments, it rolls back the single most recent change: +When `b rb` is executed without arguments, it rolls back the single most recent installation: 1. Reads the last line of the history log (e.g., `INSTALL: nvim`). -2. Executes the command manifest for `nvim`. -3. Deletes the last line from the history log. +2. Runs `uninstall_tool nvim` which executes `nvim.cmds` and cleans up registry entries. -### D. Savepoint Rollback (`b rb `) -When `b rb init` is executed, it rolls back all changes made after that savepoint: +### D. Named Rollback (`b rb ,...`) +Users can uninstall specific tools by name (e.g., `b rb nvim` or `b rb nvim,yazi`). The system runs the corresponding uninstaller manifests, reference-counts and removes any orphaned system dependencies, and cleans up the history log and registry. + +### E. Savepoint Rollback (`b rb `) +If the argument does not match an installed tool in the registry, it is treated as a savepoint: 1. Parses the history log from bottom to top. -2. For each `INSTALL: ` encountered, it executes the rollback manifest for ``. -3. Stops when it reaches `SAVEPOINT: init`. -4. Truncates the history log back to the savepoint. +2. For each `INSTALL: ` encountered, it runs `uninstall_tool `. +3. Stops when it reaches the specified `SAVEPOINT: `. +4. Truncates the history log back to that savepoint. ## 4. Required Abstractions & Helper Modifications @@ -64,22 +68,18 @@ add_rollback_cmd() { } ``` -### C. Modifying Existing Helpers -Existing helpers automatically generate their own inverse commands. -- **`pkg_install`:** +### C. Helper Operations +Helper functions record their inverse actions directly to the manifest: +- **`write_env_snippet` / `write_alias_snippet` / `write_completion_snippet`:** ```bash - add_rollback_cmd "pkg_remove $pkg" + add_rollback_cmd "rm -f \"\$BOOTSTRAP_DIR/env.d/\$snippet_name.sh\"" ``` -- **`write_env_snippet` / `write_alias_snippet`:** +- **`track_file` / `track_dir`**: ```bash - add_rollback_cmd "rm -f \"$HOME/.config/bootstrap/env.d/$snippet_name.sh\"" + track_file() { add_rollback_cmd "sudo rm -f '$1'"; } + track_dir() { add_rollback_cmd "sudo rm -rf '$1'"; } ``` - -### D. New File Tracking Helpers -```bash -track_file() { add_rollback_cmd "sudo rm -f '$1'"; } -track_dir() { add_rollback_cmd "sudo rm -rf '$1'"; } -``` +Note: distro packages are not tracked via `add_rollback_cmd`. Instead, installers declare them as system dependencies (see below). ## 5. The Rollback Execution (`b rollback `) Execution is line-by-line and fault-tolerant, allowing safe recovery even if a user injects a malformed command. @@ -102,9 +102,11 @@ Because `b ware ` allows users to modify installation scripts: 2. **Fault Isolation:** The `eval` loop ensures that a syntax error in one custom rollback step doesn't crash the removal of other tracked packages. ## 7. Handling Shared Dependencies -The `pkg_remove` helper utilizes reference counting via simple text files (e.g., `~/.local/state/bootstrap/packages/curl`). -- **On `pkg_install`**: Append tool name. -- **On `pkg_remove`**: Remove tool name. If empty, proceed with system uninstallation. +System dependencies installed via `pkg_install` must be registered via `registry_add_sys_deps ...`. +When a tool is uninstalled: +1. The registry is checked to see if any other installed tool still lists those dependencies. +2. If the reference count drops to `0`, the dependency is automatically removed using `pkg_remove`. +3. If other tools still depend on it, it is kept. ## 8. Fault Tolerance, Resumability, and Interrupted Installations diff --git a/readme.md b/readme.md index dc41a48..30aa3af 100644 --- a/readme.md +++ b/readme.md @@ -79,7 +79,7 @@ It automatically fuzzy-finds the folder in case there is no exact match. Also, i ### Rollbacks and Savepoints (`b rb` and `b fall`) -Bootstrap CLI features a powerful, procedural rollback system. It strictly tracks every extracted binary, configuration snippet, and package manager transaction to ensure your environment stays clean. +Bootstrap CLI features a robust rollback and uninstallation system driven by procedural manifests and a centralized JSON registry. To safely uninstall the very last tool you installed (including wiping its shell paths and aliases): @@ -87,11 +87,21 @@ To safely uninstall the very last tool you installed (including wiping its shell b rb ``` +To uninstall specific tools by name (supporting single or comma-separated lists of tools): + +```bash +b rb nvim +b rb bat,yazi,zoxide +``` + +Shared system package dependencies are reference-counted in the registry and will be automatically cleaned up only when the last tool depending on them is removed. + To create a named savepoint before experimenting with your setup: ```bash b fall pre_dev_setup ``` +*(Note: Savepoint names cannot conflict with the names of any available tools).* To completely roll back all installations made after that savepoint, restoring your system back to that exact state: @@ -177,6 +187,20 @@ b gone -f Then reload your shell configuration or run `unset -f b` to clear the function definition from your current terminal session. +## Directory Layout + +Bootstrap isolates all of its components and installed software inside XDG-compliant directories. You can override these variables in your shell environment, but they default to the following: + +| Variable | Default | Purpose | +| :--- | :--- | :--- | +| `BOOTSTRAP_DIR` | `~/.config/bootstrap` | Core configuration, libraries, shell snippets (`env.d`, `aliases.d`, `completions.d`), and local installers | +| `BOOTSTRAP_DATA_DIR` | `~/.local/share/bootstrap` | Active installation bin files, optional components, and runtime installations | +| `BOOTSTRAP_STATE_DIR` | `~/.local/state/bootstrap` | Stateful registry, logs, and rollback/uninstall manifests | +| `BOOTSTRAP_CACHE_DIR` | `~/.cache/bootstrap` | Downloaded files cache and temporary workspace files | +| `BOOTSTRAP_BIN` | `$BOOTSTRAP_DATA_DIR/bin` | Target directory for all installed tools' binaries | +| `BOOTSTRAP_OPT` | `$BOOTSTRAP_DATA_DIR/opt` | Destination for complex, multi-file software distributions | +| `BOOTSTRAP_RUNTIMES` | `$BOOTSTRAP_DATA_DIR/runtimes` | Workspace for programming language runtime managers (e.g. NVM) | + ## Development If you are developing this tool locally: