- 两个工作流 runs-on 改为 hk(在线);Go/Node 工具链自包含下载+工作区缓存 - 后端产物改为发布 Gitea Release 附件(server-v<版本>),与 runner 位置解耦 - deploy-watch.sh 重写为从 Release 拉取:匿名/令牌 HTTPS 下载、包结构预检、 按 release id 幂等、失败下轮重试 - 移除对 /ci-artifacts 本机目录的依赖(原设计要求 runner 与生产同机) - PULL_DEPLOY.md 架构图同步更新
99 lines
3.3 KiB
Bash
99 lines
3.3 KiB
Bash
#!/bin/bash
|
||
# 拉取式部署 watcher - 从 Gitea Release 拉取最新部署包并自动部署
|
||
# 设计原则:
|
||
# - CI 在任意 runner(hk 等)构建,产物以 Release 附件发布到 Gitea
|
||
# - 本脚本由服务器本地定时任务(1Panel 计划任务)每分钟调用,通过 HTTPS 拉取,
|
||
# 任何 AI/CI 均不通过 SSH 操作服务器
|
||
# 安装方式见 .gitea/docs/PULL_DEPLOY.md(1Panel 原生方式,一次性配置)
|
||
#
|
||
# 可选环境变量(建议写入 /opt/lunar/production/.env 并在计划任务里 source):
|
||
# GITEA_DEPLOY_TOKEN 只读令牌(仓库公开时可省略,匿名即可下载)
|
||
|
||
set -u
|
||
|
||
GITEA_URL="${GITEA_URL:-https://gitea.neatcn.com}"
|
||
REPO="${REPO:-gouki/lunar-mini}"
|
||
STATE_FILE="${STATE_FILE:-/opt/lunar/production/.last-deployed-release}"
|
||
DEPLOY_SCRIPT="${DEPLOY_SCRIPT:-/opt/lunar/scripts/deploy.sh}"
|
||
LOG_FILE="${LOG_FILE:-/opt/lunar/deploy-watch.log}"
|
||
WORK_DIR="$(mktemp -d)"
|
||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||
|
||
log() {
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
|
||
}
|
||
|
||
AUTH_ARGS=()
|
||
if [ -n "${GITEA_DEPLOY_TOKEN:-}" ]; then
|
||
AUTH_ARGS=(-H "Authorization: token ${GITEA_DEPLOY_TOKEN}")
|
||
fi
|
||
|
||
# 查询最新 20 个 release,取 tag 前缀为 server-v 且带 deploy.tar.gz 附件的最新一个
|
||
RELEASE_JSON=$(curl -fsS --connect-timeout 10 --max-time 30 \
|
||
"${AUTH_ARGS[@]}" \
|
||
"$GITEA_URL/api/v1/repos/$REPO/releases?limit=20" \
|
||
-o "$WORK_DIR/releases.json" && cat "$WORK_DIR/releases.json") || {
|
||
log "查询 release 列表失败,等待下次轮询"
|
||
exit 1
|
||
}
|
||
|
||
PARSED=$(python3 - "$WORK_DIR/releases.json" <<'PY'
|
||
import json, sys
|
||
releases = json.load(open(sys.argv[1]))
|
||
for r in releases:
|
||
if not r.get("tag_name", "").startswith("server-v"):
|
||
continue
|
||
if r.get("draft") or r.get("prerelease"):
|
||
continue
|
||
for a in r.get("assets", []):
|
||
if a.get("name") == "deploy.tar.gz":
|
||
print(r["id"])
|
||
print(r["tag_name"])
|
||
print(a["id"])
|
||
print(a["browser_download_url"])
|
||
sys.exit(0)
|
||
PY
|
||
) || true
|
||
|
||
if [ -z "$PARSED" ]; then
|
||
# 尚无可部署的发布版本,静默等待
|
||
exit 0
|
||
fi
|
||
|
||
RELEASE_ID=$(echo "$PARSED" | sed -n 1p)
|
||
TAG=$(echo "$PARSED" | sed -n 2p)
|
||
ASSET_ID=$(echo "$PARSED" | sed -n 3p)
|
||
DOWNLOAD_URL=$(echo "$PARSED" | sed -n 4p)
|
||
|
||
# 幂等:已部署过该 release 则跳过
|
||
LAST="$(cat "$STATE_FILE" 2>/dev/null || true)"
|
||
if [ "$LAST" = "$RELEASE_ID" ]; then
|
||
exit 0
|
||
fi
|
||
|
||
log "发现新版本 $TAG (release #$RELEASE_ID),开始拉取部署"
|
||
|
||
# 下载部署包(公开仓库可匿名;-L 跟随重定向)
|
||
if ! curl -fsSL --connect-timeout 10 --max-time 300 \
|
||
"${AUTH_ARGS[@]}" \
|
||
"$DOWNLOAD_URL" -o "$WORK_DIR/deploy.tar.gz"; then
|
||
log "下载部署包失败,等待下次轮询重试"
|
||
exit 1
|
||
fi
|
||
|
||
# 包结构预检(deploy.sh 内部还会再校验)
|
||
if ! tar -tzf "$WORK_DIR/deploy.tar.gz" | grep -q "^deploy/bin/server$"; then
|
||
log "部署包结构异常(缺少 deploy/bin/server),中止"
|
||
exit 1
|
||
fi
|
||
|
||
# 执行部署;成功后才记录 release id(失败时下次轮询会重试)
|
||
if bash "$DEPLOY_SCRIPT" "$WORK_DIR/deploy.tar.gz" >> "$LOG_FILE" 2>&1; then
|
||
mkdir -p "$(dirname "$STATE_FILE")"
|
||
printf '%s' "$RELEASE_ID" > "$STATE_FILE"
|
||
log "部署成功: $TAG"
|
||
else
|
||
log "部署失败($TAG),保留旧版本,等待下次轮询重试或人工介入"
|
||
exit 1
|
||
fi
|