| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- #!/usr/bin/env bash
- set -euo pipefail
- APP=opencode
- MUTED='\033[0;2m'
- RED='\033[0;31m'
- ORANGE='\033[38;5;214m'
- NC='\033[0m' # No Color
- usage() {
- cat <<EOF
- OpenCode Installer
- Usage: install.sh [options]
- Options:
- -h, --help Display this help message
- -v, --version <version> Install a specific version (e.g., 1.0.180)
- -b, --binary <path> Install from a local binary instead of downloading
- --no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
- Examples:
- curl -fsSL https://opencode.ai/install | bash
- curl -fsSL https://opencode.ai/install | bash -s -- --version 1.0.180
- ./install --binary /path/to/opencode
- EOF
- }
- requested_version=${VERSION:-}
- no_modify_path=false
- binary_path=""
- while [[ $# -gt 0 ]]; do
- case "$1" in
- -h|--help)
- usage
- exit 0
- ;;
- -v|--version)
- if [[ -n "${2:-}" ]]; then
- requested_version="$2"
- shift 2
- else
- echo -e "${RED}Error: --version requires a version argument${NC}"
- exit 1
- fi
- ;;
- -b|--binary)
- if [[ -n "${2:-}" ]]; then
- binary_path="$2"
- shift 2
- else
- echo -e "${RED}Error: --binary requires a path argument${NC}"
- exit 1
- fi
- ;;
- --no-modify-path)
- no_modify_path=true
- shift
- ;;
- *)
- echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
- shift
- ;;
- esac
- done
- INSTALL_DIR=$HOME/.opencode/bin
- mkdir -p "$INSTALL_DIR"
- # If --binary is provided, skip all download/detection logic
- if [ -n "$binary_path" ]; then
- if [ ! -f "$binary_path" ]; then
- echo -e "${RED}Error: Binary not found at ${binary_path}${NC}"
- exit 1
- fi
- specific_version="local"
- else
- raw_os=$(uname -s)
- os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
- case "$raw_os" in
- Darwin*) os="darwin" ;;
- Linux*) os="linux" ;;
- MINGW*|MSYS*|CYGWIN*) os="windows" ;;
- esac
- arch=$(uname -m)
- if [[ "$arch" == "aarch64" ]]; then
- arch="arm64"
- fi
- if [[ "$arch" == "x86_64" ]]; then
- arch="x64"
- fi
- if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
- rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
- if [ "$rosetta_flag" = "1" ]; then
- arch="arm64"
- fi
- fi
- combo="$os-$arch"
- case "$combo" in
- linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
- ;;
- *)
- echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
- exit 1
- ;;
- esac
- archive_ext=".zip"
- if [ "$os" = "linux" ]; then
- archive_ext=".tar.gz"
- fi
- is_musl=false
- if [ "$os" = "linux" ]; then
- if [ -f /etc/alpine-release ]; then
- is_musl=true
- fi
- if command -v ldd >/dev/null 2>&1; then
- if ldd --version 2>&1 | grep -qi musl; then
- is_musl=true
- fi
- fi
- fi
- needs_baseline=false
- if [ "$arch" = "x64" ]; then
- if [ "$os" = "linux" ]; then
- if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then
- needs_baseline=true
- fi
- fi
- if [ "$os" = "darwin" ]; then
- avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
- if [ "$avx2" != "1" ]; then
- needs_baseline=true
- fi
- fi
- fi
- target="$os-$arch"
- if [ "$needs_baseline" = "true" ]; then
- target="$target-baseline"
- fi
- if [ "$is_musl" = "true" ]; then
- target="$target-musl"
- fi
- filename="$APP-$target$archive_ext"
- if [ "$os" = "linux" ]; then
- if ! command -v tar >/dev/null 2>&1; then
- echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
- exit 1
- fi
- else
- if ! command -v unzip >/dev/null 2>&1; then
- echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
- exit 1
- fi
- fi
- if [ -z "$requested_version" ]; then
- url="https://github.com/anomalyco/opencode/releases/latest/download/$filename"
- specific_version=$(curl -s https://api.github.com/repos/anomalyco/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
- if [[ $? -ne 0 || -z "$specific_version" ]]; then
- echo -e "${RED}Failed to fetch version information${NC}"
- exit 1
- fi
- else
- # Strip leading 'v' if present
- requested_version="${requested_version#v}"
- url="https://github.com/anomalyco/opencode/releases/download/v${requested_version}/$filename"
- specific_version=$requested_version
- # Verify the release exists before downloading
- http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/anomalyco/opencode/releases/tag/v${requested_version}")
- if [ "$http_status" = "404" ]; then
- echo -e "${RED}Error: Release v${requested_version} not found${NC}"
- echo -e "${MUTED}Available releases: https://github.com/anomalyco/opencode/releases${NC}"
- exit 1
- fi
- fi
- fi
- print_message() {
- local level=$1
- local message=$2
- local color=""
- case $level in
- info) color="${NC}" ;;
- warning) color="${NC}" ;;
- error) color="${RED}" ;;
- esac
- echo -e "${color}${message}${NC}"
- }
- check_version() {
- if command -v opencode >/dev/null 2>&1; then
- opencode_path=$(which opencode)
- ## Check the installed version
- installed_version=$(opencode --version 2>/dev/null || echo "")
- if [[ "$installed_version" != "$specific_version" ]]; then
- print_message info "${MUTED}Installed version: ${NC}$installed_version."
- else
- print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
- exit 0
- fi
- fi
- }
- unbuffered_sed() {
- if echo | sed -u -e "" >/dev/null 2>&1; then
- sed -nu "$@"
- elif echo | sed -l -e "" >/dev/null 2>&1; then
- sed -nl "$@"
- else
- local pad="$(printf "\n%512s" "")"
- sed -ne "s/$/\\${pad}/" "$@"
- fi
- }
- print_progress() {
- local bytes="$1"
- local length="$2"
- [ "$length" -gt 0 ] || return 0
- local width=50
- local percent=$(( bytes * 100 / length ))
- [ "$percent" -gt 100 ] && percent=100
- local on=$(( percent * width / 100 ))
- local off=$(( width - on ))
- local filled=$(printf "%*s" "$on" "")
- filled=${filled// /■}
- local empty=$(printf "%*s" "$off" "")
- empty=${empty// /・}
- printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
- }
- download_with_progress() {
- local url="$1"
- local output="$2"
- if [ -t 2 ]; then
- exec 4>&2
- else
- exec 4>/dev/null
- fi
- local tmp_dir=${TMPDIR:-/tmp}
- local basename="${tmp_dir}/opencode_install_$$"
- local tracefile="${basename}.trace"
- rm -f "$tracefile"
- mkfifo "$tracefile"
- # Hide cursor
- printf "\033[?25l" >&4
- trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
- (
- curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
- ) &
- local curl_pid=$!
- unbuffered_sed \
- -e 'y/ACDEGHLNORTV/acdeghlnortv/' \
- -e '/^0000: content-length:/p' \
- -e '/^<= recv data/p' \
- "$tracefile" | \
- {
- local length=0
- local bytes=0
- while IFS=" " read -r -a line; do
- [ "${#line[@]}" -lt 2 ] && continue
- local tag="${line[0]} ${line[1]}"
- if [ "$tag" = "0000: content-length:" ]; then
- length="${line[2]}"
- length=$(echo "$length" | tr -d '\r')
- bytes=0
- elif [ "$tag" = "<= recv" ]; then
- local size="${line[3]}"
- bytes=$(( bytes + size ))
- if [ "$length" -gt 0 ]; then
- print_progress "$bytes" "$length"
- fi
- fi
- done
- }
- wait $curl_pid
- local ret=$?
- echo "" >&4
- return $ret
- }
- download_and_install() {
- print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
- local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
- mkdir -p "$tmp_dir"
- if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
- # Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
- curl -# -L -o "$tmp_dir/$filename" "$url"
- fi
- if [ "$os" = "linux" ]; then
- tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
- else
- unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
- fi
- mv "$tmp_dir/opencode" "$INSTALL_DIR"
- chmod 755 "${INSTALL_DIR}/opencode"
- rm -rf "$tmp_dir"
- }
- install_from_binary() {
- print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}from: ${NC}$binary_path"
- cp "$binary_path" "${INSTALL_DIR}/opencode"
- chmod 755 "${INSTALL_DIR}/opencode"
- }
- if [ -n "$binary_path" ]; then
- install_from_binary
- else
- check_version
- download_and_install
- fi
- add_to_path() {
- local config_file=$1
- local command=$2
- if grep -Fxq "$command" "$config_file"; then
- print_message info "Command already exists in $config_file, skipping write."
- elif [[ -w $config_file ]]; then
- echo -e "\n# opencode" >> "$config_file"
- echo "$command" >> "$config_file"
- print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
- else
- print_message warning "Manually add the directory to $config_file (or similar):"
- print_message info " $command"
- fi
- }
- XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
- current_shell=$(basename "$SHELL")
- case $current_shell in
- fish)
- config_files="$HOME/.config/fish/config.fish"
- ;;
- zsh)
- config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
- ;;
- bash)
- config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
- ;;
- ash)
- config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
- ;;
- sh)
- config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
- ;;
- *)
- # Default case if none of the above matches
- config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
- ;;
- esac
- if [[ "$no_modify_path" != "true" ]]; then
- config_file=""
- for file in $config_files; do
- if [[ -f $file ]]; then
- config_file=$file
- break
- fi
- done
- if [[ -z $config_file ]]; then
- print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"
- print_message info " export PATH=$INSTALL_DIR:\$PATH"
- elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
- case $current_shell in
- fish)
- add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
- ;;
- zsh)
- add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
- ;;
- bash)
- add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
- ;;
- ash)
- add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
- ;;
- sh)
- add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
- ;;
- *)
- export PATH=$INSTALL_DIR:$PATH
- print_message warning "Manually add the directory to $config_file (or similar):"
- print_message info " export PATH=$INSTALL_DIR:\$PATH"
- ;;
- esac
- fi
- fi
- if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
- echo "$INSTALL_DIR" >> $GITHUB_PATH
- print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
- fi
- echo -e ""
- echo -e "${MUTED} ${NC} ▄ "
- echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█"
- echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀"
- echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
- echo -e ""
- echo -e ""
- echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
- echo -e ""
- echo -e "cd <project> ${MUTED}# Open directory${NC}"
- echo -e "opencode ${MUTED}# Run command${NC}"
- echo -e ""
- echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
- echo -e ""
- echo -e ""
|