badman99dev commited on
Commit
9197478
Β·
verified Β·
1 Parent(s): 8fdf326

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +57 -0
  2. entrypoint.sh +136 -0
  3. install.sh +470 -0
Dockerfile ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Node.js stable version
2
+ FROM node:18-slim
3
+
4
+
5
+ # 2. Install required system tools & HF CLI
6
+ RUN apt-get update && apt-get install -y \
7
+ curl \
8
+ bash \
9
+ git \
10
+ python3 \
11
+ python3-pip \
12
+ python3.11-venv \
13
+ build-essential \
14
+ gcc \
15
+ g++ \
16
+ make \
17
+ nano \
18
+ zip \
19
+ unzip \
20
+ procps \
21
+ inotify-tools \
22
+ && curl -LsSf https://hf.co/cli/install.sh | bash \
23
+ && rm -rf /var/lib/apt/lists/*
24
+
25
+
26
+ # 3. Copy OpenCode install script
27
+ COPY install.sh /install.sh
28
+ RUN chmod +x /install.sh
29
+
30
+
31
+ # 4. Install OpenCode from local script
32
+ RUN /install.sh
33
+
34
+
35
+ # 5. Setup folders
36
+ RUN mkdir -p /home && chmod 777 /home
37
+
38
+
39
+ # 6. Copy entrypoint script
40
+ COPY entrypoint.sh /entrypoint.sh
41
+ RUN chmod +x /entrypoint.sh
42
+
43
+
44
+ # 7. Environment settings with RAM limit
45
+ ENV OPENCODE_DATA_DIR=/home
46
+ ENV HOME=/home
47
+ ENV PATH="/root/.local/bin:/home/node/.local/bin:/home/.local/bin:$PATH"
48
+ ENV NODE_OPTIONS="--max-old-space-size=14336"
49
+ ENV OPENCODE_SERVER_AUTH=false
50
+
51
+
52
+ # 8. Expose port 7860
53
+ EXPOSE 7860
54
+
55
+
56
+ # 9. Run entrypoint script
57
+ CMD ["/entrypoint.sh"]
entrypoint.sh ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Ensure HF CLI is in PATH
4
+ export PATH="$HOME/.local/bin:$PATH"
5
+
6
+ # Set HF Token from Space Secrets
7
+ export HF_TOKEN="${HF_TOKEN}"
8
+
9
+ # Variables
10
+ BUCKET='hf://buckets/jerecom/backup'
11
+ SOURCE='/home'
12
+
13
+ # Configure Git
14
+ git config --global user.email 'badal@example.com'
15
+ git config --global user.name 'Badal'
16
+
17
+ # Set OpenCode path
18
+ OP_PATH=$(find / -name opencode -type f -printf '%h' -quit 2>/dev/null)
19
+ export PATH="$OP_PATH:$PATH"
20
+
21
+ # ============================================
22
+ # STEP 0: BUCKET CLEANUP (Delete .cache & .npm from bucket)
23
+ # ============================================
24
+ echo '=== [STEP 0] CLEANING BUCKET (Removing .cache and .npm) ==='
25
+ hf buckets rm "$BUCKET/.cache" --recursive -y 2>/dev/null && echo '=== [STEP 0] .cache removed from bucket ===' || echo '=== [STEP 0] .cache not found in bucket (skipping) ==='
26
+ hf buckets rm "$BUCKET/.npm" --recursive -y 2>/dev/null && echo '=== [STEP 0] .npm removed from bucket ===' || echo '=== [STEP 0] .npm not found in bucket (skipping) ==='
27
+ echo '=== [STEP 0] BUCKET CLEANUP DONE ==='
28
+
29
+ # ============================================
30
+ # STEP 1: RESTORE (Try, but continue if bucket empty)
31
+ # ============================================
32
+ echo '=== [STEP 1] RESTORING DATA FROM BUCKET ==='
33
+ if hf sync "$BUCKET" "$SOURCE" --delete 2>/dev/null; then
34
+ echo '=== [STEP 1] RESTORE COMPLETE ==='
35
+ else
36
+ echo '=== [STEP 1] Restore skipped (bucket empty or failed - continuing anyway) ==='
37
+ fi
38
+
39
+ # Auto-create /home/user if not exists
40
+ if [ ! -d "/home/user" ]; then
41
+ echo '=== Creating /home/user folder ==='
42
+ mkdir -p /home/user
43
+ fi
44
+
45
+ # ============================================
46
+ # STEP 2: START OPENCODE (with RAM watchdog)
47
+ # ============================================
48
+
49
+ # RAM limit: 14GB in KB
50
+ RAM_LIMIT_KB=$((14 * 1024 * 1024))
51
+
52
+ start_opencode() {
53
+ echo '=== [STEP 2] STARTING OPENCODE ==='
54
+ export OPENCODE_SERVER_USERNAME=${OPENCODE_SERVER_USERNAME}
55
+ export OPENCODE_SERVER_PASSWORD=${OPENCODE_SERVER_PASSWORD}
56
+ opencode web --port 7860 --hostname 0.0.0.0 > /tmp/opencode.log 2>&1 &
57
+ OPENCODE_PID=$!
58
+ echo "=== [STEP 2] OPENCODE STARTED (PID: $OPENCODE_PID) ==="
59
+ }
60
+
61
+ ram_watchdog() {
62
+ while true; do
63
+ if [ -n "$OPENCODE_PID" ] && kill -0 "$OPENCODE_PID" 2>/dev/null; then
64
+ # OpenCode + uske saare child processes ki total RAM (KB)
65
+ USED_KB=$(ps --no-headers -o rss --ppid "$OPENCODE_PID" 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
66
+ SELF_KB=$(ps --no-headers -o rss -p "$OPENCODE_PID" 2>/dev/null | awk '{print $1+0}')
67
+ TOTAL_KB=$((USED_KB + SELF_KB))
68
+
69
+ #echo "=== [RAM] OpenCode using: $((TOTAL_KB / 1024))MB / 14336MB ==="
70
+
71
+ if [ "$TOTAL_KB" -ge "$RAM_LIMIT_KB" ]; then
72
+ echo "=== [RAM] LIMIT EXCEEDED! Killing OpenCode (${TOTAL_KB}KB >= ${RAM_LIMIT_KB}KB) ==="
73
+ kill -9 "$OPENCODE_PID" 2>/dev/null
74
+ sleep 3
75
+ echo '=== [RAM] Restarting OpenCode... ==='
76
+ start_opencode
77
+ fi
78
+ else
79
+ # OpenCode kisi aur wajah se mar gaya β€” restart
80
+ echo '=== [RAM] OpenCode not running! Restarting... ==='
81
+ start_opencode
82
+ fi
83
+ done
84
+ }
85
+
86
+ start_opencode
87
+ sleep 10
88
+
89
+ # RAM watchdog background mein chalao
90
+ ram_watchdog &
91
+
92
+ # ============================================
93
+ # STEP 3: INOTIFY TRIGGER + 30s DEBOUNCE SYNC
94
+ # ============================================
95
+ echo '=== [STEP 3] STARTING SMART SYNC (inotify + 30s debounce) ==='
96
+
97
+ do_sync() {
98
+ echo "=== [STEP 3] Syncing to bucket... ==="
99
+ hf sync "$SOURCE" "$BUCKET" --delete \
100
+ --exclude "*.mdb" \
101
+ --exclude "*.log" \
102
+ --exclude ".cache" \
103
+ --exclude ".npm" \
104
+ --exclude ".check_for_update_done" \
105
+ --exclude "rg"
106
+ echo "=== [STEP 3] Sync done! ==="
107
+ }
108
+
109
+ LAST_SYNC=0
110
+
111
+ while true; do
112
+ # Check if OpenCode is still running
113
+ if ! pgrep -f 'opencode' > /dev/null; then
114
+ echo 'CRITICAL: OpenCode process died! Exiting container...'
115
+ exit 1
116
+ fi
117
+
118
+ # Wait for ANY change β€” modify, create, delete, move, rename, attrib
119
+ inotifywait -r -e modify,create,delete,move,attrib \
120
+ --exclude '.*\.mdb$' \
121
+ --exclude '.*\.log$' \
122
+ --exclude '.*\.check_for_update_done$' \
123
+ --exclude '.*/rg$' \
124
+ -q "$SOURCE"
125
+
126
+ # Debounce: agar last sync 30s pehle hua ho tabhi sync karo
127
+ NOW=$(date +%s)
128
+ DIFF=$((NOW - LAST_SYNC))
129
+
130
+ if [ "$DIFF" -ge 30 ]; then
131
+ do_sync
132
+ LAST_SYNC=$(date +%s)
133
+ else
134
+ echo "=== [STEP 3] Change detected but debounce active (${DIFF}s < 30s), waiting... ==="
135
+ fi
136
+ done
install.sh ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ APP=opencode
4
+
5
+ MUTED='\033[0;2m'
6
+ RED='\033[0;31m'
7
+ ORANGE='\033[38;5;214m'
8
+ NC='\033[0m' # No Color
9
+
10
+ usage() {
11
+ cat <<EOF
12
+ OpenCode Installer
13
+
14
+ Usage: install.sh [options]
15
+
16
+ Options:
17
+ -h, --help Display this help message
18
+ -v, --version <version> Install a specific version (e.g., 1.0.180)
19
+ -b, --binary <path> Install from a local binary instead of downloading
20
+ --no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
21
+
22
+ Examples:
23
+ curl -fsSL https://opencode.ai/install | bash
24
+ curl -fsSL https://opencode.ai/install | bash -s -- --version 1.0.180
25
+ ./install --binary /path/to/opencode
26
+ EOF
27
+ }
28
+
29
+ requested_version=${VERSION:-}
30
+ no_modify_path=false
31
+ binary_path=""
32
+
33
+ while [[ $# -gt 0 ]]; do
34
+ case "$1" in
35
+ -h|--help)
36
+ usage
37
+ exit 0
38
+ ;;
39
+ -v|--version)
40
+ if [[ -n "${2:-}" ]]; then
41
+ requested_version="$2"
42
+ shift 2
43
+ else
44
+ echo -e "${RED}Error: --version requires a version argument${NC}"
45
+ exit 1
46
+ fi
47
+ ;;
48
+ -b|--binary)
49
+ if [[ -n "${2:-}" ]]; then
50
+ binary_path="$2"
51
+ shift 2
52
+ else
53
+ echo -e "${RED}Error: --binary requires a path argument${NC}"
54
+ exit 1
55
+ fi
56
+ ;;
57
+ --no-modify-path)
58
+ no_modify_path=true
59
+ shift
60
+ ;;
61
+ *)
62
+ echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
63
+ shift
64
+ ;;
65
+ esac
66
+ done
67
+
68
+ INSTALL_DIR=$HOME/.opencode/bin
69
+ mkdir -p "$INSTALL_DIR"
70
+
71
+ # If --binary is provided, skip all download/detection logic
72
+ if [ -n "$binary_path" ]; then
73
+ if [ ! -f "$binary_path" ]; then
74
+ echo -e "${RED}Error: Binary not found at ${binary_path}${NC}"
75
+ exit 1
76
+ fi
77
+ specific_version="local"
78
+ else
79
+ raw_os=$(uname -s)
80
+ os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
81
+ case "$raw_os" in
82
+ Darwin*) os="darwin" ;;
83
+ Linux*) os="linux" ;;
84
+ MINGW*|MSYS*|CYGWIN*) os="windows" ;;
85
+ esac
86
+
87
+ arch=$(uname -m)
88
+ if [[ "$arch" == "aarch64" ]]; then
89
+ arch="arm64"
90
+ fi
91
+ if [[ "$arch" == "x86_64" ]]; then
92
+ arch="x64"
93
+ fi
94
+
95
+ if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
96
+ rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
97
+ if [ "$rosetta_flag" = "1" ]; then
98
+ arch="arm64"
99
+ fi
100
+ fi
101
+
102
+ combo="$os-$arch"
103
+ case "$combo" in
104
+ linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
105
+ ;;
106
+ *)
107
+ echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
108
+ exit 1
109
+ ;;
110
+ esac
111
+
112
+ archive_ext=".zip"
113
+ if [ "$os" = "linux" ]; then
114
+ archive_ext=".tar.gz"
115
+ fi
116
+
117
+ is_musl=false
118
+ if [ "$os" = "linux" ]; then
119
+ if [ -f /etc/alpine-release ]; then
120
+ is_musl=true
121
+ fi
122
+
123
+ if command -v ldd >/dev/null 2>&1; then
124
+ if ldd --version 2>&1 | grep -qi musl; then
125
+ is_musl=true
126
+ fi
127
+ fi
128
+ fi
129
+
130
+ needs_baseline=false
131
+ if [ "$arch" = "x64" ]; then
132
+ if [ "$os" = "linux" ]; then
133
+ if ! grep -qwi avx2 /proc/cpuinfo 2>/dev/null; then
134
+ needs_baseline=true
135
+ fi
136
+ fi
137
+
138
+ if [ "$os" = "darwin" ]; then
139
+ avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
140
+ if [ "$avx2" != "1" ]; then
141
+ needs_baseline=true
142
+ fi
143
+ fi
144
+
145
+ if [ "$os" = "windows" ]; then
146
+ ps="(Add-Type -MemberDefinition \"[DllImport(\"\"kernel32.dll\"\")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);\" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)"
147
+ out=""
148
+ if command -v powershell.exe >/dev/null 2>&1; then
149
+ out=$(powershell.exe -NoProfile -NonInteractive -Command "$ps" 2>/dev/null || true)
150
+ elif command -v pwsh >/dev/null 2>&1; then
151
+ out=$(pwsh -NoProfile -NonInteractive -Command "$ps" 2>/dev/null || true)
152
+ fi
153
+ out=$(echo "$out" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
154
+ if [ "$out" != "true" ] && [ "$out" != "1" ]; then
155
+ needs_baseline=true
156
+ fi
157
+ fi
158
+ fi
159
+
160
+ target="$os-$arch"
161
+ if [ "$needs_baseline" = "true" ]; then
162
+ target="$target-baseline"
163
+ fi
164
+ if [ "$is_musl" = "true" ]; then
165
+ target="$target-musl"
166
+ fi
167
+
168
+ filename="$APP-$target$archive_ext"
169
+
170
+
171
+ if [ "$os" = "linux" ]; then
172
+ if ! command -v tar >/dev/null 2>&1; then
173
+ echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
174
+ exit 1
175
+ fi
176
+ else
177
+ if ! command -v unzip >/dev/null 2>&1; then
178
+ echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
179
+ exit 1
180
+ fi
181
+ fi
182
+
183
+ if [ -z "$requested_version" ]; then
184
+ # Fetch latest release version from badman99dev/Opencode
185
+ specific_version=$(curl -s https://api.github.com/repos/badman99dev/Opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
186
+
187
+ if [[ $? -ne 0 || -z "$specific_version" ]]; then
188
+ echo -e "${RED}Failed to fetch latest version from badman99dev/Opencode${NC}"
189
+ exit 1
190
+ fi
191
+
192
+ url="https://github.com/badman99dev/Opencode/releases/download/v${specific_version}/opencode-linux-x64.tar.gz"
193
+ else
194
+ # Strip leading 'v' if present
195
+ requested_version="${requested_version#v}"
196
+ url="https://github.com/badman99dev/Opencode/releases/download/v${requested_version}/opencode-linux-x64.tar.gz"
197
+ specific_version=$requested_version
198
+
199
+ # Verify the release exists before downloading
200
+ http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/badman99dev/Opencode/releases/tag/v${requested_version}")
201
+ if [ "$http_status" = "404" ]; then
202
+ echo -e "${RED}Error: Release v${requested_version} not found${NC}"
203
+ echo -e "${MUTED}Available releases: https://github.com/badman99dev/Opencode/releases${NC}"
204
+ exit 1
205
+ fi
206
+ fi
207
+ fi
208
+
209
+ print_message() {
210
+ local level=$1
211
+ local message=$2
212
+ local color=""
213
+
214
+ case $level in
215
+ info) color="${NC}" ;;
216
+ warning) color="${NC}" ;;
217
+ error) color="${RED}" ;;
218
+ esac
219
+
220
+ echo -e "${color}${message}${NC}"
221
+ }
222
+
223
+ check_version() {
224
+ if command -v opencode >/dev/null 2>&1; then
225
+ opencode_path=$(which opencode)
226
+
227
+ ## Check the installed version
228
+ installed_version=$(opencode --version 2>/dev/null || echo "")
229
+
230
+ if [[ "$installed_version" != "$specific_version" ]]; then
231
+ print_message info "${MUTED}Installed version: ${NC}$installed_version."
232
+ else
233
+ print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
234
+ exit 0
235
+ fi
236
+ fi
237
+ }
238
+
239
+ unbuffered_sed() {
240
+ if echo | sed -u -e "" >/dev/null 2>&1; then
241
+ sed -nu "$@"
242
+ elif echo | sed -l -e "" >/dev/null 2>&1; then
243
+ sed -nl "$@"
244
+ else
245
+ local pad="$(printf "\n%512s" "")"
246
+ sed -ne "s/$/\\${pad}/" "$@"
247
+ fi
248
+ }
249
+
250
+ print_progress() {
251
+ local bytes="$1"
252
+ local length="$2"
253
+ [ "$length" -gt 0 ] || return 0
254
+
255
+ local width=50
256
+ local percent=$(( bytes * 100 / length ))
257
+ [ "$percent" -gt 100 ] && percent=100
258
+ local on=$(( percent * width / 100 ))
259
+ local off=$(( width - on ))
260
+
261
+ local filled=$(printf "%*s" "$on" "")
262
+ filled=${filled// /β– }
263
+ local empty=$(printf "%*s" "$off" "")
264
+ empty=${empty// /ο½₯}
265
+
266
+ printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
267
+ }
268
+
269
+ download_with_progress() {
270
+ local url="$1"
271
+ local output="$2"
272
+
273
+ if [ -t 2 ]; then
274
+ exec 4>&2
275
+ else
276
+ exec 4>/dev/null
277
+ fi
278
+
279
+ local tmp_dir=${TMPDIR:-/tmp}
280
+ local basename="${tmp_dir}/opencode_install_$$"
281
+ local tracefile="${basename}.trace"
282
+
283
+ rm -f "$tracefile"
284
+ mkfifo "$tracefile"
285
+
286
+ # Hide cursor
287
+ printf "\033[?25l" >&4
288
+
289
+ trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
290
+
291
+ (
292
+ curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
293
+ ) &
294
+ local curl_pid=$!
295
+
296
+ unbuffered_sed \
297
+ -e 'y/ACDEGHLNORTV/acdeghlnortv/' \
298
+ -e '/^0000: content-length:/p' \
299
+ -e '/^<= recv data/p' \
300
+ "$tracefile" | \
301
+ {
302
+ local length=0
303
+ local bytes=0
304
+
305
+ while IFS=" " read -r -a line; do
306
+ [ "${#line[@]}" -lt 2 ] && continue
307
+ local tag="${line[0]} ${line[1]}"
308
+
309
+ if [ "$tag" = "0000: content-length:" ]; then
310
+ length="${line[2]}"
311
+ length=$(echo "$length" | tr -d '\r')
312
+ bytes=0
313
+ elif [ "$tag" = "<= recv" ]; then
314
+ local size="${line[3]}"
315
+ bytes=$(( bytes + size ))
316
+ if [ "$length" -gt 0 ]; then
317
+ print_progress "$bytes" "$length"
318
+ fi
319
+ fi
320
+ done
321
+ }
322
+
323
+ wait $curl_pid
324
+ local ret=$?
325
+ echo "" >&4
326
+ return $ret
327
+ }
328
+
329
+ download_and_install() {
330
+ print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
331
+ local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
332
+ mkdir -p "$tmp_dir"
333
+
334
+ if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
335
+ # Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
336
+ curl -# -L -o "$tmp_dir/$filename" "$url"
337
+ fi
338
+
339
+ if [ "$os" = "linux" ]; then
340
+ tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
341
+ # Find and move the opencode binary
342
+ OPENCODE_BIN=$(find "$tmp_dir" -name opencode -type f -printf '%h' 2>/dev/null | head -1)
343
+ if [ -n "$OPENCODE_BIN" ]; then
344
+ mv "$OPENCODE_BIN/opencode" "$INSTALL_DIR"
345
+ else
346
+ echo -e "${RED}Error: opencode binary not found${NC}"
347
+ ls -la "$tmp_dir"
348
+ exit 1
349
+ fi
350
+ else
351
+ unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
352
+ fi
353
+
354
+ chmod 755 "${INSTALL_DIR}/opencode"
355
+ rm -rf "$tmp_dir"
356
+ }
357
+
358
+ install_from_binary() {
359
+ print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}from: ${NC}$binary_path"
360
+ cp "$binary_path" "${INSTALL_DIR}/opencode"
361
+ chmod 755 "${INSTALL_DIR}/opencode"
362
+ }
363
+
364
+ if [ -n "$binary_path" ]; then
365
+ install_from_binary
366
+ else
367
+ check_version
368
+ download_and_install
369
+ fi
370
+
371
+
372
+ add_to_path() {
373
+ local config_file=$1
374
+ local command=$2
375
+
376
+ if grep -Fxq "$command" "$config_file"; then
377
+ print_message info "Command already exists in $config_file, skipping write."
378
+ elif [[ -w $config_file ]]; then
379
+ echo -e "\n# opencode" >> "$config_file"
380
+ echo "$command" >> "$config_file"
381
+ print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
382
+ else
383
+ print_message warning "Manually add the directory to $config_file (or similar):"
384
+ print_message info " $command"
385
+ fi
386
+ }
387
+
388
+ XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
389
+
390
+ current_shell=$(basename "$SHELL")
391
+ case $current_shell in
392
+ fish)
393
+ config_files="$HOME/.config/fish/config.fish"
394
+ ;;
395
+ zsh)
396
+ config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
397
+ ;;
398
+ bash)
399
+ config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
400
+ ;;
401
+ ash)
402
+ config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
403
+ ;;
404
+ sh)
405
+ config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
406
+ ;;
407
+ *)
408
+ # Default case if none of the above matches
409
+ config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
410
+ ;;
411
+ esac
412
+
413
+ if [[ "$no_modify_path" != "true" ]]; then
414
+ config_file=""
415
+ for file in $config_files; do
416
+ if [[ -f $file ]]; then
417
+ config_file=$file
418
+ break
419
+ fi
420
+ done
421
+
422
+ if [[ -z $config_file ]]; then
423
+ print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"
424
+ print_message info " export PATH=$INSTALL_DIR:\$PATH"
425
+ elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
426
+ case $current_shell in
427
+ fish)
428
+ add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
429
+ ;;
430
+ zsh)
431
+ add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
432
+ ;;
433
+ bash)
434
+ add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
435
+ ;;
436
+ ash)
437
+ add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
438
+ ;;
439
+ sh)
440
+ add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
441
+ ;;
442
+ *)
443
+ export PATH=$INSTALL_DIR:$PATH
444
+ print_message warning "Manually add the directory to $config_file (or similar):"
445
+ print_message info " export PATH=$INSTALL_DIR:\$PATH"
446
+ ;;
447
+ esac
448
+ fi
449
+ fi
450
+
451
+ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
452
+ echo "$INSTALL_DIR" >> $GITHUB_PATH
453
+ print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
454
+ fi
455
+
456
+ echo -e ""
457
+ echo -e "${MUTED}Β  ${NC} β–„ "
458
+ echo -e "${MUTED}β–ˆβ–€β–€β–ˆ β–ˆβ–€β–€β–ˆ β–ˆβ–€β–€β–ˆ β–ˆβ–€β–€β–„ ${NC}β–ˆβ–€β–€β–€ β–ˆβ–€β–€β–ˆ β–ˆβ–€β–€β–ˆ β–ˆβ–€β–€β–ˆ"
459
+ echo -e "${MUTED}β–ˆβ–‘β–‘β–ˆ β–ˆβ–‘β–‘β–ˆ β–ˆβ–€β–€β–€ β–ˆβ–‘β–‘β–ˆ ${NC}β–ˆβ–‘β–‘β–‘ β–ˆβ–‘β–‘β–ˆ β–ˆβ–‘β–‘β–ˆ β–ˆβ–€β–€β–€"
460
+ echo -e "${MUTED}β–€β–€β–€β–€ β–ˆβ–€β–€β–€ β–€β–€β–€β–€ β–€ β–€ ${NC}β–€β–€β–€β–€ β–€β–€β–€β–€ β–€β–€β–€β–€ β–€β–€β–€β–€"
461
+ echo -e ""
462
+ echo -e ""
463
+ echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
464
+ echo -e ""
465
+ echo -e "cd <project> ${MUTED}# Open directory${NC}"
466
+ echo -e "opencode ${MUTED}# Run command${NC}"
467
+ echo -e ""
468
+ echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
469
+ echo -e ""
470
+ echo -e ""