install 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. APP=opencode
  4. MUTED='\033[0;2m'
  5. RED='\033[0;31m'
  6. ORANGE='\033[38;5;214m'
  7. NC='\033[0m' # No Color
  8. requested_version=${VERSION:-}
  9. raw_os=$(uname -s)
  10. os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
  11. case "$raw_os" in
  12. Darwin*) os="darwin" ;;
  13. Linux*) os="linux" ;;
  14. MINGW*|MSYS*|CYGWIN*) os="windows" ;;
  15. esac
  16. arch=$(uname -m)
  17. if [[ "$arch" == "aarch64" ]]; then
  18. arch="arm64"
  19. fi
  20. if [[ "$arch" == "x86_64" ]]; then
  21. arch="x64"
  22. fi
  23. if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
  24. rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
  25. if [ "$rosetta_flag" = "1" ]; then
  26. arch="arm64"
  27. fi
  28. fi
  29. combo="$os-$arch"
  30. case "$combo" in
  31. linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
  32. ;;
  33. *)
  34. echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
  35. exit 1
  36. ;;
  37. esac
  38. archive_ext=".zip"
  39. if [ "$os" = "linux" ]; then
  40. archive_ext=".tar.gz"
  41. fi
  42. is_musl=false
  43. if [ "$os" = "linux" ]; then
  44. if [ -f /etc/alpine-release ]; then
  45. is_musl=true
  46. fi
  47. if command -v ldd >/dev/null 2>&1; then
  48. if ldd --version 2>&1 | grep -qi musl; then
  49. is_musl=true
  50. fi
  51. fi
  52. fi
  53. needs_baseline=false
  54. if [ "$arch" = "x64" ]; then
  55. if [ "$os" = "linux" ]; then
  56. if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then
  57. needs_baseline=true
  58. fi
  59. fi
  60. if [ "$os" = "darwin" ]; then
  61. avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
  62. if [ "$avx2" != "1" ]; then
  63. needs_baseline=true
  64. fi
  65. fi
  66. fi
  67. target="$os-$arch"
  68. if [ "$needs_baseline" = "true" ]; then
  69. target="$target-baseline"
  70. fi
  71. if [ "$is_musl" = "true" ]; then
  72. target="$target-musl"
  73. fi
  74. filename="$APP-$target$archive_ext"
  75. if [ "$os" = "linux" ]; then
  76. if ! command -v tar >/dev/null 2>&1; then
  77. echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
  78. exit 1
  79. fi
  80. else
  81. if ! command -v unzip >/dev/null 2>&1; then
  82. echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
  83. exit 1
  84. fi
  85. fi
  86. INSTALL_DIR=$HOME/.opencode/bin
  87. mkdir -p "$INSTALL_DIR"
  88. if [ -z "$requested_version" ]; then
  89. url="https://github.com/sst/opencode/releases/latest/download/$filename"
  90. specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
  91. if [[ $? -ne 0 || -z "$specific_version" ]]; then
  92. echo -e "${RED}Failed to fetch version information${NC}"
  93. exit 1
  94. fi
  95. else
  96. url="https://github.com/sst/opencode/releases/download/v${requested_version}/$filename"
  97. specific_version=$requested_version
  98. fi
  99. print_message() {
  100. local level=$1
  101. local message=$2
  102. local color=""
  103. case $level in
  104. info) color="${NC}" ;;
  105. warning) color="${NC}" ;;
  106. error) color="${RED}" ;;
  107. esac
  108. echo -e "${color}${message}${NC}"
  109. }
  110. check_version() {
  111. if command -v opencode >/dev/null 2>&1; then
  112. opencode_path=$(which opencode)
  113. ## TODO: check if version is installed
  114. # installed_version=$(opencode version)
  115. installed_version="0.0.1"
  116. installed_version=$(echo $installed_version | awk '{print $2}')
  117. if [[ "$installed_version" != "$specific_version" ]]; then
  118. print_message info "${MUTED}Installed version: ${NC}$installed_version."
  119. else
  120. print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
  121. exit 0
  122. fi
  123. fi
  124. }
  125. unbuffered_sed() {
  126. if echo | sed -u -e "" >/dev/null 2>&1; then
  127. sed -nu "$@"
  128. elif echo | sed -l -e "" >/dev/null 2>&1; then
  129. sed -nl "$@"
  130. else
  131. local pad="$(printf "\n%512s" "")"
  132. sed -ne "s/$/\\${pad}/" "$@"
  133. fi
  134. }
  135. print_progress() {
  136. local bytes="$1"
  137. local length="$2"
  138. [ "$length" -gt 0 ] || return 0
  139. local width=50
  140. local percent=$(( bytes * 100 / length ))
  141. [ "$percent" -gt 100 ] && percent=100
  142. local on=$(( percent * width / 100 ))
  143. local off=$(( width - on ))
  144. local filled=$(printf "%*s" "$on" "")
  145. filled=${filled// /■}
  146. local empty=$(printf "%*s" "$off" "")
  147. empty=${empty// /・}
  148. printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
  149. }
  150. download_with_progress() {
  151. local url="$1"
  152. local output="$2"
  153. if [ -t 2 ]; then
  154. exec 4>&2
  155. else
  156. exec 4>/dev/null
  157. fi
  158. local tmp_dir=${TMPDIR:-/tmp}
  159. local basename="${tmp_dir}/opencode_install_$$"
  160. local tracefile="${basename}.trace"
  161. rm -f "$tracefile"
  162. mkfifo "$tracefile"
  163. # Hide cursor
  164. printf "\033[?25l" >&4
  165. trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
  166. (
  167. curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
  168. ) &
  169. local curl_pid=$!
  170. unbuffered_sed \
  171. -e 'y/ACDEGHLNORTV/acdeghlnortv/' \
  172. -e '/^0000: content-length:/p' \
  173. -e '/^<= recv data/p' \
  174. "$tracefile" | \
  175. {
  176. local length=0
  177. local bytes=0
  178. while IFS=" " read -r -a line; do
  179. [ "${#line[@]}" -lt 2 ] && continue
  180. local tag="${line[0]} ${line[1]}"
  181. if [ "$tag" = "0000: content-length:" ]; then
  182. length="${line[2]}"
  183. length=$(echo "$length" | tr -d '\r')
  184. bytes=0
  185. elif [ "$tag" = "<= recv" ]; then
  186. local size="${line[3]}"
  187. bytes=$(( bytes + size ))
  188. if [ "$length" -gt 0 ]; then
  189. print_progress "$bytes" "$length"
  190. fi
  191. fi
  192. done
  193. }
  194. wait $curl_pid
  195. local ret=$?
  196. echo "" >&4
  197. return $ret
  198. }
  199. download_and_install() {
  200. print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
  201. local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
  202. mkdir -p "$tmp_dir"
  203. if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
  204. # Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
  205. curl -# -L -o "$tmp_dir/$filename" "$url"
  206. fi
  207. if [ "$os" = "linux" ]; then
  208. tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
  209. else
  210. unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
  211. fi
  212. mv "$tmp_dir/opencode" "$INSTALL_DIR"
  213. chmod 755 "${INSTALL_DIR}/opencode"
  214. rm -rf "$tmp_dir"
  215. }
  216. check_version
  217. download_and_install
  218. add_to_path() {
  219. local config_file=$1
  220. local command=$2
  221. if grep -Fxq "$command" "$config_file"; then
  222. print_message info "Command already exists in $config_file, skipping write."
  223. elif [[ -w $config_file ]]; then
  224. echo -e "\n# opencode" >> "$config_file"
  225. echo "$command" >> "$config_file"
  226. print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
  227. else
  228. print_message warning "Manually add the directory to $config_file (or similar):"
  229. print_message info " $command"
  230. fi
  231. }
  232. XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
  233. current_shell=$(basename "$SHELL")
  234. case $current_shell in
  235. fish)
  236. config_files="$HOME/.config/fish/config.fish"
  237. ;;
  238. zsh)
  239. config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
  240. ;;
  241. bash)
  242. config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
  243. ;;
  244. ash)
  245. config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
  246. ;;
  247. sh)
  248. config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
  249. ;;
  250. *)
  251. # Default case if none of the above matches
  252. config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
  253. ;;
  254. esac
  255. config_file=""
  256. for file in $config_files; do
  257. if [[ -f $file ]]; then
  258. config_file=$file
  259. break
  260. fi
  261. done
  262. if [[ -z $config_file ]]; then
  263. print_message error "No config file found for $current_shell. Checked files: ${config_files[@]}"
  264. exit 1
  265. fi
  266. if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
  267. case $current_shell in
  268. fish)
  269. add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
  270. ;;
  271. zsh)
  272. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  273. ;;
  274. bash)
  275. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  276. ;;
  277. ash)
  278. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  279. ;;
  280. sh)
  281. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  282. ;;
  283. *)
  284. export PATH=$INSTALL_DIR:$PATH
  285. print_message warning "Manually add the directory to $config_file (or similar):"
  286. print_message info " export PATH=$INSTALL_DIR:\$PATH"
  287. ;;
  288. esac
  289. fi
  290. if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
  291. echo "$INSTALL_DIR" >> $GITHUB_PATH
  292. print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
  293. fi
  294. echo -e ""
  295. echo -e "${MUTED}  ${NC} ▄ "
  296. echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█"
  297. echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀"
  298. echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
  299. echo -e ""
  300. echo -e ""
  301. echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
  302. echo -e ""
  303. echo -e "cd <project> ${MUTED}# Open directory${NC}"
  304. echo -e "opencode ${MUTED}# Run command${NC}"
  305. echo -e ""
  306. echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
  307. echo -e ""
  308. echo -e ""