-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·253 lines (232 loc) · 11.2 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·253 lines (232 loc) · 11.2 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
#!/usr/bin/env bash
# OpenCharaAgent installer (macOS / Linux).
#
# curl -fsSL https://raw.githubusercontent.com/OpenChara/OpenCharaAgent/main/install.sh | bash
#
# Two channels:
#
# * USER (default) — install the prebuilt WHEEL from the latest GitHub Release
# via `uv tool install`. The wheel bundles the built frontend (front/webui/),
# so there's no node build and no source checkout. Update later with
# `chara update` (uv tool upgrade).
#
# * DEV/edge — `CHARA_CHANNEL=dev` (or `--dev`) keeps the old git-checkout
# layout: a clone in $CHARA_HOME/app + `uv sync`. Developers have node and
# rebuild the served UI with `cd apps/web && npm run build`. Update later with
# `chara update` (git pull + uv sync).
#
# The repo is PUBLIC — no token needed. GITHUB_TOKEN is honoured if set (a bearer
# header, to dodge the 60/hr anonymous API rate limit), but is never required.
set -euo pipefail
REPO_SLUG="${CHARA_REPO_SLUG:-OpenChara/OpenCharaAgent}"
REPO_URL="${CHARA_REPO:-https://github.com/${REPO_SLUG}.git}"
CHARA_HOME="${CHARA_HOME:-$HOME/.chara}"
APP_DIR="$CHARA_HOME/app"
BIN_DIR="$CHARA_HOME/bin"
LINK_DIR="${CHARA_LINK_DIR:-$HOME/.local/bin}"
CHANNEL="${CHARA_CHANNEL:-user}"
# --dev / --channel dev flag (works with `… | bash -s -- --dev`).
for arg in "$@"; do
case "$arg" in
--dev|--channel=dev) CHANNEL="dev" ;;
--user|--channel=user) CHANNEL="user" ;;
esac
done
say() { printf '\033[1;36m[chara]\033[0m %s\n' "$*"; }
fail() { printf '\033[1;31m[chara]\033[0m %s\n' "$*" >&2; exit 1; }
# Browser tools are an OpenCharaAgent environment requirement (owner 2026-06-19): the
# Node `agent-browser` CLI + its own Chromium back the browser_* tools, which now
# run under the default `sandbox` isolation too. Best-effort + non-fatal: a failed
# browser setup never blocks the core install. On Linux+apt we bootstrap Node 20
# (NodeSource) if missing; on macOS we point at `brew install node`.
browser_setup() {
say "setting up browser tools (agent-browser + Chromium) ..."
if ! command -v npm >/dev/null 2>&1; then
if [ "$(uname -s)" = "Linux" ] && command -v apt-get >/dev/null 2>&1; then
say " Node.js not found — installing Node 20 (NodeSource) ..."
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 \
&& apt-get install -y nodejs >/dev/null 2>&1 || true
fi
fi
if command -v npm >/dev/null 2>&1; then
if npm install -g agent-browser >/dev/null 2>&1 \
&& agent-browser install --with-deps >/dev/null 2>&1; then
say " browser tools ready (agent-browser + Chromium)"
else
say " NOTE: browser setup incomplete — finish later with: chara setup browser"
fi
else
say " NOTE: Node.js (node+npm) not found — the browser_* tools need it."
say " Install Node 18+ ($([ "$(uname -s)" = Darwin ] && echo 'brew install node' || echo 'your package manager')), then: chara setup browser"
fi
}
# ffmpeg backs the chara's video/audio work through its terminal (e.g. an MV for
# music it generated, or motion on its homepage). Best-effort + non-fatal: a
# missing or failed ffmpeg never blocks the core install — the agent prompt only
# claims ffmpeg when it's actually on PATH.
ffmpeg_setup() {
if command -v ffmpeg >/dev/null 2>&1; then
say "ffmpeg: already present ($(command -v ffmpeg))"
return
fi
say "installing ffmpeg ..."
local SUDO=""
[ "$(id -u)" != "0" ] && command -v sudo >/dev/null 2>&1 && SUDO="sudo"
if [ "$(uname -s)" = "Darwin" ]; then
if command -v brew >/dev/null 2>&1; then
brew install ffmpeg >/dev/null 2>&1 && say " ffmpeg ready" \
|| say " NOTE: ffmpeg install failed — install manually: brew install ffmpeg"
else
say " NOTE: Homebrew not found — install ffmpeg manually: brew install ffmpeg"
fi
elif command -v apt-get >/dev/null 2>&1; then
{ $SUDO apt-get update -y >/dev/null 2>&1; $SUDO apt-get install -y ffmpeg >/dev/null 2>&1; } \
&& say " ffmpeg ready" || say " NOTE: ffmpeg install failed — install manually: apt-get install ffmpeg"
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y ffmpeg >/dev/null 2>&1 && say " ffmpeg ready" \
|| say " NOTE: ffmpeg install failed — install manually: dnf install ffmpeg"
elif command -v pacman >/dev/null 2>&1; then
$SUDO pacman -S --noconfirm ffmpeg >/dev/null 2>&1 && say " ffmpeg ready" \
|| say " NOTE: ffmpeg install failed — install manually: pacman -S ffmpeg"
else
say " NOTE: no known package manager — install ffmpeg manually for video/audio tools."
fi
}
case "$(uname -s)" in
Darwin|Linux) ;;
*) fail "unsupported platform $(uname -s) (macOS and Linux only for now)" ;;
esac
mkdir -p "$CHARA_HOME" "$BIN_DIR" "$LINK_DIR"
# --- uv: prefer system uv, else install a managed copy into $BIN_DIR --------
UV="$(command -v uv || true)"
if [ -z "$UV" ]; then
if [ ! -x "$BIN_DIR/uv" ]; then
say "installing uv into $BIN_DIR ..."
installer="$(mktemp)"
curl -fsSL https://astral.sh/uv/install.sh -o "$installer" || fail "could not download uv installer"
UV_UNMANAGED_INSTALL="$BIN_DIR" sh "$installer" >/dev/null || fail "uv install failed"
rm -f "$installer"
fi
UV="$BIN_DIR/uv"
fi
say "using uv: $UV"
# ===========================================================================
# DEV / edge channel — git checkout + uv sync (the old default).
# ===========================================================================
if [ "$CHANNEL" = "dev" ]; then
command -v git >/dev/null 2>&1 || fail "git is required for the dev channel (macOS: xcode-select --install; Linux: apt/dnf install git)"
if [ -d "$APP_DIR/.git" ]; then
say "updating existing checkout at $APP_DIR ..."
git -C "$APP_DIR" pull --ff-only origin main || fail "git pull failed (local changes? see $APP_DIR)"
else
say "cloning $REPO_URL -> $APP_DIR ..."
git clone --depth 1 "$REPO_URL" "$APP_DIR"
fi
say "syncing python environment ..."
# server + messaging extras so `chara desktop` (needs websockets) and
# `chara gateway` (qrcode/websockets) work out of the box.
(cd "$APP_DIR" && "$UV" sync -q --extra server --extra messaging) || fail "uv sync failed"
SHIM="$LINK_DIR/chara"
cat > "$SHIM" <<EOF
#!/usr/bin/env bash
exec "$APP_DIR/.venv/bin/chara" "\$@"
EOF
chmod +x "$SHIM"
say "installed dev shim: $SHIM"
say "NOTE: dev channel — rebuild the served UI after frontend edits:"
say " cd $APP_DIR/apps/web && npm ci && npm run build"
case ":$PATH:" in
*":$LINK_DIR:"*) ;;
*) say "NOTE: $LINK_DIR is not on your PATH. Add this to your shell profile:"
say " export PATH=\"$LINK_DIR:\$PATH\"" ;;
esac
browser_setup
ffmpeg_setup
say "done (dev channel). run: chara"
exit 0
fi
# ===========================================================================
# USER channel — install the prebuilt wheel from the latest GitHub Release.
# ===========================================================================
command -v curl >/dev/null 2>&1 || fail "curl is required"
API="https://api.github.com/repos/${REPO_SLUG}/releases/latest"
AUTH_HEADER=()
if [ -n "${GITHUB_TOKEN:-}" ]; then
AUTH_HEADER=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
say "resolving the latest release of ${REPO_SLUG} ..."
release_json="$(curl -fsSL ${AUTH_HEADER[@]+"${AUTH_HEADER[@]}"} -H "Accept: application/vnd.github+json" "$API")" \
|| fail "could not fetch the latest release of ${REPO_SLUG} — GitHub may be unreachable or rate-limiting you; try again shortly. To install from source instead, re-run with: ... | bash -s -- --dev"
# Find the .whl asset's download URL (grep/sed — no jq dependency).
WHEEL_URL="$(printf '%s\n' "$release_json" \
| grep -oE '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]+\.whl"' \
| head -n1 \
| sed -E 's/.*"(https[^"]+\.whl)"/\1/')"
[ -n "$WHEEL_URL" ] || fail "no .whl asset found in the latest release of ${REPO_SLUG}"
say "wheel: $WHEEL_URL"
# Download the wheel to a local file FIRST — public and private path alike — so
# the checksum below verifies the exact bytes we install. (Handing the URL
# straight to `uv tool install` on the public path used to leave the
# verification dead code on the DEFAULT install.) A private-repo asset
# additionally needs the token as a bearer header. The wheel lives in its own
# temp DIR; the trap cleans it up on EVERY exit path (fail included).
WHEEL_BASENAME="${WHEEL_URL##*/}"
TMP_DIR="$(mktemp -d -t chara-XXXXXX)"
trap 'rm -rf "$TMP_DIR"' EXIT
TMP_WHEEL="$TMP_DIR/$WHEEL_BASENAME"
if [ -n "${GITHUB_TOKEN:-}" ]; then
say "downloading wheel (authenticated) ..."
else
say "downloading wheel ..."
fi
curl -fsSL ${AUTH_HEADER[@]+"${AUTH_HEADER[@]}"} -H "Accept: application/octet-stream" -o "$TMP_WHEEL" "$WHEEL_URL" \
|| fail "wheel download failed"
INSTALL_TARGET="$TMP_WHEEL"
# Integrity: if the release publishes a checksum for the wheel, verify the
# downloaded bytes before installing — this product hands an LLM a shell, so a
# tampered wheel is the worst case. If no checksum is published we say so plainly
# rather than implying the download was verified.
SUM_URL="$(printf '%s' "$release_json" \
| grep -oE '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]+(SHA256SUMS|\.sha256)"' \
| head -n1 | sed -E 's/.*"(https[^"]+)"/\1/')"
if [ -n "$SUM_URL" ]; then
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL_SHA="$(sha256sum "$TMP_WHEEL" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
ACTUAL_SHA="$(shasum -a 256 "$TMP_WHEEL" | awk '{print $1}')"
else
ACTUAL_SHA=""
say "NOTE: cannot verify the wheel checksum — no sha256 tool found (sha256sum/shasum); install one to enable verification."
fi
if [ -n "$ACTUAL_SHA" ]; then
# Pull the hash for THIS wheel by name (a SHA256SUMS may list more than one
# file); fall back to the sole hash if the manifest has no path column.
SUMS="$(curl -fsSL ${AUTH_HEADER[@]+"${AUTH_HEADER[@]}"} -H "Accept: application/octet-stream" "$SUM_URL")"
EXPECTED_SHA="$(printf '%s\n' "$SUMS" | grep -F "$WHEEL_BASENAME" | grep -oiE '[0-9a-f]{64}' | head -n1)"
[ -n "$EXPECTED_SHA" ] || EXPECTED_SHA="$(printf '%s\n' "$SUMS" | grep -oiE '[0-9a-f]{64}' | head -n1)"
if [ -n "$EXPECTED_SHA" ] && [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
fail "wheel checksum MISMATCH (expected $EXPECTED_SHA, got $ACTUAL_SHA) — refusing to install"
fi
[ -n "$EXPECTED_SHA" ] && say "wheel checksum verified ($ACTUAL_SHA)"
fi
else
say "NOTE: this release publishes no checksum — wheel integrity NOT verified."
fi
say "installing chara (server + messaging extras) ..."
# `uv tool install` puts an isolated venv under uv's data dir and links the
# `chara` entrypoint onto PATH. Re-running upgrades in place (--force).
"$UV" tool install --force "opencharaagent[server,messaging] @ ${INSTALL_TARGET}" \
|| fail "uv tool install failed"
# `uv tool install` links into uv's own bin dir; surface it on PATH if needed.
UV_BIN="$("$UV" tool dir --bin 2>/dev/null || true)"
if [ -n "$UV_BIN" ]; then
case ":$PATH:" in
*":$UV_BIN:"*) ;;
*) say "NOTE: $UV_BIN is not on your PATH. Add this to your shell profile:"
say " export PATH=\"$UV_BIN:\$PATH\""
say " (or run: $UV tool update-shell)" ;;
esac
fi
browser_setup
ffmpeg_setup
say "done. run: chara"