Compare commits

...

5 Commits

Author SHA1 Message Date
wangjia f6e565ffdc ci(site): 官网构建/部署直接在 runner 跑,去掉嵌套 docker(修 DinD 挂载)
Deploy Site / deploy-site (push) Successful in 1m23s
job 容器内 $PWD(/workspace/...)在宿主不存在,docker run -v $PWD 挂载失败。
runner 镜像 catthehacker/ubuntu:act-latest 自带 node/npx,直接 bash 跑即可。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:34:12 +08:00
wangjia ba5f71b716 ci: runs-on nas → ubuntu-latest(匹配 nas runner 实际 label)
Deploy Site / deploy-site (push) Failing after 1m36s
nas 上的 forgejo-act-runner 注册 label 是 ubuntu-latest(非 nas),导致所有
runs-on: nas 的工作流无匹配 runner。改为 ubuntu-latest 对齐 runner 现状。
(备选:给 runner 加 nas 标签保约定,未采用。)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:33:27 +08:00
wangjia 7c26050cdb docs(spec): 官网部署改 Cloudflare Pages(架构变更说明)
Deploy Site / deploy-site (push) Has been cancelled
节点 :443 被 VPN 占用 + CF 免费套餐改回源端口需 Enterprise → 官网改 CF Pages 托管
(纯静态/全程 HTTPS/CSP 生效/无 :443 冲突),已上线 pangolin.yanmeiai.com。
deploy-site.sh 用 wrangler,需 CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:08:20 +08:00
wangjia 673914783d refactor(ci): 官网部署改用 Cloudflare Pages(替代 rsync 到 pangolin1)
节点 :443 被 sing-box(VPN 数据面)占用,CF 免费套餐回源只能打 :80/:443、改端口需
Enterprise —— 故官网改由 CF Pages 托管(项目 pangolin-site,自定义域 pangolin.yanmeiai.com),
纯静态、全程 HTTPS、_headers/CSP 自动生效、不落 VPS、彻底无 :443 冲突。
deploy-site.sh 改为 wrangler pages deploy(需 CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID
Gitea secret);deploy-site.yml deploy 步骤在 node:20 容器内跑 wrangler。lib-ssh 仍供服务端部署用。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 09:59:58 +08:00
wangjia 443d4e3a11 feat(ci): test.sh(go test / flutter test)
Add scripts/ci/test.sh <server|client>: server runs `go test ./...`
inside golang:1.25 (mirrors ci.yml's go-server job docker invocation +
gomod/gobuild caches + GOPROXY from _env.sh); client runs `flutter test`
inside ghcr.io/cirruslabs/flutter:stable (mirrors ci.yml's flutter-client
job's pub-cache mount + test dirs). Wired into deploy-server.yml's Test
step already added in Task 4. Registers the script in ci.yml's shellcheck
file list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-06 00:37:48 +08:00
7 changed files with 107 additions and 49 deletions
+12 -11
View File
@@ -21,7 +21,7 @@ jobs:
# ── Job 1: Lint (shellcheck) ───────────────────────────────────────────── # ── Job 1: Lint (shellcheck) ─────────────────────────────────────────────
lint: lint:
name: Lint — shellcheck name: Lint — shellcheck
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -53,12 +53,13 @@ jobs:
/mnt/scripts/ci/deploy-site.sh \ /mnt/scripts/ci/deploy-site.sh \
/mnt/scripts/ci/compile-backend.sh \ /mnt/scripts/ci/compile-backend.sh \
/mnt/scripts/ci/release-server.sh \ /mnt/scripts/ci/release-server.sh \
/mnt/scripts/ci/deploy-server.sh /mnt/scripts/ci/deploy-server.sh \
/mnt/scripts/ci/test.sh
# ── Job 2: OpenAPI Sync Check ──────────────────────────────────────────── # ── Job 2: OpenAPI Sync Check ────────────────────────────────────────────
openapi-check: openapi-check:
name: OpenAPI Sync Check name: OpenAPI Sync Check
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -75,7 +76,7 @@ jobs:
# ── Job 3: Redline Word Scan (脱敏) ────────────────────────────────────── # ── Job 3: Redline Word Scan (脱敏) ──────────────────────────────────────
redline-scan: redline-scan:
name: Redline Scan — 脱敏 (UI 文案) name: Redline Scan — 脱敏 (UI 文案)
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -86,7 +87,7 @@ jobs:
# ── Job 4: Flutter 客户端(分析 + 单测/组件测试)──────────────────────── # ── Job 4: Flutter 客户端(分析 + 单测/组件测试)────────────────────────
flutter-client: flutter-client:
name: Flutter — analyze + test name: Flutter — analyze + test
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -109,7 +110,7 @@ jobs:
# 规则与豁免见 ci/scan-portable-sql.sh 头注 + docs/dev-conventions.html 支柱 3。 # 规则与豁免见 ci/scan-portable-sql.sh 头注 + docs/dev-conventions.html 支柱 3。
portable-sql-scan: portable-sql-scan:
name: Portable SQL — 可移植性 (mysql/sqlite) name: Portable SQL — 可移植性 (mysql/sqlite)
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -122,7 +123,7 @@ jobs:
# (改了 design/colors_and_type.css 没重生成,或手改了生成物)。 # (改了 design/colors_and_type.css 没重生成,或手改了生成物)。
codegen-drift: codegen-drift:
name: Codegen Drift — token 生成物未漂移 name: Codegen Drift — token 生成物未漂移
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -139,7 +140,7 @@ jobs:
# -tags integration(需 docker 起 mysql/redis),见 go-integration job。 # -tags integration(需 docker 起 mysql/redis),见 go-integration job。
go-server: go-server:
name: Go — build + test name: Go — build + test
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -165,7 +166,7 @@ jobs:
# 详见 scripts/e2e-smoke.sh + server/test/e2e/。 # 详见 scripts/e2e-smoke.sh + server/test/e2e/。
e2e-smoke: e2e-smoke:
name: E2E Smoke — L4 进程级端到端 name: E2E Smoke — L4 进程级端到端
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -189,7 +190,7 @@ jobs:
# · -p 1 串行:一次只起一个 mysql 容器,避免并发把 Docker Desktop 压垮/端口资源争用。 # · -p 1 串行:一次只起一个 mysql 容器,避免并发把 Docker Desktop 压垮/端口资源争用。
go-integration: go-integration:
name: Go — integration (mysql/redis testcontainers) name: Go — integration (mysql/redis testcontainers)
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -204,7 +205,7 @@ jobs:
# tablet/desktop-stats golden 与 stats-overhaul 工作区耦合,待其合并后并入本 job。 # tablet/desktop-stats golden 与 stats-overhaul 工作区耦合,待其合并后并入本 job。
golden: golden:
name: Golden — 视觉回归 (components + auth) name: Golden — 视觉回归 (components + auth)
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
+1 -1
View File
@@ -12,7 +12,7 @@ concurrency:
jobs: jobs:
deploy-server: deploy-server:
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
+11 -9
View File
@@ -12,18 +12,20 @@ concurrency:
jobs: jobs:
deploy-site: deploy-site:
runs-on: nas runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Compile (Astro 官网, node:20 容器内构建) # runner 镜像 catthehacker/ubuntu:act-latest 自带 node/npx,直接跑;
run: | # 不用嵌套 docker run(job 容器内的 $PWD 在宿主上不存在,DinD 挂载会失败)。
docker run --rm -v "$PWD:/w" -w /w \ - name: Compile (Astro 官网)
-e SITE_URL=https://pangolin.yanmeiai.com \
node:20 bash scripts/ci/compile-site.sh
- name: Deploy → pangolin1
env: env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} SITE_URL: https://pangolin.yanmeiai.com
run: bash scripts/ci/compile-site.sh
- name: Deploy → Cloudflare Pages
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bash scripts/ci/deploy-site.sh run: bash scripts/ci/deploy-site.sh
+5 -3
View File
@@ -111,10 +111,12 @@ pangolin.yanmeiai.com)与多端产物。现状:仅 <code>ci.yml</code> 校
</ol> </ol>
</div> </div>
<h2>4. 官网部署</h2> <h2>4. 官网部署 —— Cloudflare Pages</h2>
<div class="card" style="border-left:3px solid var(--warn)">
<b>架构变更(2026-07-06 实施):</b>原计划 rsync 到 pangolin1 的 nginx。但节点 <code>:443</code> 被 sing-box(VPN 数据面)占用,而 CF 免费套餐 proxied 回源只能打 :80/:443、改回源端口需 Enterprise —— 同机同 IP 上官网 HTTPS 与 VPN 无法共存。<b>故官网改由 Cloudflare Pages 托管</b>:纯静态、全程 HTTPS、<code>_headers</code>/CSP 原生生效、不落 VPS,从根上无 :443 冲突,也不拖累 VPN 机器。<b>已上线</b> <code>https://pangolin.yanmeiai.com</code>
</div>
<p>Astro <code>npm ci && npm run build</code><code>SITE_URL=https://pangolin.yanmeiai.com</code>)→ <code>dist/</code> <p>Astro <code>npm ci && npm run build</code><code>SITE_URL=https://pangolin.yanmeiai.com</code>)→ <code>dist/</code>
rsync 到服务器 web 根 → nginx/Caddy 静态托管。<b>DNS</b>CF 加记录 <code>npx wrangler pages deploy</code> 发布到 Pages 项目 <b>pangolin-site</b>(自定义域 <code>pangolin.yanmeiai.com</code>,CNAME → <code>pangolin-site.pages.dev</code>,proxied)。需 secret <code>CLOUDFLARE_API_TOKEN</code>(带 Account&gt;Pages&gt;Edit+ <code>CLOUDFLARE_ACCOUNT_ID</code>deploy 步骤在 <code>node:20</code> 容器内跑 wrangler。灾备:产物仍纯静态,可另 rsync 到镜像。</p>
<code>pangolin.yanmeiai.com</code> → 服务器(首次部署时做,记 baize)。</p>
<h2>5. 下载链接闭环(30A</h2> <h2>5. 下载链接闭环(30A</h2>
<p><code>web/website/src/config/site.ts</code><code>downloads:{ android, macos, windows }</code>,值为 Gitea release <p><code>web/website/src/config/site.ts</code><code>downloads:{ android, macos, windows }</code>,值为 Gitea release
@@ -43,7 +43,7 @@ pangolin 现有 CI 仅 `.gitea/workflows/ci.yml`(nas,只校验无部署)+ `web/w
- `test.sh <server|client>` —— `go test` / `flutter test` - `test.sh <server|client>` —— `go test` / `flutter test`
- `notify.sh` —— 成功/失败 Telegram 通知(可选,复用节点监控 bot) - `notify.sh` —— 成功/失败 Telegram 通知(可选,复用节点监控 bot)
- `compile-site.sh` / `compile-backend.sh` / `compile-android.sh` / `compile-macos.sh` / `compile-windows.sh` - `compile-site.sh` / `compile-backend.sh` / `compile-android.sh` / `compile-macos.sh` / `compile-windows.sh`
- `deploy-site.sh` / `deploy-server.sh`(ssh pangolin1) - `deploy-site.sh`(wrangler → CF Pages)/ `deploy-server.sh`(ssh pangolin1,复用 lib-ssh)
- `release-<x>.sh` —— 建 release + 挂产物 - `release-<x>.sh` —— 建 release + 挂产物
> 每个 `compile-*` 内部封装该端已验证的构建命令(如 Android 走 > 每个 `compile-*` 内部封装该端已验证的构建命令(如 Android 走
@@ -74,11 +74,18 @@ pangolin 现有 CI 仅 `.gitea/workflows/ci.yml`(nas,只校验无部署)+ `web/w
5. `install` 新二进制到 `/usr/local/bin`(旧的备份为 `.bak-<tag>`) 5. `install` 新二进制到 `/usr/local/bin`(旧的备份为 `.bak-<tag>`)
6. `systemctl start pangolin-server` + `/healthz` 健康检查;agent 随连接自恢复 6. `systemctl start pangolin-server` + `/healthz` 健康检查;agent 随连接自恢复
### 4.4 官网部署(deploy-site.sh) ### 4.4 官网部署(deploy-site.sh)—— Cloudflare Pages
Astro `npm ci && npm run build`(`SITE_URL=https://pangolin.yanmeiai.com`)→ `dist/` rsync 到 > **架构变更(2026-07-06 实施):** 原计划 rsync 到 pangolin1 的 nginx。但节点 :443 被 sing-box
服务器 web 根 → 由 nginx/Caddy 静态托管 `pangolin.yanmeiai.com`。**DNS**:CF 加 A/CNAME > (VPN 数据面)占用,而 CF 免费套餐 proxied 回源只能打 :80/:443、改回源端口需 Enterprise ——
`pangolin.yanmeiai.com` → 服务器(首次部署时做,记 baize)。 > 无法在同机同 IP 上让官网 HTTPS 与 VPN 共存。**故官网改由 Cloudflare Pages 托管**:纯静态、
> 全程 HTTPS、`_headers`/CSP 原生生效、不落 VPS,从根上无 :443 冲突,也不拖累 VPN 机器。
Astro `npm ci && npm run build`(`SITE_URL=https://pangolin.yanmeiai.com`)→ `dist/`
`npx wrangler pages deploy` 发布到 CF Pages 项目 **`pangolin-site`**(自定义域
`pangolin.yanmeiai.com`,CNAME → `pangolin-site.pages.dev`,proxied)。
需 secret:`CLOUDFLARE_API_TOKEN`(带 Account>Pages>Edit)+ `CLOUDFLARE_ACCOUNT_ID`(账户级)。
deploy 步骤在 `node:20` 容器内跑 wrangler。**灾备**:构建产物仍是纯静态,可另 rsync 到任意镜像。
## 5. 下载链接闭环(30A) ## 5. 下载链接闭环(30A)
+17 -20
View File
@@ -1,32 +1,29 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# deploy-site.sh — rsync the built Astro 官网 (web/website/dist/) to the # deploy-site.sh — 部署构建好的 Astro 官网 (web/website/dist/) 到 Cloudflare Pages。
# pangolin1 VPS web root (/var/www/pangolin-site). Deploy user is root — the #
# CI deploy key (secrets.DEPLOY_SSH_KEY) is authorized for root on the host. # 官网托管在 CF Pages(项目 pangolin-site,自定义域 pangolin.yanmeiai.com),纯静态、
# Static files only; nginx/caddy serves them directly, no service restart # 全程 HTTPS、CSP(_headers)自动生效,不落在 VPS 上 —— 故与节点 :443(sing-box)无冲突。
# needed. Assumes it is invoked from the repo root, after compile-site.sh has #
# produced web/website/dist/. # 需环境变量:
# CLOUDFLARE_API_TOKEN 带 Account > Cloudflare Pages > Edit 权限的 CF token
# CLOUDFLARE_ACCOUNT_ID CF 账户 ID
# 由 compile-site.sh 先产出 web/website/dist/;从 repo 根调用。
set -euo pipefail set -euo pipefail
# shellcheck source=scripts/ci/lib-ssh.sh
. scripts/ci/lib-ssh.sh
# setup_ssh registers the EXIT cleanup trap itself (before writing the key),
# so a mid-setup failure still cleans up — see lib-ssh.sh.
setup_ssh
# Guard against deploying a missing/empty build: `rsync --delete` against an
# empty source would wipe the live site.
if [ ! -d web/website/dist ]; then if [ ! -d web/website/dist ]; then
echo "==> deploy-site: web/website/dist/ does not exist — refusing to deploy" >&2 echo "==> deploy-site: web/website/dist/ 不存在 — 拒绝部署" >&2
exit 1 exit 1
fi fi
if ! find web/website/dist -mindepth 1 -print -quit | grep -q .; then if ! find web/website/dist -mindepth 1 -print -quit | grep -q .; then
echo "==> deploy-site: web/website/dist/ is empty — refusing to deploy" >&2 echo "==> deploy-site: web/website/dist/ 为空 — 拒绝部署" >&2
exit 1 exit 1
fi fi
DEPLOY_TARGET="root@${DEPLOY_HOST}:/var/www/pangolin-site/" : "${CLOUDFLARE_API_TOKEN:?deploy-site: CLOUDFLARE_API_TOKEN 未设(需带 Pages:Edit)}"
echo "==> deploy-site: rsync web/website/dist/ -> ${DEPLOY_TARGET}" : "${CLOUDFLARE_ACCOUNT_ID:?deploy-site: CLOUDFLARE_ACCOUNT_ID 未设}"
rsync -az --delete -e "${RSYNC_SSH}" web/website/dist/ "${DEPLOY_TARGET}"
echo "==> deploy-site: wrangler pages deploy → project pangolin-site (branch main)"
npx --yes wrangler@4 pages deploy web/website/dist \
--project-name=pangolin-site --branch=main --commit-dirty=true
echo "==> deploy-site: done" echo "==> deploy-site: done"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# test.sh <server|client> — run the test suite for one side of the repo.
# Invoked directly on the nas runner (see deploy-server.yml's "Test" step),
# so this script itself owns the docker invocation — Go/Flutter are not on
# the host.
#
# server: `go test ./...` inside golang:1.25, mirroring the go-server job's
# docker invocation in .gitea/workflows/ci.yml (mounts + gomod/gobuild
# caches), plus GOPROXY from _env.sh so it doesn't hit proxy.golang.org.
# client: `flutter test` inside ghcr.io/cirruslabs/flutter:stable, mirroring
# the flutter-client job's docker invocation in .gitea/workflows/ci.yml
# (pub-cache volume, same test dirs); analyze/coverage stay in that job.
#
# Run from the repo root (relative paths below assume this).
set -euo pipefail
# shellcheck source=scripts/ci/_env.sh
. scripts/ci/_env.sh
TARGET="${1:-}"
case "$TARGET" in
server)
mkdir -p "$HOME/.cache/pangolin-ci/gomod" "$HOME/.cache/pangolin-ci/gobuild"
echo "==> test: go test ./... (golang:1.25 容器)"
docker run --rm \
-v "$PWD/server:/app" -w /app \
-v "$HOME/.cache/pangolin-ci/gomod:/go/pkg/mod" \
-v "$HOME/.cache/pangolin-ci/gobuild:/root/.cache/go-build" \
-e GOPROXY="$GOPROXY" \
golang:1.25 \
go test ./...
;;
client)
mkdir -p "$HOME/.cache/pangolin-ci/pubcache"
echo "==> test: flutter test (ghcr.io/cirruslabs/flutter:stable 容器)"
docker run --rm \
-v "$PWD/client:/app" -w /app \
-v "$HOME/.cache/pangolin-ci/pubcache:/root/.pub-cache" \
ghcr.io/cirruslabs/flutter:stable \
bash -c "flutter pub get && flutter test test/unit test/widget test/contract"
;;
*)
echo "usage: scripts/ci/test.sh <server|client>" >&2
exit 1
;;
esac
echo "==> test: ${TARGET} 通过"