Files
weichen 4f581306d7 fix(agent): 拒绝把工作区发给数据目录不一致的 PI runner
test 启动时根 .env 的 dev runner URL 盖过了 18866,sidecar 把 test 工作区重写到 dev 空目录,模型因读不到材料报 MISSING_TARGET。端口约定优先,并在调用前核对 runner data_dir。
2026-09-28 03:16:27 +00:00

174 lines
6.4 KiB
Bash
Executable File
Raw Permalink 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.
#!/usr/bin/env bash
# launch-single.sh — 非容器单二进制启动入口(非 dev 环境;当前用于 test)
#
# 与 launch.sh --no-docker(dev 专用:含 Vite/热更)相对,本脚本服务长期部署形态:
# - PI sidecar(环境专属端口/工作区)+ agent-server 单二进制(embed 静态,无 Vite)
# - LOCALPG_MODE 默认 managed(PGDATA=$DATA_DIR/pg,端口沿用环境约定)
# - 从 configs/.env.<env> + 根 .env 分层加载配置;SYSTEM_DB_*(compose 约定)
# 在此映射为宿主运行所需 CONTEXT_DB_*
#
# 用法:
# ./scripts/launch-single.sh test [--bin /path/to/agent-server] [--build]
# --bin 指定二进制路径(默认 bin/agent-server-<env>)
# --build 从当前工作树构建。test 部署应使用 rc tag 构建的制品,勿默认依赖 --build
#
# 日志:.tmp/launch/{server,pi-sidecar}-<env>.log
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_NAME=""
if [[ $# -gt 0 && "${1:0:1}" != "-" ]]; then
ENV_NAME="$1"
shift
fi
BIN_OVERRIDE=""
DO_BUILD=0
while [[ $# -gt 0 ]]; do
case "$1" in
--bin) BIN_OVERRIDE="${2:-}"; shift 2 ;;
--build) DO_BUILD=1; shift ;;
-h|--help)
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*) echo "unknown arg: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$ENV_NAME" || "$ENV_NAME" == "dev" ]]; then
echo "error: dev 请使用 ./scripts/launch.sh --no-docker;本脚本用于非 dev 环境(如 test)" >&2
exit 1
fi
LOG_DIR="$ROOT/.tmp/launch"
mkdir -p "$LOG_DIR"
info() { printf '\033[1;34m[launch-single]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m ✅ %s\033[0m\n' "$*"; }
skip() { printf '\033[1;32m ⏭️ %s\033[0m\n' "$*"; }
act() { printf '\033[1;33m 🔄 %s\033[0m\n' "$*"; }
fail() { printf '\033[1;31m ❌ %s\033[0m\n' "$*"; }
# ---------- 分层加载配置:根 .env(公共密钥)→ configs/.env.<env>(环境差异) ----------
# 先记下调用方在 shell 里显式导出的 runner URL。根 .env 里的 dev URL 不能算“显式”。
PROCESS_RUNNER_URL="${PI_AGENT_RUNNER_URL:-}"
# shellcheck disable=SC1091
set -a
[[ -f "$ROOT/.env" ]] && . "$ROOT/.env"
if [[ -f "$ROOT/configs/.env.$ENV_NAME" ]]; then
. "$ROOT/configs/.env.$ENV_NAME"
else
fail "缺少 configs/.env.$ENV_NAME(参考 configs/.env.$ENV_NAME.example)"
exit 1
fi
set +a
# ---------- 宿主运行派生(进程环境变量优先于 .env 文件) ----------
export ENV="$ENV_NAME"
DATA_DIR="${DATA_DIR:-$ROOT/data/$ENV_NAME}"
case "$DATA_DIR" in /*) ;; *) DATA_DIR="$ROOT/$DATA_DIR" ;; esac
export DATA_DIR
HTTP_PORT="${HTTP_PORT:-8081}"
export HTTP_HOST="${HTTP_HOST:-${APP_PUBLISH_HOST:-0.0.0.0}}"
export HTTP_PORT
export SEED_DEMO_USERS="${SEED_DEMO_USERS:-false}"
export LOCALPG_MODE="${LOCALPG_MODE:-managed}"
# SYSTEM_DB_*(compose 约定)→ CONTEXT_DB_*(config.Load 宿主读取约定)。
# 注意:SYSTEM_DB_* 必须压过根 .env 的 dev 值 CONTEXT_DB_*(分层加载先读根 .env),
# 因此这里不用 :- 回退而直接覆盖;attach 模式可用 APP_DB_DSN 显式指定。
export CONTEXT_DB_HOST="127.0.0.1"
export CONTEXT_DB_PORT="${SYSTEM_DB_PUBLISH_PORT:-5434}"
export CONTEXT_DB_USER="${SYSTEM_DB_USER:-app}"
export CONTEXT_DB_PASSWORD="${SYSTEM_DB_PASSWORD:?SYSTEM_DB_PASSWORD required}"
export CONTEXT_DB_NAME="${SYSTEM_DB_NAME:-agent_system_$ENV_NAME}"
PI_PORT="${PI_AGENT_RUNNER_PORT:-18866}"
# 非 dev 环境以端口为 runner 权威。只有进程在加载 .env 之前就导出了 URL,才尊重它
# (运维直连远端 runner)。否则根 .env 的 dev URL(18865)会把 test 工作区打到 dev sidecar。
if [[ -n "${PROCESS_RUNNER_URL}" ]]; then
export PI_AGENT_RUNNER_URL="${PROCESS_RUNNER_URL}"
else
export PI_AGENT_RUNNER_URL="http://127.0.0.1:${PI_PORT}"
fi
# 与 compose 对齐的 vault 状态根(FAV 关闭时 inert;无 master key 时 health 同样报 disconnected)
export VAULT_STATE_ROOT="${VAULT_STATE_ROOT:-$ROOT/data/vault/$ENV_NAME}"
export VAULT_STORAGE_IDENTITY="${VAULT_STORAGE_IDENTITY:-${ENV_NAME}-vault}"
BIN="${BIN_OVERRIDE:-$ROOT/bin/agent-server-$ENV_NAME}"
wait_http() {
local url="$1" retries="${2:-30}" i
for ((i = 0; i < retries; i++)); do
curl -sf -m 2 "$url" >/dev/null 2>&1 && return 0
sleep 1
done
return 1
}
stop_pid_on_port() {
local port="$1" pid
pid="$(lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | head -n1 || true)"
if [[ -n "$pid" ]]; then
act "停止占用 :$port 的进程 PID $pid"
kill "$pid" 2>/dev/null || true
for ((i = 0; i < 10; i++)); do
lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 || return 0
sleep 1
done
lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | xargs -r kill -9 2>/dev/null || true
fi
}
info "环境: $ENV_NAME(HTTP :$HTTP_HOST:$HTTP_PORT / DB :$CONTEXT_DB_PORT/$CONTEXT_DB_NAME / PI :$PI_PORT / DATA_DIR=$DATA_DIR)"
# ---------- PI sidecar ----------
info "检测 PI sidecar :$PI_PORT ..."
if wait_http "http://127.0.0.1:$PI_PORT/health" 2; then
skip "PI sidecar 已健康"
else
stop_pid_on_port "$PI_PORT"
act "启动 PI sidecar(日志 $LOG_DIR/pi-sidecar-$ENV_NAME.log)..."
(
export PORT="$PI_PORT"
export DATA_DIR="$DATA_DIR/workspaces"
export PI_CODING_AGENT_DIR="$DATA_DIR/pi-agent"
nohup bash "$ROOT/deploy/pi-agent-sidecar/start.sh" >"$LOG_DIR/pi-sidecar-$ENV_NAME.log" 2>&1 &
)
if wait_http "http://127.0.0.1:$PI_PORT/health" 60; then
ok "PI sidecar 就绪"
else
fail "PI sidecar 健康检查超时,见 $LOG_DIR/pi-sidecar-$ENV_NAME.log"
exit 1
fi
fi
# ---------- agent-server ----------
if [[ "$DO_BUILD" -eq 1 ]]; then
act "从当前工作树构建 $BIN ..."
OUT_BIN="$BIN" bash "$ROOT/scripts/build-single-binary.sh" || { fail "构建失败"; exit 1; }
fi
if [[ ! -x "$BIN" ]]; then
fail "二进制不存在或不可执行: $BIN(用 --bin 指定 rc tag 构建的制品,或 --build)"
exit 1
fi
stop_pid_on_port "$HTTP_PORT"
act "后台启动 agent-server(LOCALPG_MODE=$LOCALPG_MODE,日志 $LOG_DIR/server-$ENV_NAME.log)..."
(
cd "$ROOT"
nohup "$BIN" >"$LOG_DIR/server-$ENV_NAME.log" 2>&1 &
)
if wait_http "http://127.0.0.1:$HTTP_PORT/api/v1/health" 120; then
ok "后端已启动并通过健康检查"
else
fail "后端健康检查超时,见 $LOG_DIR/server-$ENV_NAME.log"
exit 1
fi
info "完成。入口: http://127.0.0.1:$HTTP_PORT PI: http://127.0.0.1:$PI_PORT"
info "若首次从容器切换,请先跑: ./scripts/migrate-test-db.sh(test),容器 volume 保留可回滚"