File size: 6,386 Bytes
1949064
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/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 結束 ====="