fix(update): add global installation support and path auto-detection (#230)
Resolves #224 — update.sh now mirrors install.sh path resolution: - Auto-detects local (.opencode/) then global (~/.config/opencode) installs - Adds --install-dir PATH flag and OPENCODE_BRANCH/OPENCODE_INSTALL_DIR env vars - Adds trap for .backup cleanup on SIGINT/SIGTERM - Fixes set -e + arithmetic counter bug (uses $((n+1)) form) - Adds path traversal guard and write-permission check - Adds RED color variable; fixes print_error hardcoded escape code - Renames 'skipped' counter to 'failed' for accuracy - README: adds one-liner Keep Updated section in Quick Start
This commit is contained in:
parent
8e8084e4ae
commit
1442740b4a
2 changed files with 316 additions and 47 deletions
|
|
@ -133,6 +133,14 @@ curl -fsSL https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/main/
|
||||||
bash install.sh
|
bash install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Keep Updated
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/main/update.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
> Use `--install-dir PATH` if you installed to a custom location (e.g. `~/.config/opencode`).
|
||||||
|
|
||||||
### Step 2: Start Building
|
### Step 2: Start Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
323
update.sh
323
update.sh
|
|
@ -3,79 +3,340 @@
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# OpenAgents Control Updater
|
# OpenAgents Control Updater
|
||||||
# Updates existing OpenCode components to latest versions
|
# Updates existing OpenCode components to latest versions
|
||||||
|
#
|
||||||
|
# Compatible with:
|
||||||
|
# - macOS (bash 3.2+)
|
||||||
|
# - Linux (bash 3.2+)
|
||||||
|
# - Windows (Git Bash, WSL)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./update.sh # Auto-detect install location
|
||||||
|
# ./update.sh --install-dir PATH # Update a specific install path
|
||||||
|
#
|
||||||
|
# Environment variables:
|
||||||
|
# OPENCODE_INSTALL_DIR # Override default install directory
|
||||||
|
# OPENCODE_BRANCH # Branch to pull from (default: main)
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Colors
|
# Detect platform
|
||||||
|
PLATFORM="$(uname -s)"
|
||||||
|
case "$PLATFORM" in
|
||||||
|
Linux*) PLATFORM="Linux";;
|
||||||
|
Darwin*) PLATFORM="macOS";;
|
||||||
|
CYGWIN*|MINGW*|MSYS*) PLATFORM="Windows";;
|
||||||
|
*) PLATFORM="Unknown";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Colors (disable on Windows terminals without color support)
|
||||||
|
if [ "$PLATFORM" = "Windows" ] && [ -z "$WT_SESSION" ] && [ -z "$ConEmuPID" ]; then
|
||||||
|
RED=''
|
||||||
|
GREEN=''
|
||||||
|
YELLOW=''
|
||||||
|
BLUE=''
|
||||||
|
CYAN=''
|
||||||
|
BOLD=''
|
||||||
|
NC=''
|
||||||
|
else
|
||||||
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
# YELLOW='\033[1;33m' # Unused
|
YELLOW='\033[1;33m'
|
||||||
BLUE='\033[0;34m'
|
BLUE='\033[0;34m'
|
||||||
CYAN='\033[0;36m'
|
CYAN='\033[0;36m'
|
||||||
BOLD='\033[1m'
|
BOLD='\033[1m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
fi
|
||||||
|
|
||||||
REPO_URL="https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/main"
|
BRANCH="${OPENCODE_BRANCH:-main}"
|
||||||
INSTALL_DIR=".opencode"
|
REPO_URL="https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/${BRANCH}"
|
||||||
|
|
||||||
|
# CLI argument for custom install dir (overrides env var)
|
||||||
|
CUSTOM_INSTALL_DIR=""
|
||||||
|
|
||||||
|
# Track backup files for cleanup on exit
|
||||||
|
BACKUP_FILES=()
|
||||||
|
|
||||||
|
# Clean up any leftover backup files on exit/interrupt
|
||||||
|
cleanup_backups() {
|
||||||
|
for f in "${BACKUP_FILES[@]}"; do
|
||||||
|
[ -f "$f" ] && rm -f "$f"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup_backups EXIT INT TERM
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Utility Functions
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
||||||
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
||||||
|
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||||||
|
print_error() { echo -e "${RED}✗${NC} $1" >&2; }
|
||||||
print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
|
print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
|
||||||
|
|
||||||
print_header() {
|
print_header() {
|
||||||
echo -e "${CYAN}${BOLD}"
|
echo -e "${CYAN}${BOLD}"
|
||||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||||
echo "║ ║"
|
echo "║ ║"
|
||||||
echo "║ OpenAgents Control Updater v1.0.0 ║"
|
echo "║ OpenAgents Control Updater v1.1.0 ║"
|
||||||
echo "║ ║"
|
echo "║ ║"
|
||||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||||
echo -e "${NC}"
|
echo -e "${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
update_component() {
|
print_usage() {
|
||||||
local path=$1
|
echo "Usage: $0 [--install-dir PATH]"
|
||||||
local url="${REPO_URL}/${path}"
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --install-dir PATH Update a specific installation directory"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Environment variables:"
|
||||||
|
echo " OPENCODE_INSTALL_DIR Override the default installation directory"
|
||||||
|
echo " OPENCODE_BRANCH Branch to pull updates from (default: main)"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " # Auto-detect and update"
|
||||||
|
echo " $0"
|
||||||
|
echo ""
|
||||||
|
echo " # Update a global installation"
|
||||||
|
echo " $0 --install-dir ~/.config/opencode"
|
||||||
|
echo ""
|
||||||
|
echo " # Update via environment variable"
|
||||||
|
echo " export OPENCODE_INSTALL_DIR=~/.config/opencode && $0"
|
||||||
|
}
|
||||||
|
|
||||||
if [ ! -f "$path" ]; then
|
#############################################################################
|
||||||
print_info "Skipping $path (not installed)"
|
# Path Resolution
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
get_global_install_path() {
|
||||||
|
# Return platform-appropriate global installation path
|
||||||
|
case "$PLATFORM" in
|
||||||
|
macOS)
|
||||||
|
echo "${HOME}/.config/opencode"
|
||||||
|
;;
|
||||||
|
Linux)
|
||||||
|
echo "${HOME}/.config/opencode"
|
||||||
|
;;
|
||||||
|
Windows)
|
||||||
|
# Windows Git Bash/WSL: Use same as Linux
|
||||||
|
echo "${HOME}/.config/opencode"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "${HOME}/.config/opencode"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_path() {
|
||||||
|
local input_path="$1"
|
||||||
|
|
||||||
|
# Handle empty path
|
||||||
|
if [ -z "$input_path" ]; then
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local normalized_path
|
||||||
|
|
||||||
|
# Expand tilde to $HOME (works on Linux, macOS, Windows Git Bash)
|
||||||
|
if [[ $input_path == ~* ]]; then
|
||||||
|
normalized_path="${HOME}${input_path:1}"
|
||||||
|
else
|
||||||
|
normalized_path="$input_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Convert backslashes to forward slashes (Windows compatibility)
|
||||||
|
normalized_path="${normalized_path//\\//}"
|
||||||
|
|
||||||
|
# Remove trailing slashes
|
||||||
|
normalized_path="${normalized_path%/}"
|
||||||
|
|
||||||
|
# If path is relative, make it absolute based on current directory
|
||||||
|
if [[ ! "$normalized_path" = /* ]] && [[ ! "$normalized_path" =~ ^[A-Za-z]: ]]; then
|
||||||
|
normalized_path="$(pwd)/${normalized_path}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$normalized_path"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_install_dir() {
|
||||||
|
local custom_dir="$1"
|
||||||
|
|
||||||
|
# Priority: CLI arg → env var → auto-detect (local then global)
|
||||||
|
if [ -n "$custom_dir" ]; then
|
||||||
|
normalize_path "$custom_dir"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Backup existing file
|
if [ -n "$OPENCODE_INSTALL_DIR" ]; then
|
||||||
cp "$path" "${path}.backup"
|
normalize_path "$OPENCODE_INSTALL_DIR"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
if curl -fsSL "$url" -o "$path"; then
|
# Auto-detect: prefer local project install, fall back to global
|
||||||
print_success "Updated $path"
|
local local_path
|
||||||
rm "${path}.backup"
|
local_path="$(pwd)/.opencode"
|
||||||
|
local global_path
|
||||||
|
global_path=$(get_global_install_path)
|
||||||
|
|
||||||
|
if [ -d "$local_path" ]; then
|
||||||
|
echo "$local_path"
|
||||||
|
elif [ -d "$global_path" ]; then
|
||||||
|
echo "$global_path"
|
||||||
else
|
else
|
||||||
print_info "Failed to update $path, restoring backup"
|
# Neither exists — return local path so main() gives a clear error
|
||||||
mv "${path}.backup" "$path"
|
echo "$local_path"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
#############################################################################
|
||||||
print_header
|
# Update Logic
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
if [ ! -d "$INSTALL_DIR" ]; then
|
update_component() {
|
||||||
echo "Error: $INSTALL_DIR directory not found"
|
local path="$1"
|
||||||
echo "Run install.sh first to install components"
|
local install_dir="$2"
|
||||||
exit 1
|
local relative_path="${path#"$install_dir"/}"
|
||||||
|
|
||||||
|
# Guard: reject paths that escaped the install dir
|
||||||
|
if [[ "$relative_path" == /* ]] || [[ "$relative_path" == *..* ]]; then
|
||||||
|
print_warning "Skipping suspicious path: $path"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_step "Updating components..."
|
local url="${REPO_URL}/.opencode/${relative_path}"
|
||||||
|
local backup="${path}.backup"
|
||||||
|
|
||||||
# Update all markdown files in .opencode
|
cp "$path" "$backup"
|
||||||
|
BACKUP_FILES+=("$backup")
|
||||||
|
|
||||||
|
if curl -fsSL "$url" -o "$path" 2>/dev/null; then
|
||||||
|
print_success "Updated $path"
|
||||||
|
rm -f "$backup"
|
||||||
|
# Remove from tracking array (bash 3.2 compatible)
|
||||||
|
local new_backups=()
|
||||||
|
for f in "${BACKUP_FILES[@]}"; do
|
||||||
|
[ "$f" != "$backup" ] && new_backups+=("$f")
|
||||||
|
done
|
||||||
|
BACKUP_FILES=("${new_backups[@]+"${new_backups[@]}"}")
|
||||||
|
else
|
||||||
|
print_warning "Could not update $path — restoring backup"
|
||||||
|
mv "$backup" "$path"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
update_all_components() {
|
||||||
|
local install_dir="$1"
|
||||||
|
local updated=0
|
||||||
|
local failed=0
|
||||||
|
|
||||||
|
# Update markdown files
|
||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
update_component "$file"
|
if update_component "$file" "$install_dir"; then
|
||||||
done < <(find "$INSTALL_DIR" -name "*.md" -type f -print0)
|
updated=$((updated + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$install_dir" -name "*.md" -type f -print0)
|
||||||
|
|
||||||
# Update TypeScript files
|
# Update TypeScript files
|
||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
update_component "$file"
|
if update_component "$file" "$install_dir"; then
|
||||||
done < <(find "$INSTALL_DIR" -name "*.ts" -type f -not -path "*/node_modules/*" -print0)
|
updated=$((updated + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$install_dir" -name "*.ts" -type f -not -path "*/node_modules/*" -print0)
|
||||||
|
|
||||||
# Update config files
|
# Update shell scripts inside install dir
|
||||||
[ -f "env.example" ] && update_component "env.example"
|
while IFS= read -r -d '' file; do
|
||||||
|
if update_component "$file" "$install_dir"; then
|
||||||
|
updated=$((updated + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$install_dir" -name "*.sh" -type f -print0)
|
||||||
|
|
||||||
|
print_info "Updated: $updated file(s), failed: $failed file(s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Argument Parsing
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--install-dir=*)
|
||||||
|
CUSTOM_INSTALL_DIR="${1#*=}"
|
||||||
|
if [ -z "$CUSTOM_INSTALL_DIR" ]; then
|
||||||
|
print_error "--install-dir requires a non-empty path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--install-dir)
|
||||||
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
||||||
|
CUSTOM_INSTALL_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
else
|
||||||
|
print_error "--install-dir requires a path argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unknown option: $1"
|
||||||
|
print_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Main
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
print_header
|
||||||
|
|
||||||
|
local install_dir
|
||||||
|
install_dir=$(resolve_install_dir "$CUSTOM_INSTALL_DIR")
|
||||||
|
|
||||||
|
if [ ! -d "$install_dir" ]; then
|
||||||
|
print_error "Installation directory not found: $install_dir"
|
||||||
|
echo ""
|
||||||
|
echo "Searched locations:"
|
||||||
|
echo " 1. --install-dir argument"
|
||||||
|
echo " 2. OPENCODE_INSTALL_DIR environment variable"
|
||||||
|
echo " 3. Local path: $(pwd)/.opencode"
|
||||||
|
echo " 4. Global path: $(get_global_install_path)"
|
||||||
|
echo ""
|
||||||
|
echo "Run install.sh first to install components, or specify the correct"
|
||||||
|
echo "path with: $0 --install-dir PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -w "$install_dir" ]; then
|
||||||
|
print_error "No write permission for: $install_dir"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Updating installation at: ${CYAN}${install_dir}${NC}"
|
||||||
|
print_step "Updating components..."
|
||||||
|
|
||||||
|
update_all_components "$install_dir"
|
||||||
|
|
||||||
print_success "Update complete!"
|
print_success "Update complete!"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue