小程序接口(lunar.neatcn.com)与商业版 Web(kepo.net)此前共用一个 二进制,导致小程序域名暴露商业后台与 Stripe Webhook,商业站点也 暴露了许愿树等小程序接口。现拆为两个入口: - cmd/mini:微信登录、生辰档案、许愿树、百科、版本号 - cmd/web:邮箱登录、commerce、报告/订单、/admin 后台、商业前端 静态托管与 SPA 回退 - internal/app 承载共用启动与路由注册;图片资源移至 internal/assets CI:server-deploy.yml 改构建 cmd/mini 并随包移除后台静态资源; 新增 web-deploy.yml 构建 cmd/web + web-commercial 前端,发布 web-v* Release;deploy-watch.sh 支持 TAG_PREFIX/ASSET_NAME 变量; 新增 deploy-web.sh(systemd 版)与 kepo 一次性配置文档 .gitea/docs/PULL_DEPLOY_WEB.md。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
106 lines
3.7 KiB
Bash
106 lines
3.7 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}"
|
||
# 同一脚本服务两种部署形态:
|
||
# 小程序接口(doc79):默认 server-v* + deploy.tar.gz
|
||
# 商业版 Web(kepo):TAG_PREFIX=web-v ASSET_NAME=web-deploy.tar.gz
|
||
TAG_PREFIX="${TAG_PREFIX:-server-v}"
|
||
ASSET_NAME="${ASSET_NAME:-deploy.tar.gz}"
|
||
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 前缀匹配且带部署包附件的最新一个
|
||
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=$(TAG_PREFIX="$TAG_PREFIX" ASSET_NAME="$ASSET_NAME" python3 - "$WORK_DIR/releases.json" <<'PY'
|
||
import json, os, sys
|
||
tag_prefix = os.environ["TAG_PREFIX"]
|
||
asset_name = os.environ["ASSET_NAME"]
|
||
releases = json.load(open(sys.argv[1]))
|
||
for r in releases:
|
||
if not r.get("tag_name", "").startswith(tag_prefix):
|
||
continue
|
||
if r.get("draft") or r.get("prerelease"):
|
||
continue
|
||
for a in r.get("assets", []):
|
||
if a.get("name") == asset_name:
|
||
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
|