Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 393868610f | |||
| 368dea1bbd | |||
| b31a326ca1 | |||
| dc73804416 | |||
| f118d66ec1 | |||
| 0486755771 | |||
| 725e3879d8 | |||
| 234112f304 | |||
| 6fde048250 | |||
| 9ce16a1f2b | |||
| 57a11e16a3 | |||
| b66fb4a354 | |||
| a56bee0b9c | |||
| 7e48f01cca |
@@ -77,20 +77,10 @@ Every installer follows this exact boilerplate structure. Copy this and fill in
|
||||
# <ToolName> Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -259,7 +249,7 @@ inject_block "$config_file" "<tool> init" 'eval "$(tool init bash)"'
|
||||
3. **Confirmation prompts**: Always ask before installing. Check if already installed first.
|
||||
4. **Idempotent**: Installers must be safe to re-run. Use `inject_block` (not append) for shell configs.
|
||||
5. **No hardcoded paths**: Use `$HOME`, library functions, and `detect_*` helpers.
|
||||
6. **Error handling**: Use `set -euo pipefail` after sourcing `bootstrap.sh`.
|
||||
7. **Metascript boilerplate**: The first 22 lines of every installer are identical — always copy them verbatim.
|
||||
6. **Error handling**: Use `set -euo pipefail` after the guard block.
|
||||
7. **CLI Enforcement Guard**: Always copy the standalone execution guard block verbatim to the top of your installer script to prevent direct execution.
|
||||
8. **`main "$@"`**: Always end with this pattern to pass through CLI arguments.
|
||||
9. **Clean Official Scripts**: When implementing official curl/install scripts provided in the prompt, strip them of bloat, macOS/Windows support, and redundant shell setups before writing the script.
|
||||
|
||||
61
bootstrap.sh
61
bootstrap.sh
@@ -18,6 +18,10 @@ is_sourced=false
|
||||
if [ -n "${BASH_SOURCE[0]:-}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then
|
||||
is_sourced=true
|
||||
fi
|
||||
# Detect eval from installers based on presence of specific variables
|
||||
if [ -n "${METASCRIPT_URL:-}" ]; then
|
||||
is_sourced=true
|
||||
fi
|
||||
|
||||
# Locate or download libraries so that sourced installers can use them
|
||||
BOOTSTRAP_DIR="${BOOTSTRAP_DIR:-$HOME/.config/bootstrap}"
|
||||
@@ -25,22 +29,19 @@ _SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null || pwd)"
|
||||
|
||||
if [ -f "$_SCRIPT_DIR/lib/common.sh" ]; then
|
||||
# Dev/local mode: source directly from repo
|
||||
. "$_SCRIPT_DIR/lib/common.sh"
|
||||
. "$_SCRIPT_DIR/lib/platform.sh"
|
||||
. "$_SCRIPT_DIR/lib/shell_config.sh"
|
||||
BOOTSTRAP_SOURCE_DIR="$_SCRIPT_DIR"
|
||||
elif [ -f "$BOOTSTRAP_DIR/lib/common.sh" ]; then
|
||||
# Installed mode: source from bootstrap dir
|
||||
. "$BOOTSTRAP_DIR/lib/common.sh"
|
||||
. "$BOOTSTRAP_DIR/lib/platform.sh"
|
||||
. "$BOOTSTRAP_DIR/lib/shell_config.sh"
|
||||
BOOTSTRAP_SOURCE_DIR="$BOOTSTRAP_DIR"
|
||||
else
|
||||
# Standalone/remote mode: download to a temp directory and source
|
||||
export BOOTSTRAP_TMP_DIR
|
||||
BOOTSTRAP_TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$BOOTSTRAP_TMP_DIR"' EXIT
|
||||
BOOTSTRAP_SOURCE_DIR="$BOOTSTRAP_TMP_DIR"
|
||||
|
||||
_BASE_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master"
|
||||
_LIBS=("lib/common.sh" "lib/platform.sh" "lib/shell_config.sh")
|
||||
_LIBS=("lib/common.sh" "lib/rollback.sh" "lib/platform.sh" "lib/shell_config.sh")
|
||||
|
||||
_curl_args=()
|
||||
for _lib in "${_LIBS[@]}"; do
|
||||
@@ -48,15 +49,17 @@ else
|
||||
_curl_args+=("-o" "$BOOTSTRAP_TMP_DIR/$_lib" "$_BASE_URL/$_lib")
|
||||
done
|
||||
curl -fsSL "${_curl_args[@]}" 2>/dev/null
|
||||
|
||||
if [ -f "$BOOTSTRAP_TMP_DIR/lib/common.sh" ]; then
|
||||
. "$BOOTSTRAP_TMP_DIR/lib/common.sh"
|
||||
. "$BOOTSTRAP_TMP_DIR/lib/platform.sh"
|
||||
. "$BOOTSTRAP_TMP_DIR/lib/shell_config.sh"
|
||||
else
|
||||
echo "Error: Failed to download bootstrap libraries." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$BOOTSTRAP_SOURCE_DIR/lib/common.sh" ]; then
|
||||
. "$BOOTSTRAP_SOURCE_DIR/lib/common.sh"
|
||||
. "$BOOTSTRAP_SOURCE_DIR/lib/rollback.sh"
|
||||
. "$BOOTSTRAP_SOURCE_DIR/lib/platform.sh"
|
||||
. "$BOOTSTRAP_SOURCE_DIR/lib/shell_config.sh"
|
||||
init_rollback_system
|
||||
else
|
||||
echo "Error: Failed to locate or download bootstrap libraries." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install/update the bootstrap loader and download all necessary files
|
||||
@@ -66,6 +69,8 @@ install_bootstrap() {
|
||||
|
||||
local routes_dir="$HOME/.config/bootstrap"
|
||||
mkdir -p "$routes_dir"
|
||||
mkdir -p "$routes_dir/env.d"
|
||||
mkdir -p "$routes_dir/aliases.d"
|
||||
|
||||
# List of all files to download/copy
|
||||
local files=(
|
||||
@@ -74,6 +79,7 @@ install_bootstrap() {
|
||||
"lib/routes.sh"
|
||||
"lib/registry.sh"
|
||||
"lib/common.sh"
|
||||
"lib/rollback.sh"
|
||||
"lib/platform.sh"
|
||||
"lib/shell_config.sh"
|
||||
"commands/help.sh"
|
||||
@@ -125,13 +131,15 @@ install_bootstrap() {
|
||||
# 2. Clean up old loader block if it exists
|
||||
remove_block "$config_file" "bootstrap-cli setup"
|
||||
|
||||
# 3. Append the new lightweight loader block
|
||||
# 3. Append the new lightweight loader block that sources modular configs
|
||||
log_info "Adding bootstrap loader to $config_file..."
|
||||
cat << 'EOF' >> "$config_file"
|
||||
|
||||
# >>> bootstrap-cli setup >>>
|
||||
export BOOTSTRAP_DIR="$HOME/.config/bootstrap"
|
||||
[ -f "$BOOTSTRAP_DIR/b.sh" ] && . "$BOOTSTRAP_DIR/b.sh"
|
||||
for f in "$BOOTSTRAP_DIR/env.d/"*.sh; do [ -r "$f" ] && . "$f"; done
|
||||
for f in "$BOOTSTRAP_DIR/aliases.d/"*.sh; do [ -r "$f" ] && . "$f"; done
|
||||
# <<< bootstrap-cli setup <<<
|
||||
EOF
|
||||
|
||||
@@ -172,19 +180,14 @@ if [ "$is_sourced" = false ]; then
|
||||
clear 2>/dev/null || true
|
||||
|
||||
# Locate or download pixel_art.ansi and VERSION
|
||||
_art_file="$_SCRIPT_DIR/assets/pixel_art.ansi"
|
||||
_version_file="$_SCRIPT_DIR/VERSION"
|
||||
_art_file="$BOOTSTRAP_SOURCE_DIR/assets/pixel_art.ansi"
|
||||
_version_file="$BOOTSTRAP_SOURCE_DIR/VERSION"
|
||||
|
||||
if [ ! -f "$_art_file" ]; then
|
||||
if [ -n "${BOOTSTRAP_TMP_DIR:-}" ] && [ -d "$BOOTSTRAP_TMP_DIR" ]; then
|
||||
_art_file="$BOOTSTRAP_TMP_DIR/pixel_art.ansi"
|
||||
_version_file="$BOOTSTRAP_TMP_DIR/VERSION"
|
||||
_base_url="${_BASE_URL:-https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master}"
|
||||
if [ ! -f "$_art_file" ]; then
|
||||
curl -fsSL -o "$_art_file" "$_base_url/assets/pixel_art.ansi" 2>/dev/null
|
||||
curl -fsSL -o "$_version_file" "$_base_url/VERSION" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
if [ ! -f "$_art_file" ] && [ -n "${BOOTSTRAP_TMP_DIR:-}" ]; then
|
||||
_base_url="${_BASE_URL:-https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master}"
|
||||
mkdir -p "$(dirname "$_art_file")"
|
||||
curl -fsSL -o "$_art_file" "$_base_url/assets/pixel_art.ansi" 2>/dev/null || true
|
||||
curl -fsSL -o "$_version_file" "$_base_url/VERSION" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$_art_file" ]; then
|
||||
|
||||
107
docs/rollback_design.md
Normal file
107
docs/rollback_design.md
Normal file
@@ -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/<tool>.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 <name>`)
|
||||
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 <name>`)
|
||||
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: <tool>` encountered, it executes the rollback manifest for `<tool>`.
|
||||
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 <tool>`)
|
||||
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 <tool>` 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.
|
||||
@@ -6,20 +6,10 @@
|
||||
# Antigravity CLI Installer Script (Linux Only)
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -140,27 +130,20 @@ install_agy() {
|
||||
cp "$extracted_binary" "$BINARY_PATH"
|
||||
chmod +x "$BINARY_PATH"
|
||||
rm -rf "$staging_dir"
|
||||
|
||||
track_file "$BINARY_PATH"
|
||||
|
||||
log_success "Antigravity CLI successfully installed to $BINARY_PATH."
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Ensure $TARGET_DIR is in PATH for shell configurations if not present
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
|
||||
local path_content='export PATH="$HOME/.local/bin:$PATH"'
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
if [ -f "$config_file" ] && ! grep -q '\.local/bin' "$config_file" 2>/dev/null; then
|
||||
log_info "Adding ~/.local/bin to PATH in $config_file..."
|
||||
inject_block "$config_file" "local-bin path" "$path_content"
|
||||
|
||||
# Source if modified (only for bashrc)
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
remove_block "$config_file" "local-bin path"
|
||||
done
|
||||
|
||||
write_env_snippet "local-bin" 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
}
|
||||
|
||||
run_handoff() {
|
||||
|
||||
101
installers/install_asciicinema.sh
Normal file
101
installers/install_asciicinema.sh
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tool: asciicinema
|
||||
# DisplayName: asciicinema
|
||||
# Description: Install asciinema terminal recorder
|
||||
#
|
||||
# asciinema Installer Script
|
||||
#
|
||||
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TMP_DIR="$(make_temp_dir)"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
install_asciicinema() {
|
||||
local latest_tag=""
|
||||
if has_command curl; then
|
||||
log_info "Fetching latest asciinema version from GitHub..."
|
||||
latest_tag=$(curl -sL https://api.github.com/repos/asciinema/asciinema/releases/latest \
|
||||
| grep '"tag_name":' | head -n1 \
|
||||
| sed -E 's/.*"tag_name": "([^"]+)".*/\1/' || true)
|
||||
fi
|
||||
|
||||
if [ -z "$latest_tag" ]; then
|
||||
latest_tag="v3.2.1" # fallback
|
||||
log_warn "Failed to fetch latest version from GitHub. Falling back to: $latest_tag"
|
||||
else
|
||||
log_info "Latest asciinema version found: $latest_tag"
|
||||
fi
|
||||
|
||||
|
||||
if has_command asciinema; then
|
||||
local current_version
|
||||
current_version=$(asciinema --version | head -n1 | awk '{print $2}')
|
||||
if [[ "$current_version" != v* ]]; then
|
||||
current_version="v${current_version}"
|
||||
fi
|
||||
|
||||
if [[ "$current_version" == "$latest_tag" ]]; then
|
||||
log_info "asciinema ${latest_tag} is already installed."
|
||||
if ! confirm "Reinstall/Upgrade asciinema?"; then
|
||||
log_info "Skipping asciinema installation."
|
||||
return
|
||||
fi
|
||||
else
|
||||
if ! confirm "Detecting asciinema ${current_version}. Upgrade to ${latest_tag}?"; then
|
||||
log_info "Skipping asciinema installation."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if ! confirm "Install asciinema ${latest_tag}?"; then
|
||||
log_info "Skipping asciinema installation."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
# Detect architecture
|
||||
local arch
|
||||
arch=$(detect_arch)
|
||||
local asciinema_arch=""
|
||||
case "$arch" in
|
||||
x86_64) asciinema_arch="x86_64-unknown-linux-gnu" ;;
|
||||
arm64) asciinema_arch="aarch64-unknown-linux-gnu" ;;
|
||||
*) log_error "Unsupported architecture: $arch"; exit 1 ;;
|
||||
esac
|
||||
|
||||
local download_url="https://github.com/asciinema/asciinema/releases/download/${latest_tag}/asciinema-${asciinema_arch}"
|
||||
|
||||
log_info "Downloading asciinema ${latest_tag} for ${arch}..."
|
||||
curl -fsSL "$download_url" -o "$TMP_DIR/asciinema"
|
||||
|
||||
log_info "Installing asciinema to /usr/local/bin..."
|
||||
sudo cp "$TMP_DIR/asciinema" /usr/local/bin/asciinema
|
||||
sudo chmod +x /usr/local/bin/asciinema
|
||||
track_file "/usr/local/bin/asciinema"
|
||||
|
||||
# Create compatibility symlink matching the installer name spelling
|
||||
log_info "Creating compatibility symlink for asciicinema..."
|
||||
sudo ln -sf /usr/local/bin/asciinema /usr/local/bin/asciicinema
|
||||
track_file "/usr/local/bin/asciicinema"
|
||||
|
||||
log_success "asciinema ${latest_tag} installed."
|
||||
}
|
||||
|
||||
main() {
|
||||
install_asciicinema
|
||||
|
||||
echo
|
||||
log_success "asciinema installation complete."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -6,20 +6,10 @@
|
||||
# Bat Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -77,6 +67,7 @@ install_bat() {
|
||||
|
||||
log_info "Installing Bat package..."
|
||||
sudo apt install -y "$TMP_DIR/bat.deb"
|
||||
add_rollback_cmd "sudo apt remove -y bat"
|
||||
|
||||
else
|
||||
log_error "Unsupported distribution."
|
||||
@@ -85,31 +76,16 @@ install_bat() {
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
|
||||
local content="alias cat='bat --paging=never -p'"
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
local target_file="$config_file"
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
# Clean up old block from ~/.bashrc if present to avoid duplication
|
||||
remove_block "$config_file" "bat alias"
|
||||
target_file="$HOME/.bash_aliases"
|
||||
# Ensure the file exists
|
||||
if [ ! -f "$target_file" ]; then
|
||||
touch "$target_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Adding bat alias to $target_file..."
|
||||
inject_block "$target_file" "bat alias" "$content"
|
||||
|
||||
# Source if modified (only for bashrc)
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
log_info "Sourcing $config_file..."
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
remove_block "$config_file" "bat alias"
|
||||
done
|
||||
if [ -f "$HOME/.bash_aliases" ]; then
|
||||
remove_block "$HOME/.bash_aliases" "bat alias"
|
||||
fi
|
||||
|
||||
write_alias_snippet "bat" "alias cat='bat --paging=never -p'"
|
||||
}
|
||||
|
||||
main() {
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
# Node.js and NVM Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -61,12 +51,18 @@ install_nvm() {
|
||||
log_info "Extracting NVM archive directly to $HOME/.nvm (stripping versioned subfolder to keep config generic)..."
|
||||
mkdir -p "$HOME/.nvm"
|
||||
tar -xzf "$TMP_DIR/nvm.tar.gz" -C "$HOME/.nvm" --strip-components=1
|
||||
|
||||
track_dir "$HOME/.nvm"
|
||||
|
||||
log_success "NVM source files successfully extracted to $HOME/.nvm."
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
for config_file in "${target_files[@]}"; do
|
||||
remove_block "$config_file" "nvm setup"
|
||||
done
|
||||
|
||||
local content
|
||||
content=$(cat << 'EOF'
|
||||
@@ -76,16 +72,7 @@ export NVM_DIR="$HOME/.nvm"
|
||||
EOF
|
||||
)
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
log_info "Adding NVM configuration block to $config_file..."
|
||||
inject_block "$config_file" "nvm setup" "$content"
|
||||
|
||||
# Source if modified (only for bashrc)
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
log_info "Sourcing $config_file..."
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
write_env_snippet "node" "$content"
|
||||
}
|
||||
|
||||
install_node() {
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tool: nvim
|
||||
# DisplayName: Neovim
|
||||
# Description: Install Neovim 0.11.7 and configuration
|
||||
# Description: Install Neovim 0.12.0 and configuration
|
||||
#
|
||||
# Neovim Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NVIM_VERSION="0.11.7"
|
||||
NVIM_VERSION="0.12.0"
|
||||
NVIM_INSTALL_DIR="/opt/nvim"
|
||||
NVIM_CONFIG_REPO="https://git.adityagupta.dev/sortedcord/editor.git"
|
||||
NVIM_CONFIG_DIR="$HOME/.config/nvim"
|
||||
@@ -54,6 +44,10 @@ install_packages() {
|
||||
"fedora:gcc-c++"
|
||||
|
||||
create_fd_symlink
|
||||
|
||||
log_info "Installing tree-sitter-cli globally..."
|
||||
sudo npm install -g tree-sitter-cli
|
||||
add_rollback_cmd "sudo npm uninstall -g tree-sitter-cli"
|
||||
}
|
||||
|
||||
install_nvim() {
|
||||
@@ -93,6 +87,9 @@ install_nvim() {
|
||||
sudo mv "$TMP_DIR/nvim-${nvim_arch}" "$NVIM_INSTALL_DIR"
|
||||
|
||||
sudo ln -sf "$NVIM_INSTALL_DIR/bin/nvim" /usr/local/bin/nvim
|
||||
|
||||
track_dir "$NVIM_INSTALL_DIR"
|
||||
track_file "/usr/local/bin/nvim"
|
||||
|
||||
log_success "Installed:"
|
||||
nvim --version | head -n1
|
||||
@@ -109,28 +106,32 @@ install_config() {
|
||||
|
||||
log_info "Cloning configuration to $NVIM_CONFIG_DIR..."
|
||||
git clone "$NVIM_CONFIG_REPO" "$NVIM_CONFIG_DIR"
|
||||
track_dir "$NVIM_CONFIG_DIR"
|
||||
log_success "Configuration installed."
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Clean up legacy inline edits from bashrc and bash_aliases
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
local modified=false
|
||||
|
||||
if add_alias_if_missing "$config_file" "vim" "nvim"; then
|
||||
modified=true
|
||||
fi
|
||||
|
||||
if add_env_if_missing "$config_file" "EDITOR" "nvim"; then
|
||||
modified=true
|
||||
fi
|
||||
|
||||
# Source if modified (only for bashrc, and not when sourced to prevent recursion)
|
||||
if [ "$modified" = true ] && [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
. "$config_file" 2>/dev/null || true
|
||||
if [ -f "$config_file" ]; then
|
||||
local tmp_file
|
||||
tmp_file=$(mktemp)
|
||||
sed '/^export EDITOR="nvim"/d' "$config_file" > "$tmp_file"
|
||||
cat "$tmp_file" > "$config_file"
|
||||
rm -f "$tmp_file"
|
||||
fi
|
||||
done
|
||||
if [ -f "$HOME/.bash_aliases" ]; then
|
||||
local tmp_file
|
||||
tmp_file=$(mktemp)
|
||||
sed '/^alias vim="nvim"/d' "$HOME/.bash_aliases" > "$tmp_file"
|
||||
cat "$tmp_file" > "$HOME/.bash_aliases"
|
||||
rm -f "$tmp_file"
|
||||
fi
|
||||
|
||||
write_alias_snippet "nvim" 'alias vim="nvim"'
|
||||
write_env_snippet "nvim" 'export EDITOR="nvim"'
|
||||
}
|
||||
|
||||
main() {
|
||||
|
||||
@@ -17,20 +17,10 @@
|
||||
# curl -fsSL https://get.pnpm.io/install.sh | ENV="$HOME/.bashrc" SHELL="$(which bash)" bash -
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -183,13 +173,18 @@ install_pnpm() {
|
||||
}
|
||||
fi
|
||||
|
||||
track_dir "$HOME/.local/share/pnpm"
|
||||
log_success "pnpm v${version} installed successfully!"
|
||||
}
|
||||
|
||||
# ─── Shell Configuration ─────────────────────────────────────────────
|
||||
|
||||
configure_shell() {
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
for config_file in "${target_files[@]}"; do
|
||||
remove_block "$config_file" "pnpm setup"
|
||||
done
|
||||
|
||||
# pnpm's `setup --force` configures PNPM_HOME and PATH automatically,
|
||||
# but we also add an env block to ensure PNPM_HOME is set consistently.
|
||||
@@ -204,15 +199,7 @@ esac
|
||||
EOF
|
||||
)
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
log_info "Configuring pnpm in $config_file..."
|
||||
inject_block "$config_file" "pnpm setup" "$content"
|
||||
|
||||
# Source if modified (only for bashrc)
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
write_env_snippet "pnpm" "$content"
|
||||
}
|
||||
|
||||
# ─── Main ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -6,24 +6,20 @@
|
||||
# Rust Installer Script (Simplified Local Rustup Init)
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TMP_DIR="$(make_temp_dir)"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Ensure we have curl
|
||||
install_downloader() {
|
||||
if ! has_command curl; then
|
||||
@@ -77,17 +73,10 @@ install_rust() {
|
||||
|
||||
local url="https://static.rust-lang.org/rustup/dist/${target}/rustup-init"
|
||||
|
||||
local tmpdir
|
||||
tmpdir="$(make_temp_dir)"
|
||||
cleanup() {
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
local dest="$tmpdir/rustup-init"
|
||||
local dest="$TMP_DIR/rustup-init"
|
||||
|
||||
log_info "Downloading rustup-init..."
|
||||
curl -fsSL \"$url\" -o \"$dest\"| curl -fsSL \"$url\" -o \"$dest\"
|
||||
curl -fsSL "$url" -o "$dest"
|
||||
|
||||
chmod +x "$dest"
|
||||
|
||||
@@ -96,25 +85,21 @@ install_rust() {
|
||||
# -y: skip prompts (we already confirmed)
|
||||
# --no-modify-path: let bootstrap manage the shell paths
|
||||
"$dest" -y --no-modify-path
|
||||
|
||||
add_rollback_cmd "rustup self uninstall -y"
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Add ~/.cargo/bin to PATH for the current process
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
log_info "Configuring Rust environment in $config_file..."
|
||||
local content='. "$HOME/.cargo/env"'
|
||||
|
||||
inject_block "$config_file" "rust init" "$content"
|
||||
|
||||
# Source if modified (only for bashrc)
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
remove_block "$config_file" "rust init"
|
||||
done
|
||||
|
||||
write_env_snippet "rust" '. "$HOME/.cargo/env"'
|
||||
}
|
||||
|
||||
main() {
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
# Starship Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -81,29 +71,22 @@ install_starship() {
|
||||
log_info "Installing Starship to $target_dir/starship..."
|
||||
cp "$TMP_DIR/starship" "$target_dir/starship"
|
||||
chmod +x "$target_dir/starship"
|
||||
track_file "$target_dir/starship"
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Add ~/.local/bin to PATH for the current process
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
local config_file="$HOME/.bashrc"
|
||||
if [ -f "$config_file" ]; then
|
||||
# Ensure ~/.local/bin is in PATH for this file if not already present
|
||||
if ! grep -q '\.local/bin' "$config_file" 2>/dev/null; then
|
||||
log_info "Adding ~/.local/bin to PATH in $config_file..."
|
||||
local path_content='export PATH="$HOME/.local/bin:$PATH"'
|
||||
inject_block "$config_file" "local-bin path" "$path_content"
|
||||
fi
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
for config_file in "${target_files[@]}"; do
|
||||
remove_block "$config_file" "local-bin path"
|
||||
remove_block "$config_file" "starship init"
|
||||
done
|
||||
|
||||
log_info "Adding starship initialization to $config_file..."
|
||||
local content='eval "$(starship init bash)"'
|
||||
|
||||
inject_block "$config_file" "starship init" "$content"
|
||||
|
||||
# Source to apply changes in the current context
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
write_env_snippet "local-bin" 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
write_env_snippet "starship" 'eval "$(starship init bash)"'
|
||||
}
|
||||
|
||||
main() {
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
# uv Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -91,31 +81,23 @@ install_uv() {
|
||||
cp "$TMP_DIR/uv" "$target_dir/uv"
|
||||
cp "$TMP_DIR/uvx" "$target_dir/uvx"
|
||||
chmod +x "$target_dir/uv" "$target_dir/uvx"
|
||||
track_file "$target_dir/uv"
|
||||
track_file "$target_dir/uvx"
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Add ~/.local/bin to PATH for the current process
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
# Ensure ~/.local/bin is in PATH for this file if not already present
|
||||
if ! grep -q '\.local/bin' "$config_file" 2>/dev/null; then
|
||||
log_info "Adding ~/.local/bin to PATH in $config_file..."
|
||||
local path_content='export PATH="$HOME/.local/bin:$PATH"'
|
||||
inject_block "$config_file" "local-bin path" "$path_content"
|
||||
fi
|
||||
|
||||
log_info "Adding uv completion to $config_file..."
|
||||
local content='eval "$(uv generate-shell-completion bash)"'
|
||||
inject_block "$config_file" "uv completion" "$content"
|
||||
|
||||
# Source to apply changes in the current context
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
remove_block "$config_file" "local-bin path"
|
||||
remove_block "$config_file" "uv completion"
|
||||
done
|
||||
|
||||
write_env_snippet "local-bin" 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
write_env_snippet "uv" 'eval "$(uv generate-shell-completion bash)"'
|
||||
}
|
||||
|
||||
main() {
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
# Yay Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -40,10 +30,10 @@ install_yay() {
|
||||
fi
|
||||
|
||||
local needs_install=false
|
||||
if ! pacman -Qq git &>/dev/null; then
|
||||
if ! pkg_check git; then
|
||||
needs_install=true
|
||||
fi
|
||||
if ! pacman -Qq base-devel &>/dev/null && ! pacman -Qg base-devel &>/dev/null; then
|
||||
if ! pkg_check base-devel && ! pacman -Qg base-devel &>/dev/null; then
|
||||
needs_install=true
|
||||
fi
|
||||
|
||||
@@ -71,6 +61,7 @@ install_yay() {
|
||||
|
||||
log_info "Building and installing yay..."
|
||||
makepkg -si
|
||||
add_rollback_cmd "sudo pacman -R --noconfirm yay"
|
||||
|
||||
cd "$orig_dir"
|
||||
log_info "Cleaning up installer directory..."
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
# Yazi Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -31,7 +21,11 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
add_y_wrapper() {
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
for config_file in "${target_files[@]}"; do
|
||||
remove_block "$config_file" "yazi wrapper"
|
||||
done
|
||||
|
||||
local wrapper_content
|
||||
wrapper_content=$(cat << 'EOF'
|
||||
@@ -47,16 +41,7 @@ y() {
|
||||
EOF
|
||||
)
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
log_info "Adding yazi wrapper function 'y' to $config_file..."
|
||||
inject_block "$config_file" "yazi wrapper" "$wrapper_content"
|
||||
done
|
||||
|
||||
# Source ~/.bashrc to make the alias immediately available in the current shell context (if sourced)
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
log_info "Sourcing ~/.bashrc..."
|
||||
. "$HOME/.bashrc" 2>/dev/null || true
|
||||
fi
|
||||
write_alias_snippet "yazi" "$wrapper_content"
|
||||
}
|
||||
|
||||
install_yazi() {
|
||||
@@ -84,7 +69,7 @@ install_yazi() {
|
||||
|
||||
log_info "Fetching latest Yazi version from GitHub..."
|
||||
local latest_tag
|
||||
latest_tag=$(curl -sL https://api.github.com/repos/sxyazi/yazi/releases/latest | grep '"tag_name":' | head -n1 | sed -E 's/.*"tag_name": "([^"]+)".*/\1/')
|
||||
latest_tag=$(curl -sL https://api.github.com/repos/sxyazi/yazi/releases/latest | grep '"tag_name":' | head -n1 | sed -E 's/.*"tag_name": "([^"]+)".*/\1/' || true)
|
||||
if [ -z "$latest_tag" ]; then
|
||||
latest_tag="v26.5.6"
|
||||
fi
|
||||
@@ -95,6 +80,7 @@ install_yazi() {
|
||||
|
||||
log_info "Installing Yazi package..."
|
||||
sudo apt install -y "$TMP_DIR/yazi.deb"
|
||||
add_rollback_cmd "sudo apt remove -y yazi"
|
||||
|
||||
log_info "Installing dependencies subsequently..."
|
||||
pkg_install ffmpeg jq poppler-utils fd-find ripgrep fzf zoxide resvg imagemagick 7zip || \
|
||||
@@ -116,6 +102,7 @@ install_yazi() {
|
||||
|
||||
log_info "Installing Yazi (without weak dependencies first)..."
|
||||
sudo dnf install -y yazi --setopt=install_weak_deps=False
|
||||
add_rollback_cmd "sudo dnf remove -y yazi"
|
||||
|
||||
log_info "Installing weak dependencies subsequently..."
|
||||
pkg_install yazi
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
# Zoxide Installer Script
|
||||
#
|
||||
|
||||
# Run metascript to check if the shell is bash and load libraries
|
||||
PARENT_DIR="$(dirname "$0")/.."
|
||||
METASCRIPT_LOCAL="$PARENT_DIR/bootstrap.sh"
|
||||
METASCRIPT_URL="https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master/bootstrap.sh"
|
||||
|
||||
if [ -f "$METASCRIPT_LOCAL" ]; then
|
||||
. "$METASCRIPT_LOCAL"
|
||||
else
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
eval "$(curl -fsSL "$METASCRIPT_URL")"
|
||||
else
|
||||
echo "Error: curl is not installed to fetch bootstrap.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Prevent standalone execution
|
||||
if [ -z "${_LIB_COMMON_SOURCED:-}" ]; then
|
||||
echo "Error: This script must be run through the 'b' CLI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
@@ -50,25 +40,21 @@ install_zoxide() {
|
||||
|
||||
log_info "Downloading and running the official zoxide installer..."
|
||||
curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
|
||||
track_file "$HOME/.local/bin/zoxide"
|
||||
}
|
||||
|
||||
configure_shell() {
|
||||
# Add ~/.local/bin to PATH for the current process
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# Clean up legacy in-place configuration blocks
|
||||
IFS=' ' read -ra target_files <<< "$(get_shell_configs)"
|
||||
|
||||
for config_file in "${target_files[@]}"; do
|
||||
log_info "Adding zoxide initialization to $config_file..."
|
||||
local content="eval \"\$(zoxide init --cmd cd bash)\""
|
||||
|
||||
inject_block "$config_file" "zoxide init" "$content"
|
||||
|
||||
# Source if modified (only for bashrc)
|
||||
if [ "$config_file" = "$HOME/.bashrc" ]; then
|
||||
. "$config_file" 2>/dev/null || true
|
||||
fi
|
||||
remove_block "$config_file" "zoxide init"
|
||||
done
|
||||
|
||||
write_env_snippet "local-bin" 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
write_env_snippet "zoxide" 'eval "$(zoxide init --cmd cd bash)"'
|
||||
}
|
||||
|
||||
main() {
|
||||
|
||||
@@ -84,3 +84,9 @@ version_lt() {
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Export functions and variables for subshells
|
||||
export _LIB_COMMON_SOURCED=1
|
||||
export RED GREEN YELLOW BLUE NC
|
||||
export -f require_bash log_info log_success log_warn log_error confirm has_command make_temp_dir version_lt
|
||||
|
||||
|
||||
160
lib/platform.sh
160
lib/platform.sh
@@ -36,18 +36,9 @@ detect_arch() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Install packages depending on detected distro
|
||||
# Usage: pkg_install <package_name_arch> <package_name_debian> <package_name_fedora>
|
||||
# Or simpler: map common packages to their distro equivalents
|
||||
pkg_install() {
|
||||
local distro
|
||||
distro=$(detect_distro)
|
||||
|
||||
if [ "$distro" = "unknown" ]; then
|
||||
log_error "Unsupported distribution. Cannot install packages automatically."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_resolve_pkg_names() {
|
||||
local distro="$1"
|
||||
shift
|
||||
local pkgs=()
|
||||
for arg in "$@"; do
|
||||
# Format can be "pkg" or "arch:pkg_a|debian:pkg_d|fedora:pkg_f"
|
||||
@@ -69,22 +60,159 @@ pkg_install() {
|
||||
pkgs+=("$arg")
|
||||
fi
|
||||
done
|
||||
echo "${pkgs[@]}"
|
||||
}
|
||||
|
||||
# Install packages depending on detected distro
|
||||
# Usage: pkg_install <package_name_arch> <package_name_debian> <package_name_fedora>
|
||||
# Or simpler: map common packages to their distro equivalents
|
||||
pkg_install() {
|
||||
local distro
|
||||
distro=$(detect_distro)
|
||||
|
||||
if [ "$distro" = "unknown" ]; then
|
||||
log_error "Unsupported distribution. Cannot install packages automatically."
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS=' ' read -ra pkgs <<< "$(_resolve_pkg_names "$distro" "$@")"
|
||||
|
||||
if [ ${#pkgs[@]} -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing packages via $distro package manager: ${pkgs[*]}"
|
||||
local to_install=()
|
||||
for pkg in "${pkgs[@]}"; do
|
||||
if ! pkg_check "$pkg"; then
|
||||
to_install+=("$pkg")
|
||||
fi
|
||||
|
||||
# Reference counting logic
|
||||
if [ -n "${BOOTSTRAP_CURRENT_TOOL:-}" ] && [ -n "${BOOTSTRAP_PACKAGES_DIR:-}" ]; then
|
||||
local ref_file="$BOOTSTRAP_PACKAGES_DIR/$pkg"
|
||||
if ! grep -q "^${BOOTSTRAP_CURRENT_TOOL}$" "$ref_file" 2>/dev/null; then
|
||||
echo "$BOOTSTRAP_CURRENT_TOOL" >> "$ref_file"
|
||||
# Register rollback command
|
||||
if type add_rollback_cmd >/dev/null 2>&1; then
|
||||
add_rollback_cmd "pkg_remove $pkg"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#to_install[@]} -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing packages via $distro package manager: ${to_install[*]}"
|
||||
case "$distro" in
|
||||
arch)
|
||||
sudo pacman -Sy --needed --noconfirm "${pkgs[@]}"
|
||||
sudo pacman -Sy --needed --noconfirm "${to_install[@]}"
|
||||
;;
|
||||
debian)
|
||||
sudo apt update
|
||||
sudo apt install -y "${pkgs[@]}"
|
||||
sudo apt install -y "${to_install[@]}"
|
||||
;;
|
||||
fedora)
|
||||
sudo dnf install -y "${pkgs[@]}"
|
||||
sudo dnf install -y "${to_install[@]}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if packages are installed
|
||||
# Returns 0 if all are installed, 1 otherwise
|
||||
pkg_check() {
|
||||
local distro
|
||||
distro=$(detect_distro)
|
||||
|
||||
if [ "$distro" = "unknown" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS=' ' read -ra pkgs <<< "$(_resolve_pkg_names "$distro" "$@")"
|
||||
|
||||
if [ ${#pkgs[@]} -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$distro" in
|
||||
arch)
|
||||
pacman -Qq "${pkgs[@]}" >/dev/null 2>&1
|
||||
return $?
|
||||
;;
|
||||
debian)
|
||||
dpkg -s "${pkgs[@]}" >/dev/null 2>&1
|
||||
return $?
|
||||
;;
|
||||
fedora)
|
||||
rpm -q "${pkgs[@]}" >/dev/null 2>&1
|
||||
return $?
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Remove packages depending on detected distro
|
||||
pkg_remove() {
|
||||
local distro
|
||||
distro=$(detect_distro)
|
||||
|
||||
if [ "$distro" = "unknown" ]; then
|
||||
log_error "Unsupported distribution. Cannot remove packages automatically."
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS=' ' read -ra pkgs <<< "$(_resolve_pkg_names "$distro" "$@")"
|
||||
|
||||
if [ ${#pkgs[@]} -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local to_remove=()
|
||||
for pkg in "${pkgs[@]}"; do
|
||||
if [ -n "${BOOTSTRAP_CURRENT_TOOL:-}" ] && [ -n "${BOOTSTRAP_PACKAGES_DIR:-}" ]; then
|
||||
local ref_file="$BOOTSTRAP_PACKAGES_DIR/$pkg"
|
||||
if [ -f "$ref_file" ]; then
|
||||
# Remove this tool from the reference file
|
||||
sed -i "/^${BOOTSTRAP_CURRENT_TOOL}$/d" "$ref_file"
|
||||
if [ -s "$ref_file" ]; then
|
||||
log_info "Skipping removal of '$pkg'; it is required by other tools."
|
||||
continue
|
||||
else
|
||||
rm -f "$ref_file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
to_remove+=("$pkg")
|
||||
done
|
||||
|
||||
if [ ${#to_remove[@]} -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Removing packages via $distro package manager: ${to_remove[*]}"
|
||||
case "$distro" in
|
||||
arch)
|
||||
local pac_remove=()
|
||||
for pkg in "${to_remove[@]}"; do
|
||||
if pacman -Qq "$pkg" >/dev/null 2>&1; then
|
||||
pac_remove+=("$pkg")
|
||||
fi
|
||||
done
|
||||
if [ ${#pac_remove[@]} -gt 0 ]; then
|
||||
sudo pacman -R --noconfirm "${pac_remove[@]}"
|
||||
fi
|
||||
;;
|
||||
debian)
|
||||
sudo apt remove -y "${to_remove[@]}"
|
||||
;;
|
||||
fedora)
|
||||
sudo dnf remove -y "${to_remove[@]}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Export functions and variables for subshells
|
||||
export _LIB_PLATFORM_SOURCED=1
|
||||
export -f detect_distro detect_arch _resolve_pkg_names pkg_install pkg_check pkg_remove
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
declare -A INSTALLERS=(
|
||||
[agy]="Antigravity CLI"
|
||||
[asciicinema]="asciinema terminal recorder"
|
||||
[bat]="Bat (alternative to cat) and configure alias"
|
||||
[node]="Node.js (LTS) and NVM"
|
||||
[nvim]="Neovim 0.11.7 and configuration"
|
||||
[nvim]="Neovim 0.12.0 and configuration"
|
||||
[pnpm]="pnpm package manager"
|
||||
[rust]="Rustup and Rust compiler/toolchain"
|
||||
[starship]="Starship shell prompt"
|
||||
@@ -16,6 +17,7 @@ declare -A INSTALLERS=(
|
||||
|
||||
declare -A INSTALLER_DISPLAYS=(
|
||||
[agy]="Antigravity"
|
||||
[asciicinema]="asciicinema"
|
||||
[bat]="Bat"
|
||||
[node]="Node"
|
||||
[nvim]="Neovim"
|
||||
@@ -28,4 +30,4 @@ declare -A INSTALLER_DISPLAYS=(
|
||||
[zoxide]="Zoxide"
|
||||
)
|
||||
|
||||
INSTALLER_KEYS=(agy bat node nvim pnpm rust starship uv yay yazi zoxide)
|
||||
INSTALLER_KEYS=(agy asciicinema bat node nvim pnpm rust starship uv yay yazi zoxide)
|
||||
|
||||
130
lib/rollback.sh
Normal file
130
lib/rollback.sh
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -n "${_LIB_ROLLBACK_SOURCED:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
_LIB_ROLLBACK_SOURCED=1
|
||||
|
||||
BOOTSTRAP_STATE_DIR="$HOME/.local/state/bootstrap"
|
||||
BOOTSTRAP_HISTORY_LOG="$BOOTSTRAP_STATE_DIR/history.log"
|
||||
BOOTSTRAP_UNINSTALLERS_DIR="$BOOTSTRAP_STATE_DIR/uninstallers"
|
||||
BOOTSTRAP_PACKAGES_DIR="$BOOTSTRAP_STATE_DIR/packages"
|
||||
|
||||
init_rollback_system() {
|
||||
mkdir -p "$BOOTSTRAP_UNINSTALLERS_DIR"
|
||||
mkdir -p "$BOOTSTRAP_PACKAGES_DIR"
|
||||
touch "$BOOTSTRAP_HISTORY_LOG"
|
||||
}
|
||||
|
||||
setup_uninstaller_context() {
|
||||
local tool="$1"
|
||||
export BOOTSTRAP_CURRENT_TOOL="$tool"
|
||||
export BOOTSTRAP_UNINSTALLER_CMDS="$BOOTSTRAP_UNINSTALLERS_DIR/${tool}.cmds"
|
||||
# Ensure fresh manifest for this run
|
||||
rm -f "$BOOTSTRAP_UNINSTALLER_CMDS"
|
||||
touch "$BOOTSTRAP_UNINSTALLER_CMDS"
|
||||
}
|
||||
|
||||
add_rollback_cmd() {
|
||||
local cmd="$1"
|
||||
if [ -n "${BOOTSTRAP_UNINSTALLER_CMDS:-}" ] && [ -f "$BOOTSTRAP_UNINSTALLER_CMDS" ]; then
|
||||
# Prepend to the top of the file
|
||||
sed -i "1i $cmd" "$BOOTSTRAP_UNINSTALLER_CMDS"
|
||||
fi
|
||||
}
|
||||
|
||||
track_file() {
|
||||
add_rollback_cmd "sudo rm -f '$1'"
|
||||
}
|
||||
|
||||
track_dir() {
|
||||
add_rollback_cmd "sudo rm -rf '$1'"
|
||||
}
|
||||
|
||||
create_savepoint() {
|
||||
local name="$1"
|
||||
echo "SAVEPOINT: $name" >> "$BOOTSTRAP_HISTORY_LOG"
|
||||
log_success "Savepoint '$name' created."
|
||||
}
|
||||
|
||||
mark_install_success() {
|
||||
local tool="$1"
|
||||
# Only record if we actually have an uninstaller
|
||||
if [ -f "$BOOTSTRAP_UNINSTALLERS_DIR/${tool}.cmds" ]; then
|
||||
echo "INSTALL: $tool" >> "$BOOTSTRAP_HISTORY_LOG"
|
||||
fi
|
||||
}
|
||||
|
||||
execute_rollback() {
|
||||
local tool="$1"
|
||||
local manifest="$BOOTSTRAP_UNINSTALLERS_DIR/${tool}.cmds"
|
||||
|
||||
if [ ! -f "$manifest" ]; then
|
||||
log_warn "No rollback manifest found for '$tool'."
|
||||
return 0
|
||||
fi
|
||||
|
||||
export BOOTSTRAP_CURRENT_TOOL="$tool"
|
||||
log_info "Rolling back '$tool'..."
|
||||
while IFS= read -r cmd; do
|
||||
[ -z "$cmd" ] && continue
|
||||
log_info "Executing: $cmd"
|
||||
eval "$cmd" || log_warn "Failed to execute: $cmd"
|
||||
done < "$manifest"
|
||||
|
||||
rm -f "$manifest"
|
||||
log_success "Rollback of '$tool' complete."
|
||||
}
|
||||
|
||||
rollback_bare() {
|
||||
if [ ! -s "$BOOTSTRAP_HISTORY_LOG" ]; then
|
||||
log_info "No history available to rollback."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local last_line
|
||||
last_line=$(tail -n 1 "$BOOTSTRAP_HISTORY_LOG")
|
||||
|
||||
if [[ "$last_line" == INSTALL:* ]]; then
|
||||
local tool="${last_line#INSTALL: }"
|
||||
execute_rollback "$tool"
|
||||
# Remove the last line efficiently
|
||||
sed -i '$ d' "$BOOTSTRAP_HISTORY_LOG"
|
||||
elif [[ "$last_line" == SAVEPOINT:* ]]; then
|
||||
local sp="${last_line#SAVEPOINT: }"
|
||||
log_warn "Last action was savepoint '$sp'. Cannot bare-rollback a savepoint."
|
||||
fi
|
||||
}
|
||||
|
||||
rollback_to_savepoint() {
|
||||
local target_sp="$1"
|
||||
|
||||
if ! grep -q "SAVEPOINT: $target_sp" "$BOOTSTRAP_HISTORY_LOG"; then
|
||||
log_error "Savepoint '$target_sp' not found in history."
|
||||
return 1
|
||||
fi
|
||||
|
||||
while [ -s "$BOOTSTRAP_HISTORY_LOG" ]; do
|
||||
local last_line
|
||||
last_line=$(tail -n 1 "$BOOTSTRAP_HISTORY_LOG")
|
||||
|
||||
if [[ "$last_line" == SAVEPOINT:\ $target_sp ]]; then
|
||||
log_success "Reached savepoint '$target_sp'."
|
||||
# Optionally remove the savepoint itself or keep it? Let's keep it.
|
||||
break
|
||||
elif [[ "$last_line" == INSTALL:* ]]; then
|
||||
local tool="${last_line#INSTALL: }"
|
||||
execute_rollback "$tool"
|
||||
sed -i '$ d' "$BOOTSTRAP_HISTORY_LOG"
|
||||
elif [[ "$last_line" == SAVEPOINT:* ]]; then
|
||||
local sp="${last_line#SAVEPOINT: }"
|
||||
log_info "Removing intermediate savepoint '$sp'..."
|
||||
sed -i '$ d' "$BOOTSTRAP_HISTORY_LOG"
|
||||
else
|
||||
# Unknown line format, just remove it
|
||||
sed -i '$ d' "$BOOTSTRAP_HISTORY_LOG"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
export -f init_rollback_system setup_uninstaller_context add_rollback_cmd track_file track_dir create_savepoint mark_install_success execute_rollback rollback_bare rollback_to_savepoint
|
||||
@@ -2,42 +2,37 @@
|
||||
# Central routing script for bootstrap installers.
|
||||
# This file is updated automatically by the 'b' command.
|
||||
|
||||
BOOTSTRAP_DIR="${BOOTSTRAP_DIR:-$HOME/.config/bootstrap}"
|
||||
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null || pwd)"
|
||||
BOOTSTRAP_DIR="${BOOTSTRAP_DIR:-$(dirname "$_LIB_DIR")}"
|
||||
|
||||
# Source common library
|
||||
# Fallback to ~/.config/bootstrap if not found locally
|
||||
if [ ! -d "$BOOTSTRAP_DIR/lib" ]; then
|
||||
BOOTSTRAP_DIR="$HOME/.config/bootstrap"
|
||||
fi
|
||||
export BOOTSTRAP_DIR
|
||||
|
||||
# Source libraries
|
||||
if [ -f "$BOOTSTRAP_DIR/lib/common.sh" ]; then
|
||||
. "$BOOTSTRAP_DIR/lib/common.sh"
|
||||
. "$BOOTSTRAP_DIR/lib/rollback.sh"
|
||||
. "$BOOTSTRAP_DIR/lib/platform.sh"
|
||||
. "$BOOTSTRAP_DIR/lib/shell_config.sh"
|
||||
init_rollback_system
|
||||
else
|
||||
# Fallback/Bootstrap case if lib is not installed yet
|
||||
echo "Error: Bootstrap libraries not found at $BOOTSTRAP_DIR/lib/" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_bash
|
||||
|
||||
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null || pwd)"
|
||||
|
||||
# Source registry
|
||||
if [ -f "$_SCRIPT_DIR/registry.sh" ]; then
|
||||
. "$_SCRIPT_DIR/registry.sh"
|
||||
elif [ -f "$BOOTSTRAP_DIR/lib/registry.sh" ]; then
|
||||
if [ -f "$BOOTSTRAP_DIR/lib/registry.sh" ]; then
|
||||
. "$BOOTSTRAP_DIR/lib/registry.sh"
|
||||
else
|
||||
# Standalone/remote fallback: download registry
|
||||
_tmp_registry=$(mktemp)
|
||||
BOOTSTRAP_BASE_URL="${BOOTSTRAP_BASE_URL:-https://git.adityagupta.dev/sortedcord/bootstrap/raw/branch/master}"
|
||||
BOOTSTRAP_FALLBACK_URL="${BOOTSTRAP_FALLBACK_URL:-https://raw.githubusercontent.com/sortedcord/bootstrap/refs/heads/master}"
|
||||
curl -fsSL "${BOOTSTRAP_BASE_URL}/lib/registry.sh" -o "$_tmp_registry" 2>/dev/null || \
|
||||
curl -fsSL "${BOOTSTRAP_FALLBACK_URL}/lib/registry.sh" -o "$_tmp_registry" 2>/dev/null
|
||||
if [ -s "$_tmp_registry" ]; then
|
||||
. "$_tmp_registry"
|
||||
else
|
||||
# Critical fallback
|
||||
declare -A INSTALLERS
|
||||
declare -A INSTALLER_DISPLAYS
|
||||
INSTALLER_KEYS=()
|
||||
fi
|
||||
rm -f "$_tmp_registry"
|
||||
# Critical fallback
|
||||
declare -A INSTALLERS
|
||||
declare -A INSTALLER_DISPLAYS
|
||||
INSTALLER_KEYS=()
|
||||
fi
|
||||
|
||||
# Helper function to run/edit installer scripts
|
||||
@@ -64,6 +59,13 @@ run_ware() {
|
||||
|
||||
# Check for local installer first
|
||||
local local_installer="$BOOTSTRAP_DIR/installers/install_${tool}.sh"
|
||||
|
||||
if [ "$bypass_edit" = "true" ] && [ -f "$local_installer" ]; then
|
||||
log_info "Running ${display_name} installer..."
|
||||
bash "$local_installer" "${cmd_args[@]}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
local temp_script
|
||||
temp_script=$(mktemp --suffix=".sh" 2>/dev/null || mktemp)
|
||||
|
||||
@@ -111,9 +113,14 @@ run_ware() {
|
||||
|
||||
# Run the script (edited or unchanged)
|
||||
log_info "Running ${display_name} installer..."
|
||||
setup_uninstaller_context "$tool"
|
||||
bash "$temp_script" "${cmd_args[@]}"
|
||||
local run_status=$?
|
||||
|
||||
if [ "$run_status" -eq 0 ]; then
|
||||
mark_install_success "$tool"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f "$temp_script"
|
||||
return "$run_status"
|
||||
@@ -193,6 +200,24 @@ for script in "${SCRIPTS[@]}"; do
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
fall)
|
||||
local savepoint_name="${1:-}"
|
||||
if [ -z "$savepoint_name" ]; then
|
||||
log_error "Usage: b fall <savepoint_name>"
|
||||
exit 1
|
||||
fi
|
||||
create_savepoint "$savepoint_name"
|
||||
exit 0
|
||||
;;
|
||||
rb)
|
||||
local target="${1:-}"
|
||||
if [ -z "$target" ]; then
|
||||
rollback_bare
|
||||
else
|
||||
rollback_to_savepoint "$target"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown command '$script'."
|
||||
log_info "Run 'b all' to list all available commands."
|
||||
|
||||
@@ -102,6 +102,62 @@ add_env_if_missing() {
|
||||
return 1 # Not added
|
||||
}
|
||||
|
||||
# Write environment snippet to env.d/
|
||||
# Usage: write_env_snippet <name> <content>
|
||||
write_env_snippet() {
|
||||
local name="$1"
|
||||
local content="$2"
|
||||
local dir="${BOOTSTRAP_DIR:-$HOME/.config/bootstrap}/env.d"
|
||||
|
||||
mkdir -p "$dir"
|
||||
log_info "Writing environment snippet '$name' to $dir/${name}.sh"
|
||||
echo "$content" > "$dir/${name}.sh"
|
||||
|
||||
if type add_rollback_cmd >/dev/null 2>&1; then
|
||||
add_rollback_cmd "rm -f \"$dir/${name}.sh\""
|
||||
fi
|
||||
}
|
||||
|
||||
# Write alias snippet to aliases.d/
|
||||
# Usage: write_alias_snippet <name> <content>
|
||||
write_alias_snippet() {
|
||||
local name="$1"
|
||||
local content="$2"
|
||||
local dir="${BOOTSTRAP_DIR:-$HOME/.config/bootstrap}/aliases.d"
|
||||
|
||||
mkdir -p "$dir"
|
||||
log_info "Writing alias snippet '$name' to $dir/${name}.sh"
|
||||
echo "$content" > "$dir/${name}.sh"
|
||||
|
||||
if type add_rollback_cmd >/dev/null 2>&1; then
|
||||
add_rollback_cmd "rm -f \"$dir/${name}.sh\""
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove environment snippet from env.d/
|
||||
# Usage: remove_env_snippet <name>
|
||||
remove_env_snippet() {
|
||||
local name="$1"
|
||||
local dir="${BOOTSTRAP_DIR:-$HOME/.config/bootstrap}/env.d"
|
||||
|
||||
if [ -f "$dir/${name}.sh" ]; then
|
||||
log_info "Removing environment snippet '$name'"
|
||||
rm -f "$dir/${name}.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove alias snippet from aliases.d/
|
||||
# Usage: remove_alias_snippet <name>
|
||||
remove_alias_snippet() {
|
||||
local name="$1"
|
||||
local dir="${BOOTSTRAP_DIR:-$HOME/.config/bootstrap}/aliases.d"
|
||||
|
||||
if [ -f "$dir/${name}.sh" ]; then
|
||||
log_info "Removing alias snippet '$name'"
|
||||
rm -f "$dir/${name}.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Setup fd symlink for Debian/Ubuntu (fdfind -> fd)
|
||||
create_fd_symlink() {
|
||||
if ! has_command fd && has_command fdfind; then
|
||||
@@ -109,3 +165,9 @@ create_fd_symlink() {
|
||||
sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd
|
||||
fi
|
||||
}
|
||||
|
||||
# Export functions and variables for subshells
|
||||
export _LIB_SHELL_CONFIG_SOURCED=1
|
||||
export -f get_shell_configs remove_block inject_block add_alias_if_missing add_env_if_missing create_fd_symlink write_env_snippet write_alias_snippet remove_env_snippet remove_alias_snippet
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Here is a comparison of the size and complexity of using Bootstrap (`to b`) vers
|
||||
| application | to b | not to b |
|
||||
| :--- | :--- | :--- |
|
||||
| **Antigravity CLI (`agy`)** | 197 lines | 239 lines (Official Antigravity install script) |
|
||||
| **asciicinema (`asciicinema`)** | 99 lines | N/A (Official binary distribution) |
|
||||
| **Bat (`bat`)** | 155 lines | N/A (Standard package install) |
|
||||
| **Node.js & NVM (`node`)** | 156 lines | 507 lines (Official NVM install script) |
|
||||
| **Neovim (`nvim`)** | 178 lines | N/A (Official binary/config distribution) |
|
||||
|
||||
@@ -9,41 +9,3 @@ if [ -f "./scripts/generate_registry.sh" ]; then
|
||||
./scripts/generate_registry.sh
|
||||
git add lib/registry.sh
|
||||
fi
|
||||
|
||||
VERSION_FILE="VERSION"
|
||||
|
||||
if [ ! -f "$VERSION_FILE" ]; then
|
||||
echo "1.0.0" > "$VERSION_FILE"
|
||||
git add "$VERSION_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if there are staged changes other than VERSION, documentation (*.md), or installers folder
|
||||
if ! git diff --cached --name-only | grep -Ev "^($VERSION_FILE$|.*\.md$|^installers/)" | grep -q .; then
|
||||
# No other files staged, skip version bump
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read current version
|
||||
current_version=$(cat "$VERSION_FILE" | tr -d '[:space:]')
|
||||
|
||||
# Basic regex validation for semantic versioning (X.Y.Z)
|
||||
if [[ ! "$current_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Current version '$current_version' in $VERSION_FILE is not in X.Y.Z format." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse version components
|
||||
IFS='.' read -r major minor patch <<< "$current_version"
|
||||
|
||||
# Increment patch version
|
||||
patch=$((patch + 1))
|
||||
new_version="$major.$minor.$patch"
|
||||
|
||||
# Write to VERSION file
|
||||
echo "$new_version" > "$VERSION_FILE"
|
||||
|
||||
# Add VERSION file to the commit
|
||||
git add "$VERSION_FILE"
|
||||
|
||||
echo "[pre-commit] Automatically bumped version from $current_version to $new_version"
|
||||
|
||||
Reference in New Issue
Block a user