refactor(server): 拆分 cmd/mini 与 cmd/web 两个部署入口
小程序接口(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>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
28309e0257
commit
12fa2b0d19
@@ -0,0 +1,137 @@
|
||||
# kepo.net 商业版 Web 拉取式部署配置(一次性)
|
||||
|
||||
> 安全原则同 [PULL_DEPLOY.md](./PULL_DEPLOY.md):CI 只构建、不部署;任何 AI 与流水线都不允许 SSH 到服务器执行命令。
|
||||
> 部署由 kepo 服务器本地定时任务轮询 Gitea Release 自动完成。
|
||||
> 本文档所有操作均由管理员本人在服务器终端执行,不依赖 AI 远程执行。
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
push main → Gitea Actions 构建 cmd/web + web-commercial 前端 → web-deploy.tar.gz 发布为 web-v* Release
|
||||
↓
|
||||
kepo 服务器 cron(每分钟)运行 deploy-watch.sh(TAG_PREFIX=web-v)→ 发现新 release → HTTPS 拉取
|
||||
↓
|
||||
deploy-web.sh 原子切换 + systemctl restart lunar-web + 健康检查(失败自动回滚)
|
||||
↓
|
||||
Cloudflare (HTTPS 终结) → kepo 源站 :8080 lunar-web systemd 服务
|
||||
```
|
||||
|
||||
## 与小程序接口部署的差异
|
||||
|
||||
| | lunar.neatcn.com | kepo.net |
|
||||
|---|---|---|
|
||||
| 二进制 | `cmd/mini` | `cmd/web` |
|
||||
| Release 前缀 | `server-v*` | `web-v*` |
|
||||
| 部署包 | `deploy.tar.gz` | `web-deploy.tar.gz` |
|
||||
| 运行方式 | Docker 容器 | systemd 服务 `lunar-web` |
|
||||
| 部署目录 | `/opt/lunar/production` | `/opt/lunar-web/production` |
|
||||
|
||||
## 一次性安装步骤(按序执行)
|
||||
|
||||
### 1. 准备目录与脚本
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/lunar-web/{production,scripts,backups}
|
||||
# 从仓库复制脚本(任选其一):
|
||||
# git clone 后 cp .gitea/scripts/{deploy-watch.sh,deploy-web.sh} /opt/lunar-web/scripts/
|
||||
# 或直接下载原始文件写入
|
||||
chmod +x /opt/lunar-web/scripts/*.sh
|
||||
```
|
||||
|
||||
### 2. 生产环境变量
|
||||
|
||||
写入 `/opt/lunar-web/production/.env`,权限 `600`:
|
||||
|
||||
```ini
|
||||
SERVER_ENV=production
|
||||
SERVER_PORT=8080
|
||||
PUBLIC_BASE_URL=https://kepo.net
|
||||
WEB_APP_URL=https://kepo.net
|
||||
CORS_ORIGINS=https://kepo.net,https://www.kepo.net
|
||||
|
||||
DB_TYPE=mysql
|
||||
DB_HOST=<数据库地址>
|
||||
DB_PORT=3306
|
||||
DB_USER=<用户名>
|
||||
DB_PASSWORD=<密码>
|
||||
DB_NAME=lunar_web # 建议与小程序库分开;若暂时共用,填 lunar
|
||||
|
||||
JWT_SECRET=<强随机值,至少32位>
|
||||
ADMIN_PASSWORD=<后台登录密码>
|
||||
|
||||
SMTP_HOST=smtp.qq.com
|
||||
SMTP_PORT=465
|
||||
SMTP_USERNAME=<QQ邮箱>
|
||||
SMTP_PASSWORD=<SMTP授权码>
|
||||
SMTP_FROM_EMAIL=z@kepo.net
|
||||
SMTP_TLS_MODE=implicit
|
||||
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
BINANCE_PAY_API_KEY=
|
||||
BINANCE_PAY_SECRET_KEY=
|
||||
BINANCE_PAY_WEBHOOK_PUBLIC_KEY=
|
||||
```
|
||||
|
||||
> kepo 侧不需要 `MINI_APP_ID` / `MINI_APP_SECRET` / `WECHAT_PAY_*`;
|
||||
> lunar 侧则不需要 SMTP / Stripe / Binance 变量。
|
||||
|
||||
### 3. systemd 服务
|
||||
|
||||
`/etc/systemd/system/lunar-web.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Lunar Commercial Web (kepo.net)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/lunar-web/production/current
|
||||
EnvironmentFile=/opt/lunar-web/production/.env
|
||||
ExecStart=/opt/lunar-web/production/current/bin/server
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable lunar-web
|
||||
# 首次启动前需要先手工放一版部署包,或等待第一次 Release 拉取
|
||||
```
|
||||
|
||||
### 4. 定时拉取任务
|
||||
|
||||
crontab(`crontab -e`)添加:
|
||||
|
||||
```cron
|
||||
* * * * * TAG_PREFIX=web-v ASSET_NAME=web-deploy.tar.gz \
|
||||
STATE_FILE=/opt/lunar-web/production/.last-deployed-release \
|
||||
DEPLOY_SCRIPT=/opt/lunar-web/scripts/deploy-web.sh \
|
||||
LOG_FILE=/opt/lunar-web/deploy-watch.log \
|
||||
/opt/lunar-web/scripts/deploy-watch.sh
|
||||
```
|
||||
|
||||
首次运行后会自动部署最新 `web-v*` Release。日志:`/opt/lunar-web/deploy-watch.log`。
|
||||
|
||||
### 5. Cloudflare 回源
|
||||
|
||||
kepo.net 已走 Cloudflare。确认回源指向本机 8080(或 Nginx 反代到 8080)。
|
||||
服务只监听 `127.0.0.1` 或由防火墙限制 8080 仅本机访问。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8080/api/version # 应返回 version/buildTime/commitSha
|
||||
curl -s -o /dev/null -w '%{http_code}' https://kepo.net/
|
||||
curl -s -o /dev/null -w '%{http_code}' https://kepo.net/admin/
|
||||
```
|
||||
|
||||
## 注意
|
||||
|
||||
- **数据库**:短期内两边共用 `lunar` 库(后台 `/admin` 在 kepo 侧统一查看订单与小程序数据)。后续如需分离,新建 `lunar_web` 库并改 `DB_NAME`。
|
||||
- **回滚**:`deploy-web.sh` 健康检查失败会自动恢复 `current.old`;也可手动 `systemctl restart lunar-web`。
|
||||
- **小程序端**:部署包结构不变(`deploy/bin/server`),doc79 上的 watcher 无需改动。
|
||||
@@ -13,6 +13,11 @@ 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}"
|
||||
@@ -28,7 +33,7 @@ if [ -n "${GITEA_DEPLOY_TOKEN:-}" ]; then
|
||||
AUTH_ARGS=(-H "Authorization: token ${GITEA_DEPLOY_TOKEN}")
|
||||
fi
|
||||
|
||||
# 查询最新 20 个 release,取 tag 前缀为 server-v 且带 deploy.tar.gz 附件的最新一个
|
||||
# 查询最新 20 个 release,取 tag 前缀匹配且带部署包附件的最新一个
|
||||
RELEASE_JSON=$(curl -fsS --connect-timeout 10 --max-time 30 \
|
||||
"${AUTH_ARGS[@]}" \
|
||||
"$GITEA_URL/api/v1/repos/$REPO/releases?limit=20" \
|
||||
@@ -37,16 +42,18 @@ RELEASE_JSON=$(curl -fsS --connect-timeout 10 --max-time 30 \
|
||||
exit 1
|
||||
}
|
||||
|
||||
PARSED=$(python3 - "$WORK_DIR/releases.json" <<'PY'
|
||||
import json, sys
|
||||
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("server-v"):
|
||||
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") == "deploy.tar.gz":
|
||||
if a.get("name") == asset_name:
|
||||
print(r["id"])
|
||||
print(r["tag_name"])
|
||||
print(a["id"])
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# 商业版 Web(kepo.net)自动部署脚本 - systemd 直接运行二进制
|
||||
# 设计原则:拉取式部署,只在服务器本地执行,CI 不通过 SSH 触发
|
||||
# 使用方法: ./deploy-web.sh <web-deploy.tar.gz>
|
||||
#
|
||||
# 目录约定:
|
||||
# /opt/lunar-web/production/current 当前运行版本(systemd WorkingDirectory)
|
||||
# /opt/lunar-web/production/.env 环境变量(systemd EnvironmentFile)
|
||||
# /opt/lunar-web/backups 历史版本备份
|
||||
#
|
||||
# 可通过环境变量覆盖:SERVICE_NAME / DEPLOY_DIR / BACKUP_DIR / HEALTH_URL
|
||||
|
||||
set -e
|
||||
|
||||
DEPLOY_PACKAGE="$1"
|
||||
SERVICE_NAME="${SERVICE_NAME:-lunar-web}"
|
||||
DEPLOY_DIR="${DEPLOY_DIR:-/opt/lunar-web/production}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/opt/lunar-web/backups}"
|
||||
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8080/api/version}"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 回滚:恢复 current.old 并重启服务
|
||||
rollback() {
|
||||
if [ -d "$DEPLOY_DIR/current.old" ]; then
|
||||
rm -rf "$DEPLOY_DIR/current"
|
||||
mv "$DEPLOY_DIR/current.old" "$DEPLOY_DIR/current"
|
||||
systemctl restart "$SERVICE_NAME" || log_error "回滚后服务重启失败,需人工介入"
|
||||
log_warn "已回滚到上一版本"
|
||||
else
|
||||
log_error "无可回滚版本,需人工介入"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ ! -f "$DEPLOY_PACKAGE" ]; then
|
||||
log_error "部署包不存在: $DEPLOY_PACKAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "开始自动部署(商业版 Web)..."
|
||||
log_info "部署包: $DEPLOY_PACKAGE"
|
||||
log_info "systemd 服务: $SERVICE_NAME"
|
||||
log_info "部署目录: $DEPLOY_DIR"
|
||||
|
||||
mkdir -p "$DEPLOY_DIR" "$BACKUP_DIR"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/backup_${TIMESTAMP}.tar.gz"
|
||||
if [ -d "$DEPLOY_DIR/current" ]; then
|
||||
log_info "备份当前版本到: $BACKUP_FILE"
|
||||
tar -czf "$BACKUP_FILE" -C "$DEPLOY_DIR" current/ || log_warn "备份失败,继续部署"
|
||||
fi
|
||||
|
||||
# 解压到暂存目录(不碰 current,避免部署空窗)
|
||||
log_info "解压部署包..."
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
tar -xzf "$DEPLOY_PACKAGE" -C "$TEMP_DIR"
|
||||
|
||||
if [ ! -d "$TEMP_DIR/deploy" ]; then
|
||||
log_error "部署包结构错误:缺少 deploy 目录"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$TEMP_DIR/deploy/bin/server" ]; then
|
||||
log_error "部署包缺少 bin/server 二进制"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$TEMP_DIR/deploy/frontend/index.html" ]; then
|
||||
log_error "部署包缺少 frontend/index.html(商业前端未构建?)"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "$TEMP_DIR/deploy/bin/server"
|
||||
|
||||
# 目录级交换,接近原子
|
||||
log_info "部署新版本..."
|
||||
mv "$TEMP_DIR/deploy" "$DEPLOY_DIR/current.new"
|
||||
rm -rf "$DEPLOY_DIR/current.old"
|
||||
if [ -d "$DEPLOY_DIR/current" ]; then
|
||||
mv "$DEPLOY_DIR/current" "$DEPLOY_DIR/current.old"
|
||||
fi
|
||||
mv "$DEPLOY_DIR/current.new" "$DEPLOY_DIR/current"
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# 重启 systemd 服务
|
||||
log_info "重启服务: $SERVICE_NAME"
|
||||
if systemctl restart "$SERVICE_NAME"; then
|
||||
log_info "服务重启成功"
|
||||
else
|
||||
log_error "服务重启失败,回滚到上一版本"
|
||||
rollback
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 健康检查
|
||||
log_info "执行健康检查: $HEALTH_URL"
|
||||
HEALTHY=0
|
||||
for i in $(seq 1 10); do
|
||||
sleep 2
|
||||
if curl -fsS --max-time 5 "$HEALTH_URL" > /dev/null 2>&1; then
|
||||
HEALTHY=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$HEALTHY" = "1" ]; then
|
||||
log_info "健康检查通过"
|
||||
curl -fsS --max-time 5 "$HEALTH_URL" && echo ""
|
||||
rm -rf "$DEPLOY_DIR/current.old"
|
||||
else
|
||||
log_error "健康检查失败,回滚到上一版本"
|
||||
journalctl -u "$SERVICE_NAME" --no-pager -n 50 || true
|
||||
rollback
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 清理旧备份(保留最近 5 个)
|
||||
ls -t "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
|
||||
|
||||
log_info "部署完成!"
|
||||
log_info "备份文件: $BACKUP_FILE"
|
||||
log_info "部署目录: $DEPLOY_DIR/current"
|
||||
@@ -85,11 +85,13 @@ jobs:
|
||||
echo "Building server version: $VERSION"
|
||||
|
||||
# ldflags 注入版本信息;目标平台为 doc79(linux/amd64)
|
||||
# 本流水线构建的是小程序接口二进制(cmd/mini);
|
||||
# 商业版 Web(cmd/web)由 web-deploy.yml 单独构建发布
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 "$LUNAR_GO" build -ldflags "\
|
||||
-X main.Version=$VERSION \
|
||||
-X main.BuildTime=$BUILD_TIME \
|
||||
-X main.CommitSha=$COMMIT_SHORT" \
|
||||
-o bin/server cmd/main.go
|
||||
-o bin/server ./cmd/mini
|
||||
|
||||
# 版本元数据随包分发,便于服务器侧追溯
|
||||
cat > bin/version.env <<EOF
|
||||
@@ -107,10 +109,8 @@ jobs:
|
||||
cp -r bin/ deploy/
|
||||
cp -r migrations/ deploy/
|
||||
cp -r scripts/ deploy/
|
||||
# 管理后台静态资源必须随包发布(服务以相对路径 ./web 提供)
|
||||
if [ -d "web" ]; then
|
||||
cp -r web/ deploy/web/
|
||||
fi
|
||||
# 管理后台已迁移到商业版 Web 服务(cmd/web),
|
||||
# 小程序接口二进制不再注册 /admin,无需随包发布 web/ 静态资源
|
||||
tar -czf deploy.tar.gz deploy/
|
||||
sha256sum deploy.tar.gz
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
name: Build and Publish Commercial Web
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "server/**"
|
||||
- "web/**"
|
||||
- "web-commercial/**"
|
||||
- ".gitea/workflows/web-deploy.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# 标准标签:hk / bt-runner 均带 ubuntu-latest,任一空闲 runner 接单
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 精简 runner(Alpine)无 node,无法跑 action-checkout;直接用 git 浅克隆
|
||||
- name: Checkout
|
||||
env:
|
||||
REPO_URL: ${{ gitea.server_url }}/${{ gitea.repository }}
|
||||
REF_NAME: ${{ gitea.ref_name }}
|
||||
run: |
|
||||
git init -q .
|
||||
git remote add origin "$REPO_URL"
|
||||
git fetch -q --depth 20 origin "$REF_NAME"
|
||||
git checkout -q FETCH_HEAD
|
||||
git log --oneline -3
|
||||
|
||||
- name: Ensure curl available
|
||||
run: |
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
if command -v apk >/dev/null 2>&1; then apk add --no-cache curl
|
||||
elif command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq curl
|
||||
elif command -v dnf >/dev/null 2>&1; then dnf install -y -q curl
|
||||
else echo "无法安装 curl"; exit 1; fi
|
||||
fi
|
||||
curl --version | head -1
|
||||
|
||||
- name: Prepare Go toolchain
|
||||
run: |
|
||||
TOOLDIR="$RUNNER_WORKSPACE/.lunar-toolchain"
|
||||
mkdir -p "$TOOLDIR"
|
||||
if [ -x "$TOOLDIR/go/bin/go" ]; then
|
||||
echo "复用缓存 Go 工具链"
|
||||
else
|
||||
wget -q --tries=2 --timeout=60 -O "$TOOLDIR/go.tgz" \
|
||||
"https://go.dev/dl/go1.22.10.linux-amd64.tar.gz" \
|
||||
|| wget -q --tries=2 --timeout=60 -O "$TOOLDIR/go.tgz" \
|
||||
"https://mirrors.aliyun.com/golang/go1.22.10.linux-amd64.tar.gz"
|
||||
rm -rf "$TOOLDIR/go"
|
||||
tar -C "$TOOLDIR" -xzf "$TOOLDIR/go.tgz"
|
||||
rm -f "$TOOLDIR/go.tgz"
|
||||
fi
|
||||
"$TOOLDIR/go/bin/go" version
|
||||
echo "LUNAR_GO=$TOOLDIR/go/bin/go" >> "$GITEA_ENV"
|
||||
|
||||
- name: Prepare Node toolchain
|
||||
run: |
|
||||
TOOLDIR="$RUNNER_WORKSPACE/.lunar-toolchain"
|
||||
mkdir -p "$TOOLDIR"
|
||||
if [ -x "$TOOLDIR/node/bin/node" ] && "$TOOLDIR/node/bin/node" --version >/dev/null 2>&1; then
|
||||
echo "复用缓存 Node 工具链"
|
||||
else
|
||||
rm -rf "$TOOLDIR/node" "$TOOLDIR/node.tgz"
|
||||
mkdir -p "$TOOLDIR"
|
||||
if ldd --version 2>&1 | grep -qi musl || [ ! -e /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 ]; then
|
||||
NODE_DIST="node-v22.12.0-linux-x64-musl"
|
||||
NODE_SHA="8d96de0293fc3978d09e2a3fee68c66bca4efcca90f52105e6f7463636e97308"
|
||||
URLS="https://registry.npmmirror.com/-/binary/node-unofficial-builds/v22.12.0/$NODE_DIST.tar.gz
|
||||
https://unofficial-builds.nodejs.org/download/release/v22.12.0/$NODE_DIST.tar.gz"
|
||||
else
|
||||
NODE_DIST="node-v22.12.0-linux-x64"
|
||||
NODE_SHA="e05a4d65232ae2b27b3d77da2e368522fb46b923335b8e0d5f77624c32484044"
|
||||
URLS="https://nodejs.org/dist/v22.12.0/$NODE_DIST.tar.gz
|
||||
https://mirrors.aliyun.com/nodejs-release/v22.12.0/$NODE_DIST.tar.gz"
|
||||
fi
|
||||
DL_OK=""
|
||||
for url in $URLS; do
|
||||
echo "尝试下载: $url"
|
||||
if wget -t 2 -T 120 -q -O "$TOOLDIR/node.tgz" "$url"; then
|
||||
if [ "$(sha256sum "$TOOLDIR/node.tgz" | cut -d' ' -f1)" = "$NODE_SHA" ]; then
|
||||
echo "下载成功且校验通过"
|
||||
DL_OK=1
|
||||
break
|
||||
fi
|
||||
echo "sha256 校验失败,尝试下一源"
|
||||
else
|
||||
echo "wget 下载失败,尝试下一源"
|
||||
fi
|
||||
rm -f "$TOOLDIR/node.tgz"
|
||||
done
|
||||
test -n "$DL_OK" || { echo "::error::所有下载源均失败或校验不通过"; exit 1; }
|
||||
tar -C "$TOOLDIR" -xzf "$TOOLDIR/node.tgz"
|
||||
mv "$TOOLDIR/$NODE_DIST" "$TOOLDIR/node"
|
||||
rm -f "$TOOLDIR/node.tgz"
|
||||
if [ "$NODE_DIST" = "node-v22.12.0-linux-x64-musl" ] && [ ! -e /usr/lib/libstdc++.so.6 ] && [ ! -e /lib/libstdc++.so.6 ]; then
|
||||
if command -v apk >/dev/null 2>&1; then apk add --no-cache libstdc++ libgcc || echo "::warning::libstdc++/libgcc 安装失败"; fi
|
||||
fi
|
||||
fi
|
||||
if ! "$TOOLDIR/node/bin/node" --version; then
|
||||
echo "::error::Node 二进制无法执行"
|
||||
rm -rf "$TOOLDIR/node"
|
||||
exit 1
|
||||
fi
|
||||
echo "PATH=$TOOLDIR/node/bin:$PATH" >> "$GITEA_ENV"
|
||||
|
||||
- name: Install pnpm
|
||||
run: |
|
||||
# pnpm 随工具链缓存,避免每次重复下载
|
||||
TOOLDIR="$RUNNER_WORKSPACE/.lunar-toolchain"
|
||||
if [ ! -x "$TOOLDIR/pnpm/bin/pnpm" ]; then
|
||||
npm install -g --prefix "$TOOLDIR/pnpm" pnpm@9
|
||||
fi
|
||||
echo "PATH=$TOOLDIR/pnpm/bin:$PATH" >> "$GITEA_ENV"
|
||||
pnpm --version
|
||||
|
||||
- name: Run Go tests
|
||||
working-directory: server
|
||||
run: |
|
||||
export HOME="$RUNNER_WORKSPACE"
|
||||
"$LUNAR_GO" mod download
|
||||
"$LUNAR_GO" test ./...
|
||||
|
||||
- name: Build commercial frontend
|
||||
env:
|
||||
CI: "true"
|
||||
run: |
|
||||
export HOME="$RUNNER_WORKSPACE"
|
||||
# web/ 工作区:装依赖并构建 @lunar/core(web-commercial 的 file: 依赖)
|
||||
cd web
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm --filter @lunar/core build
|
||||
cd ../web-commercial
|
||||
pnpm install --frozen-lockfile
|
||||
npx tsc -b
|
||||
npx vite build
|
||||
test -f dist/index.html
|
||||
|
||||
- name: Build web server binary
|
||||
working-directory: server
|
||||
env:
|
||||
GITEA_SHA: ${{ gitea.sha }}
|
||||
GITEA_RUN_NUMBER: ${{ gitea.run_number }}
|
||||
run: |
|
||||
export HOME="$RUNNER_WORKSPACE"
|
||||
mkdir -p bin
|
||||
|
||||
VERSION="1.1.${GITEA_RUN_NUMBER}"
|
||||
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
COMMIT_SHORT="${GITEA_SHA:0:7}"
|
||||
|
||||
echo "Building web server version: $VERSION"
|
||||
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 "$LUNAR_GO" build -ldflags "\
|
||||
-X main.Version=$VERSION \
|
||||
-X main.BuildTime=$BUILD_TIME \
|
||||
-X main.CommitSha=$COMMIT_SHORT" \
|
||||
-o bin/server ./cmd/web
|
||||
|
||||
cat > bin/version.env <<EOF
|
||||
APP_VERSION=$VERSION
|
||||
APP_BUILD_TIME=$BUILD_TIME
|
||||
APP_COMMIT_SHA=$COMMIT_SHORT
|
||||
EOF
|
||||
cat bin/version.env
|
||||
|
||||
- name: Create deployment package
|
||||
working-directory: server
|
||||
run: |
|
||||
mkdir -p deploy
|
||||
cp -r bin/ deploy/
|
||||
cp -r migrations/ deploy/
|
||||
cp -r scripts/ deploy/
|
||||
# 商业前端产物:web 二进制以相对路径 ./frontend 提供
|
||||
cp -r ../web-commercial/dist deploy/frontend/
|
||||
# 管理后台静态资源与模板(服务以相对路径 ./web 提供)
|
||||
cp -r web/ deploy/web/
|
||||
tar -czf web-deploy.tar.gz deploy/
|
||||
sha256sum web-deploy.tar.gz
|
||||
|
||||
- name: Publish release with deployment package
|
||||
env:
|
||||
GITEA_SHA: ${{ gitea.sha }}
|
||||
GITEA_RUN_NUMBER: ${{ gitea.run_number }}
|
||||
LUNAR_PUBLISH_TOKEN: ${{ secrets.LUNAR_PUBLISH_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
VERSION="1.1.${GITEA_RUN_NUMBER}"
|
||||
TAG="web-v${VERSION}"
|
||||
API="https://gitea.neatcn.com/api/v1/repos/gouki/lunar-mini"
|
||||
TOKEN="${LUNAR_PUBLISH_TOKEN:-$GITEA_TOKEN}"
|
||||
test -n "$TOKEN"
|
||||
AUTH="Authorization: token $TOKEN"
|
||||
|
||||
EXISTING=$(curl -sS -H "$AUTH" "$API/releases/tags/$TAG" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
curl -sS -X DELETE -H "$AUTH" "$API/releases/$EXISTING" -o /dev/null -w "删除旧 release: HTTP %{http_code}\n"
|
||||
fi
|
||||
|
||||
CREATE_RESP=$(curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"web $VERSION\",\"target_commitish\":\"main\",\"body\":\"CI 自动构建,commit ${GITEA_SHA:0:7}。kepo 服务器由 deploy-watch.sh(TAG_PREFIX=web-v)拉取本附件完成部署。\"}" \
|
||||
"$API/releases")
|
||||
RELEASE_ID=$(printf '%s' "$CREATE_RESP" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
echo "创建 release 失败,API 响应: $CREATE_RESP"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release created: id=$RELEASE_ID tag=$TAG"
|
||||
|
||||
curl -sS -X POST -H "$AUTH" \
|
||||
-F "attachment=@server/web-deploy.tar.gz" \
|
||||
"$API/releases/$RELEASE_ID/assets" \
|
||||
-o /dev/null -w "附件上传: HTTP %{http_code}\n"
|
||||
echo "发布地址: https://gitea.neatcn.com/gouki/lunar-mini/releases/tag/$TAG"
|
||||
|
||||
- name: Cleanup old web releases (keep latest 10)
|
||||
env:
|
||||
LUNAR_PUBLISH_TOKEN: ${{ secrets.LUNAR_PUBLISH_TOKEN }}
|
||||
run: |
|
||||
API="https://gitea.neatcn.com/api/v1/repos/gouki/lunar-mini"
|
||||
TOKEN="${LUNAR_PUBLISH_TOKEN:-$GITEA_TOKEN}"
|
||||
AUTH="Authorization: token $TOKEN"
|
||||
KEEP=10
|
||||
|
||||
PAIRS=$(curl -sS -H "$AUTH" "$API/releases?limit=50" \
|
||||
| grep -oE '"id":[0-9]+,"tag_name":"web-v[^"]*"' || true)
|
||||
TOTAL=$(printf '%s\n' "$PAIRS" | grep -c '"id"' || true)
|
||||
if [ "$TOTAL" -le "$KEEP" ]; then
|
||||
echo "当前 $TOTAL 个发布版本,未超过 $KEEP,无需清理"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "$PAIRS" | tail -n +$((KEEP + 1)) | while IFS= read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
RID=$(printf '%s' "$line" | grep -oE '"id":[0-9]+' | cut -d: -f2)
|
||||
RTAG=$(printf '%s' "$line" | sed 's/.*"tag_name":"\([^"]*\)".*/\1/')
|
||||
curl -sS -X DELETE -H "$AUTH" "$API/releases/$RID" -o /dev/null -w "删除旧发布 $RTAG: HTTP %{http_code}\n"
|
||||
curl -sS -X DELETE -H "$AUTH" "$API/tags/$RTAG" -o /dev/null -w "删除 tag $RTAG: HTTP %{http_code}\n"
|
||||
done
|
||||
|
||||
- name: Send success notification
|
||||
if: success()
|
||||
env:
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
GITEA_RUN_NUMBER: ${{ gitea.run_number }}
|
||||
run: |
|
||||
VERSION="1.1.${GITEA_RUN_NUMBER}"
|
||||
bash .gitea/scripts/notify.sh success \
|
||||
"商业版 Web 构建发布成功" \
|
||||
"版本: v${VERSION}\n产物: releases/tag/web-v${VERSION}\nkepo 服务器将自动拉取部署" || echo "通知发送失败,不影响主流程"
|
||||
|
||||
- name: Send failure notification
|
||||
if: failure()
|
||||
env:
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
run: |
|
||||
bash .gitea/scripts/notify.sh failure \
|
||||
"商业版 Web 构建失败" \
|
||||
"请检查 Actions 日志了解失败原因" || echo "通知发送失败"
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
rm -rf server/deploy/
|
||||
rm -f server/web-deploy.tar.gz
|
||||
Reference in New Issue
Block a user