- remote.conf.example: 含空格的值加引号,避免 source 时截断报错 - deploy.sh: 抽出 run_remote_script 公共远程流程(deploy/restart 复用), 新增 remote-restart 命令(不重新构建直接重启) - 启动前 source 服务器 app.env 注入环境变量(app.env 值需加引号, JDBC URL 含 & 否则被截断)
203 lines
7.0 KiB
Bash
Executable File
203 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# xiaoqu-agent-service 本机部署脚本(dev:本机开发 + 本机运行)
|
|
# 用法: ./deploy/deploy.sh {build|start|stop|restart|status|log}
|
|
# 凭据: 首次使用复制 deploy/.env.example 为 deploy/.env.dev 并填写(该文件已 gitignore,不入库)
|
|
set -euo pipefail
|
|
|
|
APP_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
|
JAR="$APP_HOME/target/xiaoqu-agent-service.jar"
|
|
ENV_FILE="$APP_HOME/deploy/.env.dev"
|
|
PID_FILE="$APP_HOME/deploy/app.pid"
|
|
LOG_DIR="$APP_HOME/logs"
|
|
LOG_FILE="$LOG_DIR/agent-service.log"
|
|
PORT=8088
|
|
PROFILE=dev
|
|
|
|
load_env() {
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo "[ERROR] 缺少 $ENV_FILE,请复制 deploy/.env.example 并填写 dev 凭据" >&2
|
|
exit 1
|
|
fi
|
|
set -a; source "$ENV_FILE"; set +a
|
|
}
|
|
|
|
is_running() {
|
|
[ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null
|
|
}
|
|
|
|
health_check() {
|
|
# /mcp 未带 ak 会返回 401,带 ak 返回 200——两者都证明服务已起来
|
|
for i in $(seq 1 90); do
|
|
if ! is_running; then
|
|
echo "[ERROR] 服务进程已退出,可能是数据库迁移或启动失败" >&2
|
|
tail -40 "$LOG_FILE" >&2 || true
|
|
rm -f "$PID_FILE"
|
|
return 1
|
|
fi
|
|
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "http://127.0.0.1:$PORT/mcp" \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Accept: application/json, text/event-stream' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"deploy-check","version":"0"}}}' || true)
|
|
if [ "$code" = "200" ] || [ "$code" = "401" ]; then
|
|
echo "[OK] 服务已就绪 (HTTP $code, /mcp)"
|
|
return 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "[ERROR] 健康检查超时,查看日志: tail -f $LOG_FILE" >&2
|
|
return 1
|
|
}
|
|
|
|
do_build() {
|
|
cd "$APP_HOME"
|
|
./mvnw clean package
|
|
echo "[OK] 构建完成: $JAR"
|
|
}
|
|
|
|
do_start() {
|
|
if is_running; then
|
|
echo "[SKIP] 已在运行 (pid $(cat "$PID_FILE"))"
|
|
return 0
|
|
fi
|
|
[ -f "$JAR" ] || { echo "[ERROR] 未找到 jar,先执行: $0 build" >&2; exit 1; }
|
|
load_env
|
|
mkdir -p "$LOG_DIR"
|
|
cd "$APP_HOME"
|
|
nohup java -jar "$JAR" --spring.profiles.active="$PROFILE" >> "$LOG_FILE" 2>&1 &
|
|
echo $! > "$PID_FILE"
|
|
echo "[OK] 已启动 (pid $(cat "$PID_FILE")),等待健康检查..."
|
|
health_check
|
|
}
|
|
|
|
do_stop() {
|
|
if is_running; then
|
|
kill "$(cat "$PID_FILE")"
|
|
for i in $(seq 1 15); do is_running || break; sleep 1; done
|
|
is_running && { echo "[WARN] 优雅退出超时,强制 kill -9"; kill -9 "$(cat "$PID_FILE")"; }
|
|
echo "[OK] 已停止"
|
|
else
|
|
echo "[SKIP] 未在运行"
|
|
fi
|
|
rm -f "$PID_FILE"
|
|
}
|
|
|
|
do_status() {
|
|
if is_running; then
|
|
echo "运行中 (pid $(cat "$PID_FILE"), port $PORT)"
|
|
else
|
|
echo "未运行"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ---------- 远程部署 ----------
|
|
REMOTE_CONF="$APP_HOME/deploy/remote.conf"
|
|
|
|
load_remote_conf() {
|
|
if [ ! -f "$REMOTE_CONF" ]; then
|
|
echo "[ERROR] 缺少 $REMOTE_CONF,请复制 deploy/remote.conf.example 并填写" >&2
|
|
exit 1
|
|
fi
|
|
set -a; source "$REMOTE_CONF"; set +a
|
|
: "${REMOTE_SSH_HOST:?未配置 REMOTE_SSH_HOST}"
|
|
: "${REMOTE_DIR:?未配置 REMOTE_DIR}"
|
|
REMOTE_SSH_PORT="${REMOTE_SSH_PORT:-22}"
|
|
REMOTE_PROFILE="${REMOTE_PROFILE:-prod}"
|
|
REMOTE_PORT="${REMOTE_PORT:-8088}"
|
|
REMOTE_JAVA_OPTS="${REMOTE_JAVA_OPTS:-}"
|
|
REMOTE_SPRING_ARGS="${REMOTE_SPRING_ARGS:-}"
|
|
REMOTE_JAVA_BIN="${REMOTE_JAVA_BIN:-java}"
|
|
}
|
|
|
|
ssh_remote() { ssh -p "$REMOTE_SSH_PORT" "$REMOTE_SSH_HOST" "$@"; }
|
|
|
|
do_push() {
|
|
[ -f "$JAR" ] || { echo "[ERROR] 未找到 jar,先执行: $0 build" >&2; exit 1; }
|
|
load_remote_conf
|
|
ssh_remote "mkdir -p '$REMOTE_DIR/logs'"
|
|
scp -P "$REMOTE_SSH_PORT" "$JAR" "$REMOTE_SSH_HOST:$REMOTE_DIR/xiaoqu-agent-service.jar.new"
|
|
echo "[OK] 已上传到 $REMOTE_SSH_HOST:$REMOTE_DIR/xiaoqu-agent-service.jar.new"
|
|
}
|
|
|
|
# 在远程执行 停旧→(可选)换包→启动→健康检查;$1: 1=换上新传的 jar,0=仅重启现有 jar
|
|
run_remote_script() {
|
|
load_remote_conf
|
|
ssh_remote "REMOTE_DIR='$REMOTE_DIR' REMOTE_PROFILE='$REMOTE_PROFILE' REMOTE_PORT='$REMOTE_PORT' REMOTE_JAVA_OPTS='$REMOTE_JAVA_OPTS' REMOTE_SPRING_ARGS='$REMOTE_SPRING_ARGS' REMOTE_JAVA_BIN='$REMOTE_JAVA_BIN' DEPLOY_NEW='$1' bash -s" <<'EOS'
|
|
set -euo pipefail
|
|
cd "$REMOTE_DIR"
|
|
if [ -f app.pid ] && kill -0 "$(cat app.pid)" 2>/dev/null; then
|
|
kill "$(cat app.pid)"
|
|
for i in $(seq 1 15); do kill -0 "$(cat app.pid)" 2>/dev/null || break; sleep 1; done
|
|
kill -0 "$(cat app.pid)" 2>/dev/null && kill -9 "$(cat app.pid)"
|
|
fi
|
|
if [ "$DEPLOY_NEW" = "1" ]; then
|
|
[ -f xiaoqu-agent-service.jar ] && cp xiaoqu-agent-service.jar "xiaoqu-agent-service.jar.bak.$(date +%Y%m%d%H%M%S)"
|
|
mv -f xiaoqu-agent-service.jar.new xiaoqu-agent-service.jar
|
|
fi
|
|
if [ -f app.env ]; then
|
|
set -a; source app.env; set +a
|
|
else
|
|
echo "[WARN] 未找到 $REMOTE_DIR/app.env,环境变量未注入,prd 可能启动失败" >&2
|
|
fi
|
|
nohup "$REMOTE_JAVA_BIN" $REMOTE_JAVA_OPTS -jar xiaoqu-agent-service.jar --spring.profiles.active="$REMOTE_PROFILE" $REMOTE_SPRING_ARGS >> logs/agent-service.log 2>&1 &
|
|
echo $! > app.pid
|
|
echo "[OK] 远程已启动 (pid $(cat app.pid)),等待健康检查..."
|
|
for i in $(seq 1 90); do
|
|
if ! kill -0 "$(cat app.pid)" 2>/dev/null; then
|
|
echo "[ERROR] 远程进程已退出,最后 40 行日志:" >&2
|
|
tail -40 logs/agent-service.log >&2 || true
|
|
exit 1
|
|
fi
|
|
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "http://127.0.0.1:$REMOTE_PORT/mcp" \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Accept: application/json, text/event-stream' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"deploy-check","version":"0"}}}' || true)
|
|
if [ "$code" = "200" ] || [ "$code" = "401" ]; then
|
|
echo "[OK] 远程服务已就绪 (HTTP $code, /mcp)"
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "[ERROR] 远程健康检查超时,日志: $REMOTE_DIR/logs/agent-service.log" >&2
|
|
exit 1
|
|
EOS
|
|
}
|
|
|
|
do_remote_deploy() {
|
|
do_build
|
|
do_push
|
|
run_remote_script 1
|
|
}
|
|
|
|
do_remote_restart() {
|
|
run_remote_script 0
|
|
}
|
|
|
|
do_remote_stop() {
|
|
load_remote_conf
|
|
ssh_remote "cd '$REMOTE_DIR' && if [ -f app.pid ] && kill -0 \$(cat app.pid) 2>/dev/null; then kill \$(cat app.pid) && rm -f app.pid && echo '[OK] 远程已停止'; else echo '[SKIP] 远程未在运行'; fi"
|
|
}
|
|
|
|
do_remote_status() {
|
|
load_remote_conf
|
|
ssh_remote "cd '$REMOTE_DIR' && if [ -f app.pid ] && kill -0 \$(cat app.pid) 2>/dev/null; then echo \"远程运行中 (pid \$(cat app.pid))\"; else echo '远程未运行'; exit 1; fi"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
build) do_build ;;
|
|
start) do_start ;;
|
|
stop) do_stop ;;
|
|
restart) do_stop; do_start ;;
|
|
status) do_status ;;
|
|
log) tail -f "$LOG_FILE" ;;
|
|
push) do_push ;;
|
|
remote-deploy) do_remote_deploy ;;
|
|
remote-restart) do_remote_restart ;;
|
|
remote-stop) do_remote_stop ;;
|
|
remote-status) do_remote_status ;;
|
|
remote-log) load_remote_conf; ssh_remote "tail -f '$REMOTE_DIR/logs/agent-service.log'" ;;
|
|
*)
|
|
echo "用法: $0 {build|start|stop|restart|status|log|push|remote-deploy|remote-restart|remote-stop|remote-status|remote-log}" >&2
|
|
exit 1 ;;
|
|
esac
|