testzerogpu / bootstrap.sh
HelloSun's picture
pi default context 128k (CONTEXT_SIZE=131072 for build.sh)
85620fa verified
Raw History Blame Contribute Delete
6.39 kB
#!/usr/bin/env bash
# testzerogpu2 預設環境 bootstrap。
# app.py 啟動時會在背景執行這支腳本,做兩件事:
# 1. 安裝 HelloSun/storage 的 s5p(oshopencode 等)到工作目錄並設定 PATH
# 2. 安裝「甲」:flint Q4_1 llama-server(port 20000)並設定 pi coding agent 使用它
#
# 所有輸出都寫到 /tmp/webterm-bootstrap.log,狀態可看 GET /api/bootstrap/status。
set -uo pipefail
LOG="${WEBTERM_BOOTSTRAP_LOG:-/tmp/webterm-bootstrap.log}"
mkdir -p "$(dirname "$LOG")" 2>/dev/null || true
exec >>"$LOG" 2>&1
HOME_DIR="${HOME:-/home/user}"
# repo/工作目錄 = 這支腳本所在目錄(HF Space 是 /app,不是 $HOME/app)
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
S5P_DIR="$APP_DIR/s5p"
FLINT_DIR="$HOME_DIR/flint_cpu_opt"
STORAGE_BASE="${S5P_STORAGE_BASE:-https://huggingface.co/spaces/HelloSun/storage/resolve/main}"
FLINT_BASE="${FLINT_BASE:-https://huggingface.co/HelloSun/flint_cpu_opt/resolve/main}"
S5P_FILES=(
"a.sh"
"o.sh"
"oshopencode.py"
"oshopencode/HANDOFF.md"
"oshopencode/o.sh"
"oshopencode/oshopencode.py"
"oshopencode/tests/local_http_proxy.py"
"oshopencode/tests/test_oshopencode.py"
)
log() { echo "[bootstrap $(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
tor_port_open() { (exec 3<>/dev/tcp/127.0.0.1/9050) 2>/dev/null; }
# oshopencode.py 預設走 Tor;Space 沒有 systemd,所以自己設定 torrc 並用 init script 啟動。
install_tor_env() {
log "設定 Tor 環境(SocksPort 9050 + ControlSocket /run/tor/control)"
local rc=/etc/tor/torrc
if [ -f "$rc" ]; then
grep -qE '^[[:space:]]*SocksPort[[:space:]]+9050' "$rc" 2>/dev/null || \
grep -qE '^[[:space:]]*SocksPort[[:space:]]' "$rc" 2>/dev/null || \
echo 'SocksPort 9050' >>"$rc"
grep -qE '^[[:space:]]*ControlSocket[[:space:]]+/run/tor/control' "$rc" 2>/dev/null || \
echo 'ControlSocket /run/tor/control' >>"$rc"
grep -qE '^[[:space:]]*CookieAuthentication[[:space:]]+1' "$rc" 2>/dev/null || \
echo 'CookieAuthentication 1' >>"$rc"
fi
if ! tor_port_open; then
mkdir -p /run/tor
if id debian-tor >/dev/null 2>&1; then
chown -R debian-tor:debian-tor /run/tor 2>/dev/null || true
fi
chmod 700 /run/tor 2>/dev/null || true
if [ -x /etc/init.d/tor ]; then
/etc/init.d/tor start >>"$LOG" 2>&1 || true
fi
if ! tor_port_open && command -v tor >/dev/null 2>&1; then
if id debian-tor >/dev/null 2>&1; then
chown -R debian-tor:debian-tor /run/tor 2>/dev/null || true
su -s /bin/sh debian-tor -c 'tor --RunAsDaemon 1' >>"$LOG" 2>&1 || true
fi
fi
fi
for _ in $(seq 1 15); do
tor_port_open && break
sleep 1
done
if tor_port_open; then
log "Tor 就緒(127.0.0.1:9050)"
return 0
fi
log "WARN Tor 未就緒;oshopencode 會自動退回 HTTP proxy 模式"
return 1
}
add_path_line() {
local rc="$1" line="$2"
[ -n "$rc" ] || return 0
touch "$rc" 2>/dev/null || return 0
grep -qF "$line" "$rc" 2>/dev/null || echo "$line" >>"$rc"
}
install_s5p() {
log "install s5p -> $S5P_DIR"
mkdir -p "$S5P_DIR/oshopencode/tests" "$HOME_DIR/.local/bin"
local f failed=0
for f in "${S5P_FILES[@]}"; do
mkdir -p "$S5P_DIR/$(dirname "$f")"
if ! curl -fL --retry 3 --retry-delay 3 --max-time 300 \
"$STORAGE_BASE/s5p/$f" -o "$S5P_DIR/$f"; then
log "WARN 下載失敗:$f"
failed=1
fi
done
chmod +x "$S5P_DIR"/*.sh "$S5P_DIR/oshopencode.py" 2>/dev/null || true
chmod +x "$S5P_DIR/oshopencode"/*.sh "$S5P_DIR/oshopencode/oshopencode.py" 2>/dev/null || true
# 讓 `oshopencode.py` 直接可用(不論 shell 有沒有讀到 rc)。
cat >"$HOME_DIR/.local/bin/oshopencode.py" <<EOS
#!/usr/bin/env bash
exec python3 "$S5P_DIR/oshopencode.py" "\$@"
EOS
chmod +x "$HOME_DIR/.local/bin/oshopencode.py"
ln -sf "$HOME_DIR/.local/bin/oshopencode.py" /usr/local/bin/oshopencode.py 2>/dev/null || true
# opencode 由 app.py 安裝在 ~/.opencode/bin;做一個固定路徑的連結,登入 shell 一定找得到。
if [ -x "$HOME_DIR/.opencode/bin/opencode" ]; then
ln -sf "$HOME_DIR/.opencode/bin/opencode" /usr/local/bin/opencode 2>/dev/null || true
fi
local path_line="export PATH=\"$S5P_DIR:$S5P_DIR/oshopencode:$HOME_DIR/.opencode/bin:$HOME_DIR/.local/bin:\$PATH\""
add_path_line "$HOME_DIR/.bashrc" "$path_line"
add_path_line "$HOME_DIR/.profile" "$path_line"
add_path_line "$HOME_DIR/.bash_profile" "$path_line"
export PATH="$S5P_DIR:$S5P_DIR/oshopencode:$HOME_DIR/.opencode/bin:$HOME_DIR/.local/bin:$PATH"
if [ "$failed" -ne 0 ]; then
log "s5p 部分檔案下載失敗"
return 1
fi
log "s5p 安裝完成;oshopencode.py -> $(command -v oshopencode.py 2>/dev/null || echo 未找到)"
return 0
}
install_jia() {
log "install 甲:flint Q4_1 llama-server :20000 + pi coding agent 設定"
mkdir -p "$FLINT_DIR/patches"
local f
for f in "build.sh" "patches/BASE_COMMIT" "patches/flint-cpu-opt.patch"; do
mkdir -p "$FLINT_DIR/$(dirname "$f")"
if ! curl -fL --retry 3 --retry-delay 3 --max-time 300 \
"$FLINT_BASE/$f" -o "$FLINT_DIR/$f"; then
log "WARN 無法取得 $f"
return 1
fi
done
chmod +x "$FLINT_DIR/build.sh"
local avail_kb
avail_kb=$(df -Pk "$HOME_DIR" 2>/dev/null | awk 'NR==2{print $4}')
avail_kb=${avail_kb:-0}
log "HOME 可用磁碟:$((avail_kb / 1024 / 1024)) GiB"
if [ "$avail_kb" -lt 30000000 ]; then
log "WARN 可用磁碟 < 30 GiB;24 GiB 模型下載可能失敗,仍嘗試"
fi
# build.sh 會:編譯 patched llama.cpp(若尚未編譯)→ 下載 Q4_1 → 設定 pi → 啟動 server:20000
# pi 預設 context 128k(131072);build.sh 的預設是 32768,這裡用環境變數覆寫。
( cd "$FLINT_DIR" && CONTEXT_SIZE="${PI_CONTEXT_SIZE:-131072}" bash ./build.sh )
local rc=$?
# pi 裝在 ~/.local/bin;連結到 /usr/local/bin,任何 shell 都直接可用。
local b
for b in pi pi-local; do
if [ -x "$HOME_DIR/.local/bin/$b" ]; then
ln -sf "$HOME_DIR/.local/bin/$b" "/usr/local/bin/$b" 2>/dev/null || true
fi
done
log "build.sh 結束,exit=$rc"
return "$rc"
}
log "===== bootstrap 開始 ====="
install_tor_env || true
install_s5p || log "WARN s5p 安裝未完全成功"
install_jia || log "WARN 甲 安裝未完全成功"
log "===== bootstrap 結束 ====="