#!/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" </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 結束 ====="