1- #! /usr/bin/env bash
2- set -euo pipefail
3- APP=opencode
4-
5- RED=' \033[0;31m'
6- GREEN=' \033[0;32m'
7- YELLOW=' \033[1;33m'
8- ORANGE=' \033[38;2;255;140;0m'
9- NC=' \033[0m' # No Color
10-
11- requested_version=${VERSION:- }
12- if [[ " $requested_version " == " latest" ]]; then
13- requested_version=" "
14- fi
15-
16- os=$( uname -s | tr ' [:upper:]' ' [:lower:]' )
17- if [[ " $os " == " darwin" ]]; then
18- os=" darwin"
19- fi
20- arch=$( uname -m)
21-
22- if [[ " $arch " == " aarch64" ]]; then
23- arch=" arm64"
24- elif [[ " $arch " == " x86_64" ]]; then
25- arch=" x64"
26- fi
27-
28- filename=" $APP -$os -$arch .zip"
29-
30-
31- case " $filename " in
32- * " -linux-" * )
33- [[ " $arch " == " x64" || " $arch " == " arm64" ]] || exit 1
34- ;;
35- * " -darwin-" * )
36- [[ " $arch " == " x64" || " $arch " == " arm64" ]] || exit 1
37- ;;
38- * " -windows-" * )
39- [[ " $arch " == " x64" ]] || exit 1
40- ;;
41- * )
42- echo -e " ${RED} Unsupported OS/Arch: $os /$arch ${NC} "
43- exit 1
44- ;;
45- esac
46-
47- # Set installation directory based on whether script is run as root
48- if [[ $EUID -eq 0 ]]; then
49- INSTALL_DIR=" /usr/local/bin"
50- else
51- INSTALL_DIR=" $HOME /.opencode/bin"
52- fi
53- mkdir -p " $INSTALL_DIR "
54-
55- if [ -z " $requested_version " ]; then
56- url=" https://github.com/sst/opencode/releases/latest/download/$filename "
57- specific_version=$( curl -s https://api.github.com/repos/sst/opencode/releases/latest | awk -F' "' ' /"tag_name": "/ {gsub(/^v/, "", $4); print $4}' )
58-
59- if [[ $? -ne 0 || -z " $specific_version " ]]; then
60- echo -e " ${RED} Failed to fetch version information${NC} "
61- exit 1
1+ #! /bin/bash
2+ set -o errexit
3+ set -o pipefail
4+ set -o noclobber
5+ set -o nounset
6+ set -o allexport
7+ readonly githubRepository=' sst/opencode'
8+ readonly binaryName=' opencode'
9+ readonly versionArgument=' --version'
10+ readonly downloadUrlTemplate=' https://github.com/${githubRepository}/releases/download/v${version}/${name}-linux-${architecture}.zip'
11+ readonly binaryTargetFolder=' /usr/local/bin'
12+ readonly name=" ${githubRepository##*/ } "
13+ apt_get_update () {
14+ if [ " $( find /var/lib/apt/lists/* | wc -l) " = " 0" ]; then
15+ echo " Running apt-get update..."
16+ apt-get update -y
17+ fi
18+ }
19+ apt_get_checkinstall () {
20+ if ! dpkg -s " $@ " > /dev/null 2>&1 ; then
21+ apt_get_update
22+ DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends --no-install-suggests --option ' Debug::pkgProblemResolver=true' --option ' Debug::pkgAcquire::Worker=1' " $@ "
23+ fi
24+ }
25+ apt_get_cleanup () {
26+ apt-get clean
27+ rm -rf /var/lib/apt/lists/*
28+ }
29+ check_curl_envsubst_file_unzip_installed () {
30+ declare -a requiredAptPackagesMissing=()
31+ if ! [ -r ' /etc/ssl/certs/ca-certificates.crt' ]; then
32+ requiredAptPackagesMissing+=(' ca-certificates' )
33+ fi
34+ if ! command -v curl > /dev/null 2>&1 ; then
35+ requiredAptPackagesMissing+=(' curl' )
36+ fi
37+ if ! command -v envsubst > /dev/null 2>&1 ; then
38+ requiredAptPackagesMissing+=(' gettext-base' )
39+ fi
40+ if ! command -v file > /dev/null 2>&1 ; then
41+ requiredAptPackagesMissing+=(' file' )
42+ fi
43+ if ! command -v unzip > /dev/null 2>&1 ; then
44+ requiredAptPackagesMissing+=(' unzip' )
45+ fi
46+ declare -i requiredAptPackagesMissingCount=${# requiredAptPackagesMissing[@]}
47+ if [ $requiredAptPackagesMissingCount -gt 0 ]; then
48+ apt_get_update
49+ apt_get_checkinstall " ${requiredAptPackagesMissing[@]} "
50+ apt_get_cleanup
6251 fi
63- else
64- url=" https://github.com/sst/opencode/releases/download/v${requested_version} /$filename "
65- specific_version=$requested_version
66- fi
67-
68- print_message () {
69- local level=$1
70- local message=$2
71- local color=" "
72-
73- case $level in
74- info) color=" ${GREEN} " ;;
75- warning) color=" ${YELLOW} " ;;
76- error) color=" ${RED} " ;;
77- esac
78-
79- echo -e " ${color}${message}${NC} "
8052}
81-
82- check_version () {
83- if command -v opencode > /dev/null 2>&1 ; then
84- opencode_path=$( which opencode)
85-
86-
87- # # TODO: check if version is installed
88- # installed_version=$(opencode version)
89- installed_version=" 0.0.1"
90- installed_version=$( echo $installed_version | awk ' {print $2}' )
91-
92- if [[ " $installed_version " != " $specific_version " ]]; then
93- print_message info " Installed version: ${YELLOW} $installed_version ."
94- else
95- print_message info " Version ${YELLOW} $specific_version ${GREEN} already installed"
96- exit 0
97- fi
53+ curl_check_url () {
54+ local url=$1
55+ local status_code
56+ status_code=$( curl -s -o /dev/null -w ' %{http_code}' " $url " )
57+ if [ " $status_code " -ne 200 ] && [ " $status_code " -ne 302 ]; then
58+ echo " Failed to download '$url '. Status code: $status_code ."
59+ return 1
9860 fi
9961}
100-
101- download_and_install () {
102- print_message info " Downloading ${ORANGE} opencode ${GREEN} version: ${YELLOW} $specific_version ${GREEN} ..."
103- mkdir -p opencodetmp && cd opencodetmp
104- curl -# -L -o " $filename " " $url "
105- unzip -q " $filename "
106- mv opencode " $INSTALL_DIR "
107- cd .. && rm -rf opencodetmp
62+ curl_download_stdout () {
63+ local url=$1
64+ curl \
65+ --silent \
66+ --location \
67+ --output ' -' \
68+ --connect-timeout 5 \
69+ " $url "
70+ }
71+ curl_download_unzip () {
72+ local url=$1
73+ local strip=$2
74+ local target=$3
75+ local bin_path=$4
76+ local temp_file=$( mktemp)
77+ curl_download_stdout " $url " > " $temp_file "
78+ unzip -j " $temp_file " " $bin_path " -d " $target "
79+ rm " $temp_file "
80+ }
81+ debian_get_arch () {
82+ echo " $( dpkg --print-architecture) "
83+ }
84+ echo_banner () {
85+ local text=" $1 "
86+ echo -e " \e[1m\e[97m\e[41m$text \e[0m"
10887}
109-
110- check_version
111- download_and_install
112-
113- # Skip PATH modification if installed to /usr/local/bin (already in PATH)
114- if [[ " $INSTALL_DIR " == " /usr/local/bin" ]]; then
115- print_message info " Binary installed to ${ORANGE} /usr/local/bin${GREEN} (already in \$ PATH)"
116-
117- if [ -n " ${GITHUB_ACTIONS-} " ] && [ " ${GITHUB_ACTIONS} " == " true" ]; then
118- echo " $INSTALL_DIR " >> $GITHUB_PATH
119- print_message info " Added $INSTALL_DIR to \$ GITHUB_PATH"
88+ github_list_releases () {
89+ if [ -z " $1 " ]; then
90+ echo " Usage: list_github_releases <owner/repo>"
91+ return 1
12092 fi
121-
122- exit 0
123- fi
124-
125- add_to_path () {
126- local config_file=$1
127- local command=$2
128-
129- if grep -Fxq " $command " " $config_file " ; then
130- print_message info " Command already exists in $config_file , skipping write."
131- elif [[ -w $config_file ]]; then
132- echo -e " \n# opencode" >> " $config_file "
133- echo " $command " >> " $config_file "
134- print_message info " Successfully added ${ORANGE} opencode ${GREEN} to \$ PATH in $config_file "
135- else
136- print_message warning " Manually add the directory to $config_file (or similar):"
137- print_message info " $command "
93+ local repo=" $1 "
94+ local url=" https://api.github.com/repos/$repo /releases"
95+ curl -s " $url " | grep -Po ' "tag_name": "\K.*?(?=")' | grep -E ' ^v?[0-9]+\.[0-9]+\.[0-9]+$' | sed ' s/^v//'
96+ }
97+ github_get_latest_release () {
98+ if [ -z " $1 " ]; then
99+ echo " Usage: get_latest_github_release <owner/repo>"
100+ return 1
138101 fi
102+ github_list_releases " $1 " | head -n 1
139103}
140-
141- XDG_CONFIG_HOME=${XDG_CONFIG_HOME:- $HOME / .config}
142-
143- current_shell=$( basename " $SHELL " )
144- case $current_shell in
145- fish)
146- config_files=" $HOME /.config/fish/config.fish"
147- ;;
148- zsh)
149- config_files=" $HOME /.zshrc $HOME /.zshenv $XDG_CONFIG_HOME /zsh/.zshrc $XDG_CONFIG_HOME /zsh/.zshenv"
150- ;;
151- bash)
152- config_files=" $HOME /.bashrc $HOME /.bash_profile $HOME /.profile $XDG_CONFIG_HOME /bash/.bashrc $XDG_CONFIG_HOME /bash/.bash_profile"
153- ;;
154- ash)
155- config_files=" $HOME /.ashrc $HOME /.profile /etc/profile"
156- ;;
157- sh)
158- config_files=" $HOME /.ashrc $HOME /.profile /etc/profile"
159- ;;
160- * )
161- # Default case if none of the above matches
162- config_files=" $HOME /.bashrc $HOME /.bash_profile $XDG_CONFIG_HOME /bash/.bashrc $XDG_CONFIG_HOME /bash/.bash_profile"
163- ;;
164- esac
165-
166- config_file=" "
167- for file in $config_files ; do
168- if [[ -f $file ]]; then
169- config_file=$file
170- break
104+ utils_check_version () {
105+ local version=$1
106+ if ! [[ " ${version:- } " =~ ^(latest| [0-9]+\. [0-9]+\. [0-9]+)$ ]]; then
107+ printf >&2 ' === [ERROR] Option "version" (value: "%s") is not "latest" or valid semantic version format "X.Y.Z" !\n' \
108+ " $version "
109+ exit 1
171110 fi
172- done
173-
174- if [[ -z $config_file ]]; then
175- print_message error " No config file found for $current_shell . Checked files: ${config_files[@]} "
176- exit 1
177- fi
178-
179- if [[ " :$PATH :" != * " :$INSTALL_DIR :" * ]]; then
180- case $current_shell in
181- fish)
182- add_to_path " $config_file " " fish_add_path $INSTALL_DIR "
183- ;;
184- zsh)
185- add_to_path " $config_file " " export PATH=$INSTALL_DIR :\$ PATH"
186- ;;
187- bash)
188- add_to_path " $config_file " " export PATH=$INSTALL_DIR :\$ PATH"
189- ;;
190- ash)
191- add_to_path " $config_file " " export PATH=$INSTALL_DIR :\$ PATH"
192- ;;
193- sh)
194- add_to_path " $config_file " " export PATH=$INSTALL_DIR :\$ PATH"
195- ;;
196- * )
197- export PATH=$INSTALL_DIR :$PATH
198- print_message warning " Manually add the directory to $config_file (or similar):"
199- print_message info " export PATH=$INSTALL_DIR :\$ PATH"
200- ;;
201- esac
202- fi
203-
204- if [ -n " ${GITHUB_ACTIONS-} " ] && [ " ${GITHUB_ACTIONS} " == " true" ]; then
205- echo " $INSTALL_DIR " >> $GITHUB_PATH
206- print_message info " Added $INSTALL_DIR to \$ GITHUB_PATH"
207- fi
111+ }
112+ install () {
113+ utils_check_version " $VERSION "
114+ check_curl_envsubst_file_unzip_installed
115+ readonly architecture=" $( debian_get_arch) "
116+ readonly binaryTargetPathTemplate=' ${binaryTargetFolder}/${binaryName}'
117+ if [ " $VERSION " == ' latest' ] || [ -z " $VERSION " ]; then
118+ VERSION=$( github_get_latest_release " $githubRepository " )
119+ fi
120+ readonly version=" ${VERSION:? } "
121+ readonly downloadUrl=" $( echo -n " $downloadUrlTemplate " | envsubst) "
122+ curl_check_url " $downloadUrl "
123+ readonly binaryPathInArchive=" $binaryName "
124+ readonly stripComponents=" $( echo -n " $binaryPathInArchive " | awk -F' /' ' {print NF-1}' ) "
125+ readonly binaryTargetPath=" $( echo -n " $binaryTargetPathTemplate " | envsubst) "
126+ curl_download_unzip " $downloadUrl " " $stripComponents " " $binaryTargetFolder " " $binaryPathInArchive "
127+ chmod 755 " $binaryTargetPath "
128+ }
129+ echo_banner " devcontainer.community"
130+ echo " Installing $name ..."
131+ install " $@ "
132+ echo " (*) Done!"
0 commit comments