diff --git a/VERSION b/VERSION index 6085e94..227cea2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.1 +2.0.0 diff --git a/docs/rollback_design.md b/docs/rollback_design.md new file mode 100644 index 0000000..0a27d99 --- /dev/null +++ b/docs/rollback_design.md @@ -0,0 +1,107 @@ +# 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: 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. + +## 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 `) +The `b fall` command simply appends a marker to the history log. +```bash +echo "SAVEPOINT: $name" >> "$HOME/.local/state/bootstrap/history.log" +``` + +### B. Tracking Installations +Whenever an installation successfully completes, the `b` CLI appends an install marker: +```bash +echo "INSTALL: nvim" >> "$HOME/.local/state/bootstrap/history.log" +``` +**Example History Log:** +```text +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 change: +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. + +### D. Savepoint Rollback (`b rb `) +When `b rb init` is executed, it rolls back all changes made after that 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. + +## 4. Required Abstractions & Helper Modifications + +### A. Context Initialization +Before executing an installer script, the `b` CLI initializes the command list: +```bash +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. +```bash +add_rollback_cmd() { + local cmd="$1" + sed -i "1i $cmd" "$BOOTSTRAP_UNINSTALLER_CMDS" +} +``` + +### C. Modifying Existing Helpers +Existing helpers automatically generate their own inverse commands. +- **`pkg_install`:** + ```bash + add_rollback_cmd "pkg_remove $pkg" + ``` +- **`write_env_snippet` / `write_alias_snippet`:** + ```bash + add_rollback_cmd "rm -f \"$HOME/.config/bootstrap/env.d/$snippet_name.sh\"" + ``` + +### D. New File Tracking Helpers +```bash +track_file() { add_rollback_cmd "sudo rm -f '$1'"; } +track_dir() { add_rollback_cmd "sudo rm -rf '$1'"; } +``` + +## 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. + +```bash +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 ` allows users to modify installation scripts: +1. **Dynamic Adaptation:** The manifest is built *during* execution, adapting to whatever packages the user manually added. +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.