| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- #!/usr/bin/env bash
- #############################################################################
- # OpenAgents Control Updater
- # 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
- # 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'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- CYAN='\033[0;36m'
- BOLD='\033[1m'
- NC='\033[0m'
- fi
- BRANCH="${OPENCODE_BRANCH:-main}"
- 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_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_header() {
- echo -e "${CYAN}${BOLD}"
- echo "╔════════════════════════════════════════════════════════════════╗"
- echo "║ ║"
- echo "║ OpenAgents Control Updater v1.1.0 ║"
- echo "║ ║"
- echo "╚════════════════════════════════════════════════════════════════╝"
- echo -e "${NC}"
- }
- print_usage() {
- echo "Usage: $0 [--install-dir 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"
- }
- #############################################################################
- # 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
- fi
- if [ -n "$OPENCODE_INSTALL_DIR" ]; then
- normalize_path "$OPENCODE_INSTALL_DIR"
- return
- fi
- # Auto-detect: prefer local project install, fall back to global
- local local_path
- 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
- # Neither exists — return local path so main() gives a clear error
- echo "$local_path"
- fi
- }
- #############################################################################
- # Update Logic
- #############################################################################
- update_component() {
- local path="$1"
- local install_dir="$2"
- 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
- local url="${REPO_URL}/.opencode/${relative_path}"
- local backup="${path}.backup"
- 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
- if update_component "$file" "$install_dir"; then
- updated=$((updated + 1))
- else
- failed=$((failed + 1))
- fi
- done < <(find "$install_dir" -name "*.md" -type f -print0)
- # Update TypeScript files
- 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 "*.ts" -type f -not -path "*/node_modules/*" -print0)
- # Update shell scripts inside install dir
- 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!"
- }
- main "$@"
|