update.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. #!/usr/bin/env bash
  2. #############################################################################
  3. # OpenAgents Control Updater
  4. # Updates existing OpenCode components to latest versions
  5. #
  6. # Compatible with:
  7. # - macOS (bash 3.2+)
  8. # - Linux (bash 3.2+)
  9. # - Windows (Git Bash, WSL)
  10. #
  11. # Usage:
  12. # ./update.sh # Auto-detect install location
  13. # ./update.sh --install-dir PATH # Update a specific install path
  14. #
  15. # Environment variables:
  16. # OPENCODE_INSTALL_DIR # Override default install directory
  17. # OPENCODE_BRANCH # Branch to pull from (default: main)
  18. #############################################################################
  19. set -e
  20. # Detect platform
  21. PLATFORM="$(uname -s)"
  22. case "$PLATFORM" in
  23. Linux*) PLATFORM="Linux";;
  24. Darwin*) PLATFORM="macOS";;
  25. CYGWIN*|MINGW*|MSYS*) PLATFORM="Windows";;
  26. *) PLATFORM="Unknown";;
  27. esac
  28. # Colors (disable on Windows terminals without color support)
  29. if [ "$PLATFORM" = "Windows" ] && [ -z "$WT_SESSION" ] && [ -z "$ConEmuPID" ]; then
  30. RED=''
  31. GREEN=''
  32. YELLOW=''
  33. BLUE=''
  34. CYAN=''
  35. BOLD=''
  36. NC=''
  37. else
  38. RED='\033[0;31m'
  39. GREEN='\033[0;32m'
  40. YELLOW='\033[1;33m'
  41. BLUE='\033[0;34m'
  42. CYAN='\033[0;36m'
  43. BOLD='\033[1m'
  44. NC='\033[0m'
  45. fi
  46. BRANCH="${OPENCODE_BRANCH:-main}"
  47. REPO_URL="https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/${BRANCH}"
  48. # CLI argument for custom install dir (overrides env var)
  49. CUSTOM_INSTALL_DIR=""
  50. # Track backup files for cleanup on exit
  51. BACKUP_FILES=()
  52. # Clean up any leftover backup files on exit/interrupt
  53. cleanup_backups() {
  54. for f in "${BACKUP_FILES[@]}"; do
  55. [ -f "$f" ] && rm -f "$f"
  56. done
  57. }
  58. trap cleanup_backups EXIT INT TERM
  59. #############################################################################
  60. # Utility Functions
  61. #############################################################################
  62. print_success() { echo -e "${GREEN}✓${NC} $1"; }
  63. print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
  64. print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
  65. print_error() { echo -e "${RED}✗${NC} $1" >&2; }
  66. print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
  67. print_header() {
  68. echo -e "${CYAN}${BOLD}"
  69. echo "╔════════════════════════════════════════════════════════════════╗"
  70. echo "║ ║"
  71. echo "║ OpenAgents Control Updater v1.1.0 ║"
  72. echo "║ ║"
  73. echo "╚════════════════════════════════════════════════════════════════╝"
  74. echo -e "${NC}"
  75. }
  76. print_usage() {
  77. echo "Usage: $0 [--install-dir PATH]"
  78. echo ""
  79. echo "Options:"
  80. echo " --install-dir PATH Update a specific installation directory"
  81. echo " --help Show this help message"
  82. echo ""
  83. echo "Environment variables:"
  84. echo " OPENCODE_INSTALL_DIR Override the default installation directory"
  85. echo " OPENCODE_BRANCH Branch to pull updates from (default: main)"
  86. echo ""
  87. echo "Examples:"
  88. echo " # Auto-detect and update"
  89. echo " $0"
  90. echo ""
  91. echo " # Update a global installation"
  92. echo " $0 --install-dir ~/.config/opencode"
  93. echo ""
  94. echo " # Update via environment variable"
  95. echo " export OPENCODE_INSTALL_DIR=~/.config/opencode && $0"
  96. }
  97. #############################################################################
  98. # Path Resolution
  99. #############################################################################
  100. get_global_install_path() {
  101. # Return platform-appropriate global installation path
  102. case "$PLATFORM" in
  103. macOS)
  104. echo "${HOME}/.config/opencode"
  105. ;;
  106. Linux)
  107. echo "${HOME}/.config/opencode"
  108. ;;
  109. Windows)
  110. # Windows Git Bash/WSL: Use same as Linux
  111. echo "${HOME}/.config/opencode"
  112. ;;
  113. *)
  114. echo "${HOME}/.config/opencode"
  115. ;;
  116. esac
  117. }
  118. normalize_path() {
  119. local input_path="$1"
  120. # Handle empty path
  121. if [ -z "$input_path" ]; then
  122. echo ""
  123. return 1
  124. fi
  125. local normalized_path
  126. # Expand tilde to $HOME (works on Linux, macOS, Windows Git Bash)
  127. if [[ $input_path == ~* ]]; then
  128. normalized_path="${HOME}${input_path:1}"
  129. else
  130. normalized_path="$input_path"
  131. fi
  132. # Convert backslashes to forward slashes (Windows compatibility)
  133. normalized_path="${normalized_path//\\//}"
  134. # Remove trailing slashes
  135. normalized_path="${normalized_path%/}"
  136. # If path is relative, make it absolute based on current directory
  137. if [[ ! "$normalized_path" = /* ]] && [[ ! "$normalized_path" =~ ^[A-Za-z]: ]]; then
  138. normalized_path="$(pwd)/${normalized_path}"
  139. fi
  140. echo "$normalized_path"
  141. return 0
  142. }
  143. resolve_install_dir() {
  144. local custom_dir="$1"
  145. # Priority: CLI arg → env var → auto-detect (local then global)
  146. if [ -n "$custom_dir" ]; then
  147. normalize_path "$custom_dir"
  148. return
  149. fi
  150. if [ -n "$OPENCODE_INSTALL_DIR" ]; then
  151. normalize_path "$OPENCODE_INSTALL_DIR"
  152. return
  153. fi
  154. # Auto-detect: prefer local project install, fall back to global
  155. local local_path
  156. local_path="$(pwd)/.opencode"
  157. local global_path
  158. global_path=$(get_global_install_path)
  159. if [ -d "$local_path" ]; then
  160. echo "$local_path"
  161. elif [ -d "$global_path" ]; then
  162. echo "$global_path"
  163. else
  164. # Neither exists — return local path so main() gives a clear error
  165. echo "$local_path"
  166. fi
  167. }
  168. #############################################################################
  169. # Update Logic
  170. #############################################################################
  171. update_component() {
  172. local path="$1"
  173. local install_dir="$2"
  174. local relative_path="${path#"$install_dir"/}"
  175. # Guard: reject paths that escaped the install dir
  176. if [[ "$relative_path" == /* ]] || [[ "$relative_path" == *..* ]]; then
  177. print_warning "Skipping suspicious path: $path"
  178. return 1
  179. fi
  180. local url="${REPO_URL}/.opencode/${relative_path}"
  181. local backup="${path}.backup"
  182. cp "$path" "$backup"
  183. BACKUP_FILES+=("$backup")
  184. if curl -fsSL "$url" -o "$path" 2>/dev/null; then
  185. print_success "Updated $path"
  186. rm -f "$backup"
  187. # Remove from tracking array (bash 3.2 compatible)
  188. local new_backups=()
  189. for f in "${BACKUP_FILES[@]}"; do
  190. [ "$f" != "$backup" ] && new_backups+=("$f")
  191. done
  192. BACKUP_FILES=("${new_backups[@]+"${new_backups[@]}"}")
  193. else
  194. print_warning "Could not update $path — restoring backup"
  195. mv "$backup" "$path"
  196. return 1
  197. fi
  198. }
  199. update_all_components() {
  200. local install_dir="$1"
  201. local updated=0
  202. local failed=0
  203. # Update markdown files
  204. while IFS= read -r -d '' file; do
  205. if update_component "$file" "$install_dir"; then
  206. updated=$((updated + 1))
  207. else
  208. failed=$((failed + 1))
  209. fi
  210. done < <(find "$install_dir" -name "*.md" -type f -print0)
  211. # Update TypeScript files
  212. while IFS= read -r -d '' file; do
  213. if update_component "$file" "$install_dir"; then
  214. updated=$((updated + 1))
  215. else
  216. failed=$((failed + 1))
  217. fi
  218. done < <(find "$install_dir" -name "*.ts" -type f -not -path "*/node_modules/*" -print0)
  219. # Update shell scripts inside install dir
  220. while IFS= read -r -d '' file; do
  221. if update_component "$file" "$install_dir"; then
  222. updated=$((updated + 1))
  223. else
  224. failed=$((failed + 1))
  225. fi
  226. done < <(find "$install_dir" -name "*.sh" -type f -print0)
  227. print_info "Updated: $updated file(s), failed: $failed file(s)"
  228. }
  229. #############################################################################
  230. # Argument Parsing
  231. #############################################################################
  232. parse_args() {
  233. while [[ $# -gt 0 ]]; do
  234. case "$1" in
  235. --install-dir=*)
  236. CUSTOM_INSTALL_DIR="${1#*=}"
  237. if [ -z "$CUSTOM_INSTALL_DIR" ]; then
  238. print_error "--install-dir requires a non-empty path"
  239. exit 1
  240. fi
  241. shift
  242. ;;
  243. --install-dir)
  244. if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
  245. CUSTOM_INSTALL_DIR="$2"
  246. shift 2
  247. else
  248. print_error "--install-dir requires a path argument"
  249. exit 1
  250. fi
  251. ;;
  252. --help|-h)
  253. print_usage
  254. exit 0
  255. ;;
  256. *)
  257. print_error "Unknown option: $1"
  258. print_usage
  259. exit 1
  260. ;;
  261. esac
  262. done
  263. }
  264. #############################################################################
  265. # Main
  266. #############################################################################
  267. main() {
  268. parse_args "$@"
  269. print_header
  270. local install_dir
  271. install_dir=$(resolve_install_dir "$CUSTOM_INSTALL_DIR")
  272. if [ ! -d "$install_dir" ]; then
  273. print_error "Installation directory not found: $install_dir"
  274. echo ""
  275. echo "Searched locations:"
  276. echo " 1. --install-dir argument"
  277. echo " 2. OPENCODE_INSTALL_DIR environment variable"
  278. echo " 3. Local path: $(pwd)/.opencode"
  279. echo " 4. Global path: $(get_global_install_path)"
  280. echo ""
  281. echo "Run install.sh first to install components, or specify the correct"
  282. echo "path with: $0 --install-dir PATH"
  283. exit 1
  284. fi
  285. if [ ! -w "$install_dir" ]; then
  286. print_error "No write permission for: $install_dir"
  287. exit 1
  288. fi
  289. print_info "Updating installation at: ${CYAN}${install_dir}${NC}"
  290. print_step "Updating components..."
  291. update_all_components "$install_dir"
  292. print_success "Update complete!"
  293. }
  294. main "$@"