Files
edict/scripts/start_opencode.sh

288 lines
9.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 三省六部 · OpenCode 一键启动
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_DIR"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
DASHBOARD_HOST="${EDICT_DASHBOARD_HOST:-127.0.0.1}"
DASHBOARD_PORT="${EDICT_DASHBOARD_PORT:-7891}"
OPENCODE_HOST="${OPENCODE_HOST:-127.0.0.1}"
OPENCODE_PORT="${OPENCODE_PORT:-4096}"
OPENCODE_SERVER_URL="${OPENCODE_SERVER_URL:-http://${OPENCODE_HOST}:${OPENCODE_PORT}}"
PIDDIR="$REPO_DIR/.pids"
LOGDIR="$REPO_DIR/logs"
SERVER_PIDFILE="$PIDDIR/server.pid"
LOOP_PIDFILE="$PIDDIR/loop.pid"
OPENCODE_PIDFILE="$PIDDIR/opencode.pid"
SERVER_LOG="$LOGDIR/server.log"
LOOP_LOG="$LOGDIR/loop.log"
OPENCODE_LOG="$LOGDIR/opencode.log"
resolve_python() {
local candidate version major minor
for candidate in "${EDICT_PYTHON:-}" python3.14 python3.13 python3.12 python3.11 python3.10 python3; do
[ -n "$candidate" ] || continue
command -v "$candidate" &>/dev/null || continue
version=$("$candidate" -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")' 2>/dev/null) || continue
major=${version%%.*}
minor=${version#*.}
if [ "$major" -gt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -ge 10 ]; }; then
command -v "$candidate"
return 0
fi
done
return 1
}
resolve_opencode() {
if [[ -n "${OPENCODE_BIN:-}" && -x "${OPENCODE_BIN:-}" ]]; then
echo "$OPENCODE_BIN"
return 0
fi
if command -v opencode &>/dev/null; then
command -v opencode
return 0
fi
if [[ -x "$HOME/.opencode/bin/opencode" ]]; then
echo "$HOME/.opencode/bin/opencode"
return 0
fi
return 1
}
http_ok() {
"$PYTHON_BIN" - "$1" <<'PY' >/dev/null 2>&1
import sys
import urllib.request
try:
r = urllib.request.urlopen(sys.argv[1], timeout=3)
raise SystemExit(0 if 200 <= r.status < 500 else 1)
except Exception:
raise SystemExit(1)
PY
}
opencode_agents_ok() {
"$PYTHON_BIN" - "$OPENCODE_SERVER_URL" <<'PY' >/dev/null 2>&1
import json
import sys
import urllib.request
try:
url = sys.argv[1].rstrip("/") + "/agent"
data = json.loads(urllib.request.urlopen(url, timeout=5).read().decode("utf-8"))
names = {item.get("name") for item in data if isinstance(item, dict)}
raise SystemExit(0 if {"taizi", "zhongshu", "shangshu"}.issubset(names) else 1)
except Exception:
raise SystemExit(1)
PY
}
write_opencode_pid_from_port() {
command -v lsof &>/dev/null || return 0
local pid
pid=$(lsof -tiTCP:"$OPENCODE_PORT" -sTCP:LISTEN 2>/dev/null | head -n 1 || true)
if [[ "$pid" =~ ^[0-9]+$ ]]; then
echo "$pid" > "$OPENCODE_PIDFILE"
fi
return 0
}
stop_pidfile() {
local pidfile="$1"
if [[ -f "$pidfile" ]]; then
local pid
pid=$(cat "$pidfile" 2>/dev/null || true)
if [[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
for _ in $(seq 1 10); do
kill -0 "$pid" 2>/dev/null || break
sleep 0.3
done
fi
rm -f "$pidfile"
fi
}
stop_opencode_on_port() {
command -v lsof &>/dev/null || return 0
local pids cmd
pids=$(lsof -tiTCP:"$OPENCODE_PORT" -sTCP:LISTEN 2>/dev/null || true)
for pid in $pids; do
cmd=$(ps -p "$pid" -o command= 2>/dev/null || true)
if [[ "$cmd" == *"opencode"* ]]; then
kill "$pid" 2>/dev/null || true
fi
done
return 0
}
stop_screen_session() {
local name="$1"
if command -v screen &>/dev/null && screen -ls 2>/dev/null | grep -q "[.]${name}[[:space:]]"; then
screen -S "$name" -X quit >/dev/null 2>&1 || true
fi
}
start_opencode_server() {
if command -v screen &>/dev/null; then
screen -dmS edict-opencode bash -lc "cd \"$REPO_DIR\" && echo \$\$ > \"$OPENCODE_PIDFILE\" && exec \"$OPENCODE_BIN_RESOLVED\" serve --hostname \"$OPENCODE_HOST\" --port \"$OPENCODE_PORT\" >> \"$OPENCODE_LOG\" 2>&1"
else
nohup "$OPENCODE_BIN_RESOLVED" serve --hostname "$OPENCODE_HOST" --port "$OPENCODE_PORT" \
>> "$OPENCODE_LOG" 2>&1 &
echo $! > "$OPENCODE_PIDFILE"
fi
}
start_dashboard_server() {
if command -v screen &>/dev/null; then
screen -dmS edict-server bash -lc "cd \"$REPO_DIR\" && echo \$\$ > \"$SERVER_PIDFILE\" && exec env EDICT_RUNTIME=opencode EDICT_AGENT_RUNTIME=opencode OPENCODE_BIN=\"$OPENCODE_BIN_RESOLVED\" OPENCODE_SERVER_URL=\"$OPENCODE_SERVER_URL\" \"$PYTHON_BIN\" \"$REPO_DIR/dashboard/server.py\" --host \"$DASHBOARD_HOST\" --port \"$DASHBOARD_PORT\" >> \"$SERVER_LOG\" 2>&1"
else
nohup env EDICT_RUNTIME=opencode EDICT_AGENT_RUNTIME=opencode \
OPENCODE_BIN="$OPENCODE_BIN_RESOLVED" OPENCODE_SERVER_URL="$OPENCODE_SERVER_URL" \
"$PYTHON_BIN" "$REPO_DIR/dashboard/server.py" --host "$DASHBOARD_HOST" --port "$DASHBOARD_PORT" \
>> "$SERVER_LOG" 2>&1 &
echo $! > "$SERVER_PIDFILE"
fi
}
start_refresh_loop() {
if command -v screen &>/dev/null; then
screen -dmS edict-loop bash -lc "cd \"$REPO_DIR\" && echo \$\$ > \"$LOOP_PIDFILE\" && exec env EDICT_RUNTIME=opencode EDICT_AGENT_RUNTIME=opencode EDICT_PYTHON=\"$PYTHON_BIN\" OPENCODE_BIN=\"$OPENCODE_BIN_RESOLVED\" OPENCODE_SERVER_URL=\"$OPENCODE_SERVER_URL\" bash \"$REPO_DIR/scripts/run_loop_opencode.sh\" >> \"$LOOP_LOG\" 2>&1"
else
nohup env EDICT_RUNTIME=opencode EDICT_AGENT_RUNTIME=opencode EDICT_PYTHON="$PYTHON_BIN" \
OPENCODE_BIN="$OPENCODE_BIN_RESOLVED" OPENCODE_SERVER_URL="$OPENCODE_SERVER_URL" \
bash "$REPO_DIR/scripts/run_loop_opencode.sh" \
>> "$LOOP_LOG" 2>&1 &
echo $! > "$LOOP_PIDFILE"
fi
}
stop_repo_dashboard_on_port() {
command -v lsof &>/dev/null || return 0
local pids cmd
pids=$(lsof -tiTCP:"$DASHBOARD_PORT" -sTCP:LISTEN 2>/dev/null || true)
for pid in $pids; do
cmd=$(ps -p "$pid" -o command= 2>/dev/null || true)
if [[ "$cmd" == *"$REPO_DIR/dashboard/server.py"* || "$cmd" == *"dashboard/server.py"* ]]; then
kill "$pid" 2>/dev/null || true
fi
done
return 0
}
stop_repo_loops() {
command -v lsof &>/dev/null || return 0
local pid cmd cwd
while read -r pid cmd; do
[[ -n "${pid:-}" ]] || continue
if [[ "$cmd" != *"scripts/run_loop.sh"* && "$cmd" != *"scripts/run_loop_opencode.sh"* ]]; then
continue
fi
cwd=$(lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1 || true)
if [[ "$cwd" == "$REPO_DIR" ]]; then
kill "$pid" 2>/dev/null || true
fi
done < <(ps ax -o pid=,command=)
return 0
}
PYTHON_BIN=$(resolve_python) || {
echo -e "${RED}未找到 Python 3.10+${NC}"
exit 1
}
OPENCODE_BIN_RESOLVED=$(resolve_opencode) || {
echo -e "${RED}未找到 OpenCode CLI。请先安装 opencode或设置 OPENCODE_BIN。${NC}"
exit 1
}
export EDICT_PYTHON="$PYTHON_BIN"
export EDICT_RUNTIME=opencode
export EDICT_AGENT_RUNTIME=opencode
export OPENCODE_BIN="$OPENCODE_BIN_RESOLVED"
export OPENCODE_SERVER_URL
mkdir -p "$PIDDIR" "$LOGDIR" "$REPO_DIR/data"
for f in live_status.json agent_config.json model_change_log.json sync_status.json; do
[[ -f "$REPO_DIR/data/$f" ]] || echo '{}' > "$REPO_DIR/data/$f"
done
[[ -f "$REPO_DIR/data/pending_model_changes.json" ]] || echo '[]' > "$REPO_DIR/data/pending_model_changes.json"
[[ -f "$REPO_DIR/data/tasks_source.json" ]] || echo '[]' > "$REPO_DIR/data/tasks_source.json"
[[ -f "$REPO_DIR/data/tasks.json" ]] || echo '[]' > "$REPO_DIR/data/tasks.json"
[[ -f "$REPO_DIR/data/officials.json" ]] || echo '[]' > "$REPO_DIR/data/officials.json"
[[ -f "$REPO_DIR/data/officials_stats.json" ]] || echo '{}' > "$REPO_DIR/data/officials_stats.json"
echo ""
echo -e "${BLUE}╔══════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 三省六部 · OpenCode 模式启动中 ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════╝${NC}"
echo ""
echo -e "Python: ${GREEN}${PYTHON_BIN}${NC}"
echo -e "OpenCode: ${GREEN}${OPENCODE_BIN_RESOLVED}${NC}"
echo -e "${GREEN}▶ 同步 OpenCode agent 配置...${NC}"
"$PYTHON_BIN" "$REPO_DIR/scripts/sync_opencode_agents.py"
if http_ok "${OPENCODE_SERVER_URL}/doc" && opencode_agents_ok; then
write_opencode_pid_from_port
echo -e "${GREEN}▶ 复用已运行的 OpenCode server: ${OPENCODE_SERVER_URL}${NC}"
else
if http_ok "${OPENCODE_SERVER_URL}/doc"; then
echo -e "${YELLOW}⚠️ 当前 ${OPENCODE_SERVER_URL} 不是本项目的 OpenCode server正在切换...${NC}"
stop_screen_session edict-opencode
stop_opencode_on_port
stop_pidfile "$OPENCODE_PIDFILE"
sleep 0.5
fi
echo -e "${GREEN}▶ 启动 OpenCode server...${NC}"
stop_screen_session edict-opencode
start_opencode_server
for _ in $(seq 1 20); do
http_ok "${OPENCODE_SERVER_URL}/doc" && break
sleep 0.5
done
if ! http_ok "${OPENCODE_SERVER_URL}/doc" || ! opencode_agents_ok; then
echo -e "${RED}OpenCode server 启动失败,请查看日志: ${OPENCODE_LOG}${NC}"
exit 1
fi
write_opencode_pid_from_port
fi
echo -e "${GREEN}▶ 切换看板到 OpenCode 运行时...${NC}"
stop_pidfile "$SERVER_PIDFILE"
stop_pidfile "$LOOP_PIDFILE"
stop_screen_session edict-server
stop_screen_session edict-loop
stop_repo_dashboard_on_port
stop_repo_loops
rm -f /tmp/sansheng_liubu_opencode_refresh.pid
sleep 0.5
start_dashboard_server
start_refresh_loop
for _ in $(seq 1 20); do
http_ok "http://${DASHBOARD_HOST}:${DASHBOARD_PORT}/healthz" && break
sleep 0.5
done
if ! http_ok "http://${DASHBOARD_HOST}:${DASHBOARD_PORT}/healthz"; then
echo -e "${RED}看板启动失败,请查看日志: ${SERVER_LOG}${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}✅ OpenCode 模式已启动${NC}"
echo -e " 看板: ${BLUE}http://${DASHBOARD_HOST}:${DASHBOARD_PORT}${NC}"
echo -e " OpenCode: ${BLUE}${OPENCODE_SERVER_URL}${NC}"
echo -e " 日志: ${BLUE}${LOGDIR}${NC}"
if command -v open &>/dev/null; then
open "http://${DASHBOARD_HOST}:${DASHBOARD_PORT}" >/dev/null 2>&1 || true
elif command -v xdg-open &>/dev/null; then
xdg-open "http://${DASHBOARD_HOST}:${DASHBOARD_PORT}" >/dev/null 2>&1 || true
fi