Files
gongxue-base/deploy.sh
wangziqi 9d74bd2d51
Some checks failed
CI / check (pull_request) Failing after 5m3s
ci(deploy): deploy.sh 支持本地直跑模式 + workflow 健康检查读取 .env PORT
- deploy.sh 默认本地直跑(./deploy.sh 或 ./deploy.sh local),不再需要 SSH;传 ssh_host 才走远程版
- workflow 健康检查从 .env 读取 PORT(默认 3000),适配 PORT=3002 等自定义端口
2026-08-09 22:04:21 +08:00

161 lines
5.0 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.

#!/usr/bin/env bash
# 恭学教育 — PM2 部署脚本
# 用法:
# ./deploy.sh # 本地直跑(默认,适合 runner/代码就在生产服务器上,不需要 SSH
# ./deploy.sh local # 同上
# ./deploy.sh <ssh_host> # 远程部署(构建后 rsync 到远端,需要 SSH 免密/key
# 前提: 服务器 MySQL 已在运行PM2 已安装
set -euo pipefail
MODE="${1:-local}"
# 调用锁mkdir 原子创建,已存在即代表已有部署在跑,直接退出
LOCK_DIR="/tmp/gongxue-deploy.lock"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
echo "检测到 $LOCK_DIR,已有部署在运行。若确认没有其他部署,请手动删除该锁目录。" >&2
exit 1
fi
release_deploy_lock() {
rmdir "$LOCK_DIR" 2>/dev/null || true
}
trap release_deploy_lock EXIT
echo "=== 1/4 本地构建后端 ==="
npm run build -w @gongxue/server
echo "=== 2/4 本地构建前端 ==="
npm run build -w @gongxue/admin
# ── 健康检查(本地/远程共用逻辑,通过 pm2 jlist 按 name 判断 online 且无 unstable_restarts──
# online ≠ healthy若后续后端提供健康端点应优先 curl 探测。
run_health_check() {
set -euo pipefail
local healthy=0
local i
for i in $(seq 1 15); do
if pm2 jlist 2>/dev/null | node -e '
let data = "";
process.stdin.on("data", (c) => (data += c));
process.stdin.on("end", () => {
const apps = JSON.parse(data || "[]");
const app = apps.find((a) => a && a.name === "gongxue-backend");
const env = (app || {}).pm2_env || {};
if (env.status === "online" && Number(env.unstable_restarts || 0) === 0) {
process.exit(0);
}
process.exit(1);
});
'; then
healthy=1
break
fi
sleep 2
done
if [ "${healthy}" -ne 1 ]; then
echo "错误后端进程未就绪gongxue-backend 非 online 或存在不稳定重启)" >&2
pm2 status >&2
return 1
fi
echo "健康检查通过gongxue-backend 在线"
}
if [ "${MODE}" = "local" ]; then
echo "=== 3/4 安装依赖 → 迁移 → PM2 重载(本机) ==="
npm ci
echo '执行数据库迁移...'
npm run migration:run -w @gongxue/server
echo 'PM2 重载...'
pm2 startOrReload ecosystem.config.cjs --update-env
pm2 save
pm2 status
echo "=== 4/4 健康检查 ==="
run_health_check
echo ""
echo "部署完成!后端: http://127.0.0.1:$(grep '^PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || echo 3000)/api"
exit 0
fi
# ── 远程模式(保留,用于部署到其他主机)──
SSH_HOST="${MODE}"
# 校验 SSH_HOST非空、不以 - 开头、仅允许安全字符(防注入)
if [[ -z "${SSH_HOST}" ]]; then
echo "错误SSH_HOST 不能为空" >&2
exit 1
fi
if [[ "${SSH_HOST}" == -* ]]; then
echo "错误SSH_HOST 不能以 - 开头(会被 ssh/rsync 当作命令行选项)" >&2
exit 1
fi
if [[ ! "${SSH_HOST}" =~ ^[A-Za-z0-9._:@%+=,-]+$ ]]; then
echo "错误SSH_HOST 含空白或 shell 元字符(仅允许字母、数字及 . _ : @ % + = , -" >&2
exit 1
fi
REMOTE_DIR="/opt/gongxue"
echo "=== 3/5 同步到 ${SSH_HOST} ==="
# --delete 保留,但必须排除服务器端独有文件(.env、数据、上传目录防止被清掉
rsync -avz --delete \
-e "ssh -o BatchMode=yes -o ConnectTimeout=10" \
--exclude='node_modules' \
--exclude='.git' \
--exclude='*.db' \
--exclude='.DS_Store' \
--exclude='logs/' \
--exclude='.turbo/' \
--exclude='.env*' \
--exclude='data.sql' \
--exclude='uploads/' \
./ "${SSH_HOST}:${REMOTE_DIR}/"
echo "=== 4/5 安装依赖 → 迁移 → PM2 重载 ==="
ssh -o BatchMode=yes -o ConnectTimeout=10 "${SSH_HOST}" "
set -e
cd ${REMOTE_DIR}
mkdir -p logs
echo '安装依赖...'
# 注意migration:run 依赖 ts-node/tsconfig-pathsdevDependencies不能 --omit=dev
npm ci
echo '执行数据库迁移...'
npm run migration:run -w @gongxue/server
echo 'PM2 重载...'
pm2 startOrReload ecosystem.config.cjs --update-env
pm2 save
pm2 status
"
echo "=== 5/5 健康检查 ==="
ssh -o BatchMode=yes -o ConnectTimeout=10 "${SSH_HOST}" bash -s <<'REMOTE_HEALTH'
set -euo pipefail
HEALTHY=0
for _i in $(seq 1 15); do
if pm2 jlist 2>/dev/null | node -e '
let data = "";
process.stdin.on("data", (c) => (data += c));
process.stdin.on("end", () => {
const apps = JSON.parse(data || "[]");
const app = apps.find((a) => a && a.name === "gongxue-backend");
const env = (app || {}).pm2_env || {};
if (env.status === "online" && Number(env.unstable_restarts || 0) === 0) {
process.exit(0);
}
process.exit(1);
});
'; then
HEALTHY=1
break
fi
sleep 2
done
if [ "${HEALTHY}" -ne 1 ]; then
echo "错误后端进程未就绪gongxue-backend 非 online 或存在不稳定重启)" >&2
pm2 status >&2
exit 1
fi
echo "健康检查通过gongxue-backend 在线"
REMOTE_HEALTH
echo ""
echo "部署完成!"
echo "访问: http://$(ssh -o BatchMode=yes -o ConnectTimeout=10 "${SSH_HOST}" 'hostname -I 2>/dev/null | awk "{print \$1}" || curl -s ifconfig.me')"