-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·497 lines (427 loc) · 13.6 KB
/
install.sh
File metadata and controls
executable file
·497 lines (427 loc) · 13.6 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
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
#!/bin/sh
set -eu
# GoZen installer - download and install from GitHub Releases
# Usage:
# curl -fsSL https://raw.githubusercontent.com/dopejs/gozen/main/install.sh | sh
# curl -fsSL https://raw.githubusercontent.com/dopejs/gozen/main/install.sh | sh -s -- --version 3.0.0-alpha.21
# curl -fsSL https://raw.githubusercontent.com/dopejs/gozen/main/install.sh | sh -s -- --uninstall
REPO="dopejs/gozen"
BIN_TARGET="/usr/local/bin/zen"
ZEN_DIR="$HOME/.zen"
# --- Helpers ---
info() { printf "\033[1;34m==>\033[0m %s\n" "$1"; }
ok() { printf "\033[1;32m==>\033[0m %s\n" "$1"; }
err() { printf "\033[1;31mError:\033[0m %s\n" "$1" >&2; }
need_sudo() {
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo >/dev/null 2>&1; then
echo "sudo"
else
err "Need root privileges. Run with sudo or as root."
exit 1
fi
fi
}
detect_platform() {
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
esac
case "$OS" in
darwin|linux) ;;
*) err "Unsupported OS: $OS"; exit 1 ;;
esac
info "Detected platform: ${OS}/${ARCH}"
}
get_latest_version() {
if command -v curl >/dev/null 2>&1; then
curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then
wget -qO- "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null
else
err "curl or wget is required"
exit 1
fi
}
fetch() {
_url="$1"
_out="$2"
if command -v curl >/dev/null 2>&1; then
curl -fL --progress-bar -o "$_out" "$_url"
elif command -v wget >/dev/null 2>&1; then
wget --show-progress -qO "$_out" "$_url" 2>&1
else
err "curl or wget is required"
exit 1
fi
}
# --- Completion install paths ---
zsh_comp_dir() {
if command -v zsh >/dev/null 2>&1; then
_fpath_list="$(zsh -ic 'for d in $fpath; do echo "$d"; done' 2>/dev/null)"
for _d in $_fpath_list; do
case "$_d" in
/usr/share/*) continue ;;
*/site-functions)
echo "$_d"
return 0
;;
esac
done
fi
echo "/usr/local/share/zsh/site-functions"
}
bash_comp_dir() {
if [ "$(uname -s)" = "Darwin" ]; then
if [ -d "/opt/homebrew/etc/bash_completion.d" ]; then
echo "/opt/homebrew/etc/bash_completion.d"
elif [ -d "/usr/local/etc/bash_completion.d" ]; then
echo "/usr/local/etc/bash_completion.d"
fi
else
if [ -d "/usr/share/bash-completion/completions" ]; then
echo "/usr/share/bash-completion/completions"
elif [ -d "/etc/bash_completion.d" ]; then
echo "/etc/bash_completion.d"
fi
fi
}
fish_comp_dir() {
echo "$HOME/.config/fish/completions"
}
# --- First provider setup ---
setup_first_provider() {
printf "\n"
info "No providers configured yet. Let's set up your first one."
printf " (Press Ctrl+C to skip and configure later with 'zen config')\n\n"
# Use /dev/tty for input since stdin may be a pipe
if [ ! -t 0 ] && [ -e /dev/tty ]; then
_input="/dev/tty"
else
_input="/dev/stdin"
fi
printf " Provider name (e.g. work, personal): "
read -r _name < "$_input"
_name="$(printf '%s' "$_name" | tr -d '[:space:]')"
if [ -z "$_name" ]; then
err "Name cannot be empty. Run 'zen config' to set up later."
return
fi
printf " ANTHROPIC_BASE_URL: "
read -r _base_url < "$_input"
_base_url="$(printf '%s' "$_base_url" | tr -d '[:space:]')"
if [ -z "$_base_url" ]; then
err "Base URL cannot be empty. Run 'zen config' to set up later."
return
fi
printf " ANTHROPIC_AUTH_TOKEN: "
read -r _token < "$_input"
_token="$(printf '%s' "$_token" | tr -d '[:space:]')"
if [ -z "$_token" ]; then
err "Auth token cannot be empty. Run 'zen config' to set up later."
return
fi
printf " ANTHROPIC_MODEL (leave empty for default): "
read -r _model < "$_input"
_model="$(printf '%s' "$_model" | tr -d '[:space:]')"
# Use zen config add provider to create the provider
"$BIN_TARGET" config add provider "$_name" --base-url "$_base_url" --auth-token "$_token" ${_model:+--model "$_model"} 2>/dev/null || {
# Fallback: write directly to config
_env_path="$ZEN_DIR/${_name}.env"
printf "ANTHROPIC_BASE_URL=%s\n" "$_base_url" > "$_env_path"
printf "ANTHROPIC_AUTH_TOKEN=%s\n" "$_token" >> "$_env_path"
if [ -n "$_model" ]; then
printf "ANTHROPIC_MODEL=%s\n" "$_model" >> "$_env_path"
fi
}
printf "\n"
ok "Provider '${_name}' created and set as default fallback."
printf " Run 'zen' to start with this provider\n"
printf " Run 'zen config' to add more providers\n"
}
# --- Legacy cleanup ---
cleanup_legacy() {
SUDO="$(need_sudo || true)"
# Remove old /usr/local/bin/opencc binary
if [ -f "/usr/local/bin/opencc" ]; then
info "Removing legacy /usr/local/bin/opencc binary..."
$SUDO rm -f "/usr/local/bin/opencc"
fi
# Remove old completion files
if command -v zsh >/dev/null 2>&1; then
zdir="$(zsh_comp_dir)"
if [ -n "$zdir" ] && [ -f "$zdir/_opencc" ]; then
$SUDO rm -f "$zdir/_opencc"
fi
fi
if command -v bash >/dev/null 2>&1; then
bdir="$(bash_comp_dir)"
if [ -n "$bdir" ] && [ -f "$bdir/opencc" ]; then
$SUDO rm -f "$bdir/opencc"
fi
fi
fdir="$(fish_comp_dir)"
if [ -f "$fdir/opencc.fish" ]; then
rm -f "$fdir/opencc.fish"
fi
}
# --- Install ---
do_install() {
detect_platform
SUDO="$(need_sudo)"
if [ -n "$INSTALL_VERSION" ]; then
# User specified a version
VERSION="v$(printf '%s' "$INSTALL_VERSION" | sed 's/^v//')"
info "Requested version: ${VERSION}"
else
# Fetch latest stable release
info "Fetching latest release info..."
RELEASE_JSON="$(get_latest_version)"
# Extract version tag (simple grep, no jq dependency)
VERSION="$(printf '%s' "$RELEASE_JSON" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')"
if [ -z "$VERSION" ]; then
err "Failed to determine latest version"
exit 1
fi
info "Latest version: ${VERSION}"
fi
# Determine download format based on version
# v1.4.0+ uses tar.gz, earlier versions use raw binary
VERSION_NUM="$(printf '%s' "$VERSION" | sed 's/^v//')"
MAJOR="$(printf '%s' "$VERSION_NUM" | cut -d. -f1)"
MINOR="$(printf '%s' "$VERSION_NUM" | cut -d. -f2)"
USE_TARBALL=0
if [ "$MAJOR" -gt 1 ] || { [ "$MAJOR" -eq 1 ] && [ "$MINOR" -ge 4 ]; }; then
USE_TARBALL=1
fi
# Download binary or tarball
_tmpdir="$(mktemp -d)"
trap 'rm -rf "$_tmpdir"' EXIT
if [ "$USE_TARBALL" -eq 1 ]; then
# v1.4.0+: Download tar.gz
ASSET_NAME="zen-${OS}-${ARCH}.tar.gz"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET_NAME}"
info "Downloading ${DOWNLOAD_URL}..."
fetch "$DOWNLOAD_URL" "${_tmpdir}/zen.tar.gz"
info "Extracting..."
tar -xzf "${_tmpdir}/zen.tar.gz" -C "${_tmpdir}"
if [ ! -f "${_tmpdir}/zen" ]; then
err "Binary not found in tarball"
exit 1
fi
else
# v1.3.x and earlier: Download raw binary
ASSET_NAME="zen-${OS}-${ARCH}"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET_NAME}"
info "Downloading ${DOWNLOAD_URL}..."
fetch "$DOWNLOAD_URL" "${_tmpdir}/zen"
fi
# Install binary (rm first to avoid ETXTBSY on Linux if daemon is running)
chmod +x "${_tmpdir}/zen"
info "Installing zen to ${BIN_TARGET}"
$SUDO rm -f "$BIN_TARGET"
$SUDO cp "${_tmpdir}/zen" "$BIN_TARGET"
$SUDO chmod +x "$BIN_TARGET"
# On macOS, ad-hoc codesign to clear com.apple.provenance
# so Gatekeeper won't kill the downloaded binary
if [ "$OS" = "darwin" ] && command -v codesign >/dev/null 2>&1; then
$SUDO codesign --force --sign - "$BIN_TARGET" 2>/dev/null || true
fi
ok "Installed ${BIN_TARGET}"
# Clean up legacy opencc installation
cleanup_legacy
# Restart web daemon if it was running before upgrade
# Check all PID files (both old ~/.opencc and new ~/.zen)
_was_running=0
for _pid_dir in "$HOME/.zen" "$HOME/.opencc"; do
for pidfile in "$_pid_dir"/web*.pid; do
[ -f "$pidfile" ] || continue
_web_pid="$(cat "$pidfile" 2>/dev/null)"
if [ -n "$_web_pid" ] && kill -0 "$_web_pid" 2>/dev/null; then
_was_running=1
info "Stopping web daemon (PID ${_web_pid})..."
kill "$_web_pid" 2>/dev/null || true
rm -f "$pidfile"
fi
done
done
if [ "$_was_running" = "1" ]; then
sleep 1
info "Restarting web daemon..."
"$BIN_TARGET" web --daemon >/dev/null 2>&1 || true
ok "Web daemon restarted."
fi
# Create zen dir
if [ ! -d "$ZEN_DIR" ]; then
info "Creating ${ZEN_DIR}"
mkdir -p "$ZEN_DIR"
fi
# Install completions
install_completions
printf "\n"
ok "GoZen ${VERSION} installed!"
# Check if config exists
if [ ! -f "$ZEN_DIR/zen.json" ] && [ ! -f "$HOME/.opencc/opencc.json" ]; then
setup_first_provider
else
printf "\n"
printf " Quick Start:\n"
printf " zen Start with default profile\n"
printf " zen use <provider> Use a specific provider\n"
printf " zen web Open web UI\n"
printf " zen config add provider Add a new provider\n"
printf " zen config add profile Add a new profile\n"
printf " zen daemon enable Auto-start daemon on login\n"
printf " zen daemon disable Remove auto-start\n"
fi
}
install_completions() {
SUDO="$(need_sudo)"
# zsh
if command -v zsh >/dev/null 2>&1; then
zdir="$(zsh_comp_dir)"
if [ -n "$zdir" ]; then
info "Installing zsh completion to $zdir/_zen"
"$BIN_TARGET" completion zsh > "${_tmpdir}/_zen_comp"
$SUDO mkdir -p "$zdir"
$SUDO cp "${_tmpdir}/_zen_comp" "$zdir/_zen"
rm -f "${_tmpdir}/_zen_comp"
rm -f "$HOME"/.zcompdump*
ok "zsh completion installed"
fi
fi
# bash
if command -v bash >/dev/null 2>&1; then
bdir="$(bash_comp_dir)"
if [ -n "$bdir" ]; then
info "Installing bash completion to $bdir/zen"
"$BIN_TARGET" completion bash > "${_tmpdir}/_zen_bash_comp"
$SUDO cp "${_tmpdir}/_zen_bash_comp" "$bdir/zen"
rm -f "${_tmpdir}/_zen_bash_comp"
ok "bash completion installed"
fi
fi
# fish
if command -v fish >/dev/null 2>&1; then
fdir="$(fish_comp_dir)"
info "Installing fish completion to $fdir/zen.fish"
mkdir -p "$fdir"
"$BIN_TARGET" completion fish > "$fdir/zen.fish"
ok "fish completion installed"
fi
}
# --- Uninstall ---
do_uninstall() {
SUDO="$(need_sudo)"
info "Uninstalling zen..."
# Stop web daemon if running (check both ~/.zen and ~/.opencc PID files)
for _pid_dir in "$HOME/.zen" "$HOME/.opencc"; do
for pidfile in "$_pid_dir"/web*.pid; do
[ -f "$pidfile" ] || continue
_web_pid="$(cat "$pidfile" 2>/dev/null)"
if [ -n "$_web_pid" ] && kill -0 "$_web_pid" 2>/dev/null; then
info "Stopping web daemon (PID ${_web_pid})..."
kill "$_web_pid" 2>/dev/null || true
fi
rm -f "$pidfile"
done
done
# Remove system services
# macOS launchd
for _label in "com.dopejs.zen-web" "com.dopejs.opencc-web"; do
_plist="$HOME/Library/LaunchAgents/${_label}.plist"
if [ -f "$_plist" ]; then
launchctl unload "$_plist" 2>/dev/null || true
rm -f "$_plist"
ok "Removed launchd service ${_label}"
fi
done
# Linux systemd
for _svc in "zen-web.service" "opencc-web.service"; do
_unit="$HOME/.config/systemd/user/${_svc}"
if [ -f "$_unit" ]; then
systemctl --user stop "$_svc" 2>/dev/null || true
systemctl --user disable "$_svc" 2>/dev/null || true
rm -f "$_unit"
systemctl --user daemon-reload 2>/dev/null || true
ok "Removed systemd service ${_svc}"
fi
done
# Windows scheduled tasks
if command -v schtasks >/dev/null 2>&1; then
for _task in "zen-web" "opencc-web"; do
schtasks /query /tn "$_task" >/dev/null 2>&1 && {
schtasks /delete /tn "$_task" /f 2>/dev/null || true
ok "Removed scheduled task ${_task}"
}
done
fi
if [ -f "$BIN_TARGET" ]; then
$SUDO rm -f "$BIN_TARGET"
ok "Removed $BIN_TARGET"
fi
# Also remove legacy binary if it exists
if [ -f "/usr/local/bin/opencc" ]; then
$SUDO rm -f "/usr/local/bin/opencc"
ok "Removed /usr/local/bin/opencc (legacy)"
fi
if command -v zsh >/dev/null 2>&1; then
zdir="$(zsh_comp_dir)"
if [ -n "$zdir" ] && [ -f "$zdir/_zen" ]; then
$SUDO rm -f "$zdir/_zen"
ok "Removed $zdir/_zen"
fi
if [ -n "$zdir" ] && [ -f "$zdir/_opencc" ]; then
$SUDO rm -f "$zdir/_opencc"
ok "Removed $zdir/_opencc (legacy)"
fi
fi
if command -v bash >/dev/null 2>&1; then
bdir="$(bash_comp_dir)"
if [ -n "$bdir" ] && [ -f "$bdir/zen" ]; then
$SUDO rm -f "$bdir/zen"
ok "Removed $bdir/zen"
fi
if [ -n "$bdir" ] && [ -f "$bdir/opencc" ]; then
$SUDO rm -f "$bdir/opencc"
ok "Removed $bdir/opencc (legacy)"
fi
fi
fdir="$(fish_comp_dir)"
if [ -f "$fdir/zen.fish" ]; then
rm -f "$fdir/zen.fish"
ok "Removed $fdir/zen.fish"
fi
if [ -f "$fdir/opencc.fish" ]; then
rm -f "$fdir/opencc.fish"
ok "Removed $fdir/opencc.fish (legacy)"
fi
printf "\n"
ok "GoZen has been uninstalled"
info "~/.zen/ was preserved (your configurations)"
}
# --- Main ---
INSTALL_VERSION=""
while [ $# -gt 0 ]; do
case "$1" in
--uninstall)
do_uninstall
exit 0
;;
--version)
if [ $# -lt 2 ]; then
err "Missing version argument. Usage: --version 3.0.0-alpha.21"
exit 1
fi
INSTALL_VERSION="$2"
shift 2
;;
*)
err "Unknown option: $1"
exit 1
;;
esac
done
do_install