7.1 KiB
Bootstrap CLI: Procedural Rollback System Design
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: Procedural Manifests & JSON Registry
The system uses a hybrid approach:
- Procedural Command Manifests: As the installation progresses, a LIFO script manifest (
$BOOTSTRAP_STATE_DIR/uninstallers/<tool>.cmds) is built by prepending the inverse commands for each helper action (files, directories, env/alias snippets). - Centralized JSON Registry: Metadata, strategy, and system-level dependencies are tracked in a thread-safe
$BOOTSTRAP_STATE_DIR/registry.jsonusingjq. 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.
Location: ~/.local/state/bootstrap/history.log
A. Creating a Savepoint (b fall <name>)
The b fall command simply appends a marker to the history log.
echo "SAVEPOINT: $name" >> "$HOME/.local/state/bootstrap/history.log"
B. Tracking Installations
Whenever an installation successfully completes, the b CLI appends an install marker:
echo "INSTALL: nvim" >> "$HOME/.local/state/bootstrap/history.log"
Example History Log:
SAVEPOINT: init
INSTALL: rust
INSTALL: node
SAVEPOINT: dev_setup
INSTALL: yazi
INSTALL: nvim
C. Bare Rollback (b rb)
When b rb is executed without arguments, it rolls back the single most recent installation:
- Reads the last line of the history log (e.g.,
INSTALL: nvim). - Runs
uninstall_tool nvimwhich executesnvim.cmdsand cleans up registry entries.
D. Named Rollback (b rb <tool1>,<tool2>...)
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 <savepoint>)
If the argument does not match an installed tool in the registry, it is treated as a savepoint:
- Parses the history log from bottom to top.
- For each
INSTALL: <tool>encountered, it runsuninstall_tool <tool>. - Stops when it reaches the specified
SAVEPOINT: <name>. - Truncates the history log back to that savepoint.
4. Required Abstractions & Helper Modifications
A. Context Initialization
Before executing a tool script, the b CLI initializes the command list:
export BOOTSTRAP_UNINSTALLER_CMDS="$HOME/.local/state/bootstrap/uninstallers/nvim.cmds"
mkdir -p "$(dirname "$BOOTSTRAP_UNINSTALLER_CMDS")"
touch "$BOOTSTRAP_UNINSTALLER_CMDS"
B. Recording Commands (LIFO Execution)
Rollback steps are safest when executed in reverse order. A helper prepends commands to the top of the manifest.
add_rollback_cmd() {
local cmd="$1"
sed -i "1i $cmd" "$BOOTSTRAP_UNINSTALLER_CMDS"
}
C. Helper Operations
Helper functions record their inverse actions directly to the manifest:
write_env_snippet/write_alias_snippet/write_completion_snippet:add_rollback_cmd "rm -f \"\$BOOTSTRAP_DIR/env.d/\$snippet_name.sh\""track_file/track_dir: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 <tool>)
Execution is line-by-line and fault-tolerant, allowing safe recovery even if a user injects a malformed command.
log_info "Rolling back..."
while IFS= read -r cmd; do
[ -z "$cmd" ] && continue
log_info "Executing: $cmd"
eval "$cmd" || log_warn "Failed to execute rollback step: $cmd"
done < "$BOOTSTRAP_UNINSTALLER_CMDS"
rm -f "$BOOTSTRAP_UNINSTALLER_CMDS"
log_success "Rollback complete."
6. Resilience Against User Modifications
Because b ware <tool> allows users to modify tool scripts:
- Dynamic Adaptation: The manifest is built during execution, adapting to whatever packages the user manually added.
- Fault Isolation: The
evalloop ensures that a syntax error in one custom rollback step doesn't crash the removal of other tracked packages.
7. Handling Shared Dependencies
System dependencies installed via pkg_install must be registered via registry_add_sys_deps <tool> <dep1> <dep2>....
When a tool is uninstalled:
- The registry is checked to see if any other installed tool still lists those dependencies.
- If the reference count drops to
0, the dependency is automatically removed usingpkg_remove. - If other tools still depend on it, it is kept.
8. Fault Tolerance, Resumability, and Interrupted Installations
To handle failures during installation (e.g., network drops, script errors, or user cancellation via Ctrl+C), the CLI incorporates a transactional approach that balances automatic rollback and resumability:
A. The Interruption Trap & Prompt
When running a tool, the central router (lib/routes.sh) traps SIGINT and SIGTERM signals. If the installation fails or is interrupted:
- The trap catches the event and stops execution.
- The user is prompted interactively:
- Rollback (r): Invokes
execute_rollback <tool>immediately to clean up all partial modifications. - Keep (k): Preserves the partial changes and leaves the
.cmdsmanifest intact.
- Rollback (r): Invokes
- In non-interactive environments (e.g., CI/CD or scripts), the CLI defaults to automatic rollback to keep the system clean.
B. Resuming via Preserved Manifests
If the user chooses to keep the partial state and runs b <tool> again:
setup_uninstaller_contextdetects that a manifest already exists and that the tool was not successfully installed (noINSTALL: <tool>in the history log).- It preserves the existing manifest instead of wiping it.
- As the script runs again from the top, new rollback commands are prepended to the existing manifest, maintaining the correct LIFO order without losing the tracking of previously completed steps.
C. Resumable Downloads (Caching Layer)
To make rerunning an interrupted script fast and efficient, installers use download_file <url> <dest> instead of raw curl:
- It downloads the payload to a central cache directory:
~/.local/state/bootstrap/cache/. - It uses
curl -C -to continue the download from the byte offset where it was interrupted. - Once completed, it copies the cached file to the tool's temp directory.
- Distro package manager commands (
pkg_install) and shell snippets (write_env_snippet) are naturally idempotent, allowing the script to breeze through already completed steps in milliseconds and resume exactly where the heavy work failed.