Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
451 lines (382 loc) · 15.9 KB
/
Copy pathinstall.sh
File metadata and controls
451 lines (382 loc) · 15.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
#!/data/data/com.termux/files/usr/bin/bash
#
# Installer for pure Android/aarch64 OpenCode builds (opencode-android).
#
# bash <(curl -fsSL https://raw.githubusercontent.com/astreele/opencode-android/master/install.sh)
#
# Default: registers the signed APT repository
# (https://opencode-android-apt.psmsword148.workers.dev) and installs/upgrades the
# opencode2 package with the system package manager. Later updates then
# arrive through `pkg upgrade`, like any other program.
#
# Options (append after the command):
# --cmd opencode install ONLY as `opencode` (run from just that command;
# no opencode2 name, no symlink; implies --zip because
# the deb always ships opencode2 + the opencode link)
# --cmd opencode2 default: install opencode2 + link opencode -> opencode2
# --no-link never touch $PREFIX/bin/opencode (implies --zip)
# --force replace an existing opencode this installer doesn't manage
# --apt force the package-manager path (default)
# --zip legacy direct-download path (zip asset + SHA256SUMS check)
# --no-verify (zip only) skip SHA256SUMS verification (emergency only)
#
set -e
REPO="astreele/opencode-android"
APT_ROOT="https://opencode-android-apt.psmsword148.workers.dev"
# The key URL is overridable for tests; the raw.githubusercontent copy is the
# fallback for devices reaching GitHub before Pages serves the branch.
APT_KEY_URL="${APT_KEY_URL:-$APT_ROOT/opencode-android.gpg}"
APT_KEY_FALLBACK="https://raw.githubusercontent.com/${REPO}/master/apt/opencode-android.gpg"
PREFIX="${PREFIX:-/data/data/com.termux/files/usr}"
CMD="${OPENCODE_CMD:-opencode2}"
LINK=1
FORCE="${OPENCODE_FORCE:-0}"
VERIFY=1
METHOD="auto"
usage() {
cat <<'EOF'
opencode-android installer
Usage: install.sh [options]
Options:
--cmd NAME Which command to install: 'opencode2' (default) or 'opencode'.
'--cmd opencode' installs the program so it runs from just
the `opencode` command — no opencode2 name, no symlink
(uses the --zip path; the deb always ships both names).
--link Create/refresh the `opencode` -> `opencode2` symlink
(default for --cmd opencode2).
--no-link Install opencode2 but never touch $PREFIX/bin/opencode
(use when a v1 `opencode` should keep working; uses --zip).
--force Replace an existing $PREFIX/bin/opencode that this installer
does not manage (e.g. a v1 install). Without this flag such a
file is left untouched and v2 stays available as `opencode2`.
--apt Install through the system package manager (default):
registers the signed APT repository and runs
`pkg install opencode2`. Later updates arrive via `pkg upgrade`.
--zip Legacy path instead: download the release zip, verify it
against SHA256SUMS, and copy the files by hand.
--no-verify (zip only) Skip SHA256SUMS verification. The installer
verifies the download digest by default; only use this in an
emergency.
-h, --help Show this help.
Environment: OPENCODE_CMD (same as --cmd), OPENCODE_FORCE=1 (same as --force).
Examples:
install.sh # opencode2 via pkg (default) + opencode symlink
install.sh --zip # same layout, direct download instead of pkg
install.sh --cmd opencode # just the `opencode` command (zip path)
install.sh --no-link # opencode2 only, leave v1's opencode alone
EOF
}
die() {
echo "FAILED: $*" >&2
exit 1
}
# Is $1 a binary/link managed by this project (and not a foreign/v1 install)?
is_ours() {
if [ -L "$1" ]; then
case "$(readlink "$1")" in
*opencode2*) return 0 ;;
*) return 1 ;;
esac
fi
[ -f "$1" ] || return 1
grep -q "wrapper for OpenCode 2 CLI" "$1" 2>/dev/null
}
# Who owns a path according to the package manager (best effort, for hints)?
owner_of() {
if command -v dpkg >/dev/null 2>&1; then
dpkg -S "$1" 2>/dev/null | head -n 1 || true
elif command -v pacman >/dev/null 2>&1; then
pacman -Qo "$1" 2>/dev/null || true
fi
}
# ── options ────────────────────────────────────────────────────────────
while [ $# -gt 0 ]; do
case "$1" in
--cmd)
[ $# -ge 2 ] || die "--cmd needs a value (opencode|opencode2)"
CMD="$2"; shift 2 ;;
--cmd=*) CMD="${1#--cmd=}"; shift ;;
--link) LINK=1; shift ;;
--no-link) LINK=0; shift ;;
--force) FORCE=1; shift ;;
--apt) METHOD="apt"; shift ;;
--zip) METHOD="zip"; shift ;;
--no-verify) VERIFY=0; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown option: $1" >&2; usage >&2; exit 1 ;;
esac
done
case "$CMD" in
opencode|opencode2) ;;
*) die "--cmd must be 'opencode' or 'opencode2' (got '$CMD')" ;;
esac
# The deb always ships opencode2 + the opencode link, so single-name and
# no-link installs can only come from the zip path.
if [ "$METHOD" = "apt" ]; then
if [ "$CMD" = "opencode" ]; then
die "--cmd opencode needs --zip (the deb always ships opencode2 + the opencode link)"
fi
if [ "$LINK" = 0 ]; then
die "--no-link needs --zip (the deb always ships the opencode link)"
fi
elif [ "$METHOD" = "auto" ]; then
if [ "$CMD" = "opencode" ] || [ "$LINK" = 0 ]; then
echo "(note: --cmd $CMD / --no-link uses the direct-download path)"
METHOD="zip"
else
METHOD="apt"
fi
fi
stop_ours() {
for c in opencode opencode2; do
if [ -x "$PREFIX/bin/$c" ] && is_ours "$PREFIX/bin/$c"; then
"$PREFIX/bin/$c" service stop >/dev/null 2>&1 || true
fi
done
if command -v pkill >/dev/null 2>&1; then
pkill -f "libexec/opencode" >/dev/null 2>&1 || true
fi
}
# ── package-manager path ─────────────────────────────────────────────
install_apt() {
command -v pkg >/dev/null 2>&1 ||
die "pkg not found — the package-manager path needs Termux (use --zip elsewhere)"
command -v curl >/dev/null 2>&1 || pkg install -y curl
echo "[1] Checking for a foreign 'opencode'..."
# dpkg-owned v1 is handled by the deb itself (Conflicts/Replaces); only a
# hand-installed file needs the explicit guard. Runs first so a refusal
# happens before anything is registered or downloaded.
L="$PREFIX/bin/opencode"
if { [ -e "$L" ] || [ -L "$L" ]; } && ! is_ours "$L" && ! owner_of "$L" | grep -q .; then
if [ "$FORCE" = 1 ]; then
echo "WARNING: replacing existing $L (--force)"
else
die "$L already exists and is not managed by this installer.
Options:
• install without touching it: install.sh --zip --no-link
(v2 then runs as \`opencode2\`)
• replace it with this build: install.sh --force
Nothing was installed."
fi
fi
echo "[2] Registering the package repository..."
SOURCES_DIR="$PREFIX/etc/apt/sources.list.d"
KEYRING_DIR="$PREFIX/etc/apt/trusted.gpg.d"
mkdir -p "$SOURCES_DIR" "$KEYRING_DIR"
printf 'deb %s stable main\n' "$APT_ROOT" > "$SOURCES_DIR/opencode-android.list"
if curl -fL --retry 3 -o "$KEYRING_DIR/opencode-android.gpg.tmp" "$APT_KEY_URL" 2>/dev/null ||
curl -fL --retry 3 -o "$KEYRING_DIR/opencode-android.gpg.tmp" "$APT_KEY_FALLBACK"; then
mv -f "$KEYRING_DIR/opencode-android.gpg.tmp" \
"$KEYRING_DIR/opencode-android.gpg"
echo "repository key installed"
else
rm -f "$KEYRING_DIR/opencode-android.gpg.tmp"
die "could not fetch the repository key from
$APT_KEY_URL
$APT_KEY_FALLBACK
Nothing was installed."
fi
echo "[3] Updating package lists..."
pkg update -y
echo "[4] Stopping existing OpenCode..."
stop_ours
sleep 1
echo "[5] Installing opencode2 (re-run any time to update)..."
pkg install -y opencode2
echo "[6] Verifying..."
"$PREFIX/bin/opencode2" --version
echo
echo "================================"
echo " OpenCode installed successfully"
echo "================================"
echo
echo "Installed (package manager):"
echo " $PREFIX/bin/opencode2"
echo " $PREFIX/bin/opencode -> opencode2"
echo " repository: $APT_ROOT"
echo
echo "Update later like any other program:"
echo " pkg upgrade"
echo
echo "Uninstall:"
echo " pkg uninstall opencode2"
echo
echo "Run:"
echo " opencode2 auth # connect a provider (or: opencode auth)"
echo " opencode2 # TUI (or: opencode)"
}
# ── direct-download path (legacy) ────────────────────────────────────
install_zip() {
echo "[1] Resolving latest release..."
API="https://api.github.com/repos/${REPO}/releases/latest"
TAG=$(curl -fsSL "$API" |
awk -F'"' '/"tag_name"/ {print $4; exit}')
if [ -z "$TAG" ]; then
die "could not resolve latest release"
fi
echo "TAG=$TAG"
echo "[2] Installing dependencies..."
pkg update -y
pkg install -y curl unzip grep ripgrep libc++ coreutils
echo "[3] Creating temporary directory..."
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
cd "$TMP"
echo "[4] Getting release information..."
RELEASE_API="https://api.github.com/repos/${REPO}/releases/tags/${TAG}"
RELEASE=$(curl -fsSL "$RELEASE_API")
echo "[5] Finding Android ARM64 binary..."
ZIP=$(printf '%s\n' "$RELEASE" |
grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]*android[^"]*aarch64[^"]*\.zip"' |
head -n 1 |
cut -d '"' -f 4)
if [ -z "$ZIP" ]; then
echo
die "could not find Android ARM64 ZIP. Available release assets:
$(printf '%s\n' "$RELEASE" | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' | cut -d '"' -f 4)"
fi
echo "Found: $ZIP"
DOWNLOAD="https://github.com/${REPO}/releases/download/${TAG}/${ZIP}"
echo "[6] Downloading..."
curl -fL --retry 3 -o "$ZIP" "$DOWNLOAD"
echo "[7] Verifying download (SHA256SUMS)..."
if [ "$VERIFY" = 1 ]; then
command -v sha256sum >/dev/null 2>&1 ||
die "sha256sum missing (coreutils); refusing to install unverified — fix coreutils or pass --no-verify"
curl -fL --retry 3 -o SHA256SUMS \
"https://github.com/${REPO}/releases/download/${TAG}/SHA256SUMS" ||
die "release $TAG has no SHA256SUMS; pass --no-verify only if you accept an unverified download"
# 2>/dev/null || true: an unreadable SHA256SUMS must fall through to the
# explicit "no entry" error below, not die silently under set -e.
EXPECTED=$(awk -v f="$ZIP" '$2 == f {print $1; exit}' SHA256SUMS 2>/dev/null || true)
[ -n "$EXPECTED" ] || die "SHA256SUMS contains no entry for $ZIP"
ACTUAL=$(sha256sum "$ZIP" | awk '{print $1}')
if [ "$EXPECTED" != "$ACTUAL" ]; then
die "checksum mismatch for $ZIP
expected: $EXPECTED
actual: $ACTUAL
The download is corrupt or was tampered with; nothing was installed."
fi
echo "checksum OK: $ZIP"
else
echo "skipped (--no-verify)"
fi
echo "[8] Extracting..."
unzip -o "$ZIP"
if [ ! -f opencode2 ]; then
die "opencode2 not found in archive"
fi
if [ ! -f opencode2.bin ]; then
die "opencode2.bin not found in archive"
fi
# ── guard: never silently clobber a command we don't own (e.g. v1) ─────
TARGET="$PREFIX/bin/$CMD"
if { [ -e "$TARGET" ] || [ -L "$TARGET" ]; } && ! is_ours "$TARGET"; then
OWNER=$(owner_of "$TARGET")
if [ "$FORCE" = 1 ]; then
echo "WARNING: replacing existing $TARGET (--force)"
if [ -n "$OWNER" ]; then echo " it was provided by: $OWNER"; fi
else
if [ "$CMD" = "opencode" ]; then
die "$TARGET already exists and is not managed by this installer.
${OWNER:+ It is provided by: $OWNER}
Options:
• replace it with this build: install.sh --cmd opencode --force
• keep it and install v2 under
a different command name: install.sh --cmd opencode2 --no-link
(v2 then runs as \`opencode2\`)
Nothing was installed."
else
die "$TARGET already exists and is not managed by this installer.
${OWNER:+ It is provided by: $OWNER}
Options:
• install without touching it: install.sh --no-link
• replace it with this build: install.sh --force
• install v2 under a single command: install.sh --cmd opencode
Nothing was installed."
fi
fi
fi
echo "[9] Stopping existing OpenCode..."
stop_ours
sleep 1
echo "[10] Installing (command: $CMD)..."
mkdir -p "$PREFIX/bin"
mkdir -p "$PREFIX/libexec/$CMD"
# The shipped wrapper is named opencode2; rewrite its internal paths when the
# user asked for a different command name (--cmd opencode).
sed -e "s/opencode2\.bin/${CMD}.bin/g" \
-e "s|libexec/opencode2|libexec/${CMD}|g" \
-e "s/opencode2: error/${CMD}: error/g" \
-e "s/# opencode2 - wrapper/# ${CMD} - wrapper/g" \
opencode2 > "$PREFIX/bin/$CMD"
chmod 755 "$PREFIX/bin/$CMD"
cp -f opencode2.bin "$PREFIX/libexec/$CMD/$CMD.bin"
chmod 755 "$PREFIX/libexec/$CMD/$CMD.bin"
# Ship the license alongside the binary (present in release zips).
if [ -f LICENSE ]; then
mkdir -p "$PREFIX/share/doc/$CMD"
cp -f LICENSE "$PREFIX/share/doc/$CMD/"
fi
echo "[11] Linking 'opencode'..."
LINK_STATE="not requested"
if [ "$CMD" = "opencode" ]; then
LINK_STATE="runs directly as 'opencode' (no symlink)"
elif [ "$LINK" = 0 ]; then
LINK_STATE="skipped (--no-link)"
else
L="$PREFIX/bin/opencode"
if { [ -e "$L" ] || [ -L "$L" ]; } && ! is_ours "$L"; then
if [ "$FORCE" = 1 ]; then
OWNER=$(owner_of "$L")
echo "WARNING: replacing existing $L (--force)"
if [ -n "$OWNER" ]; then echo " it was provided by: $OWNER"; fi
ln -sf "$TARGET" "$L"
LINK_STATE="replaced (--force)"
else
LINK_STATE="left untouched (existing '$L' is not ours)"
echo
echo " NOTE: $L exists and was left alone (likely a v1 install)."
echo " v2 is installed and runs as: opencode2"
echo " To make \`opencode\` run v2 instead:"
echo " • remove the old command first (e.g. pkg uninstall <package>),"
echo " or re-run with --force to replace it,"
echo " • or re-run with --no-link to keep both as-is."
fi
else
ln -sf "$TARGET" "$L"
LINK_STATE="created ($L -> opencode2)"
fi
fi
echo "[12] Verifying..."
"$PREFIX/bin/$CMD" --version
echo
echo "================================"
echo " OpenCode installed successfully"
echo "================================"
echo
echo "Installed:"
echo " $PREFIX/bin/$CMD"
echo " $PREFIX/libexec/$CMD/$CMD.bin"
if [ "$CMD" = "opencode2" ]; then
echo " symlink opencode: $LINK_STATE"
else
echo " opencode link: $LINK_STATE"
fi
echo
echo "Uninstall:"
echo " rm -f $PREFIX/bin/$CMD"
echo " rm -rf $PREFIX/libexec/$CMD"
echo " rm -f $PREFIX/share/doc/$CMD/LICENSE"
if [ "$CMD" = "opencode2" ]; then
echo " rm -f $PREFIX/bin/opencode # only if it points to opencode2"
fi
echo
echo "Run:"
echo " $CMD auth # connect a provider"
echo " $CMD # TUI"
}
case "$METHOD" in
apt) install_apt ;;
zip) install_zip ;;
esac