Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c26050cdb | |||
| 673914783d | |||
| 443d4e3a11 | |||
| 0825170044 | |||
| 4fb3fe3fee | |||
| 91d1355721 | |||
| 9bac5c8dcb |
@@ -47,7 +47,14 @@ jobs:
|
|||||||
-S warning \
|
-S warning \
|
||||||
/mnt/scripts/ci/_env.sh \
|
/mnt/scripts/ci/_env.sh \
|
||||||
/mnt/scripts/ci/lib-forgejo.sh \
|
/mnt/scripts/ci/lib-forgejo.sh \
|
||||||
/mnt/scripts/ci/notify.sh
|
/mnt/scripts/ci/notify.sh \
|
||||||
|
/mnt/scripts/ci/lib-ssh.sh \
|
||||||
|
/mnt/scripts/ci/compile-site.sh \
|
||||||
|
/mnt/scripts/ci/deploy-site.sh \
|
||||||
|
/mnt/scripts/ci/compile-backend.sh \
|
||||||
|
/mnt/scripts/ci/release-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:
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: Deploy Server
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'server-v[0-9]*.[0-9]*.[0-9]*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-server
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-server:
|
||||||
|
runs-on: nas
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Compile (Go 控制面, golang:1.25 容器内构建)
|
||||||
|
run: |
|
||||||
|
mkdir -p "$HOME/.cache/pangolin-ci/gomod" "$HOME/.cache/pangolin-ci/gobuild"
|
||||||
|
docker run --rm -v "$PWD:/w" -w /w \
|
||||||
|
-v "$HOME/.cache/pangolin-ci/gomod:/go/pkg/mod" \
|
||||||
|
-v "$HOME/.cache/pangolin-ci/gobuild:/root/.cache/go-build" \
|
||||||
|
golang:1.25 bash scripts/ci/compile-backend.sh
|
||||||
|
|
||||||
|
- name: Test (go test)
|
||||||
|
run: bash scripts/ci/test.sh server
|
||||||
|
|
||||||
|
- name: Release → Forgejo
|
||||||
|
env:
|
||||||
|
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
|
||||||
|
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
|
||||||
|
TAG: ${{ gitea.ref_name }}
|
||||||
|
run: bash scripts/ci/release-server.sh "$TAG"
|
||||||
|
|
||||||
|
- name: Deploy → pangolin1
|
||||||
|
env:
|
||||||
|
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
TAG: ${{ gitea.ref_name }}
|
||||||
|
run: bash scripts/ci/deploy-server.sh "$TAG"
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: Deploy Site
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'site-v[0-9]*.[0-9]*.[0-9]*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-site
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-site:
|
||||||
|
runs-on: nas
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Compile (Astro 官网, node:20 容器内构建)
|
||||||
|
run: |
|
||||||
|
docker run --rm -v "$PWD:/w" -w /w \
|
||||||
|
-e SITE_URL=https://pangolin.yanmeiai.com \
|
||||||
|
node:20 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: |
|
||||||
|
docker run --rm -v "$PWD:/w" -w /w \
|
||||||
|
-e CLOUDFLARE_API_TOKEN -e CLOUDFLARE_ACCOUNT_ID \
|
||||||
|
node:20 bash scripts/ci/deploy-site.sh
|
||||||
@@ -48,6 +48,7 @@ app/kernel/.build/
|
|||||||
# Go 编译产物(mock server 等)
|
# Go 编译产物(mock server 等)
|
||||||
server/mockserver
|
server/mockserver
|
||||||
server/pangolin-server
|
server/pangolin-server
|
||||||
|
server/out/
|
||||||
|
|
||||||
# Flutter / Dart 构建产物与本地配置
|
# Flutter / Dart 构建产物与本地配置
|
||||||
client/android/local.properties
|
client/android/local.properties
|
||||||
|
|||||||
@@ -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>Pages>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)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# compile-backend.sh — cross-compile the pangolin Go control-plane binaries
|
||||||
|
# (server / agent / migrate) for the pangolin1 deploy target. CGO disabled:
|
||||||
|
# modernc.org/sqlite is pure Go, no cgo toolchain needed on the runner.
|
||||||
|
# Output: server/out/{pangolin-server,pangolin-agent,pangolin-migrate}.
|
||||||
|
#
|
||||||
|
# Run inside a golang:1.25 container by .gitea/workflows/deploy-server.yml;
|
||||||
|
# this script itself just runs `go build` and assumes it is invoked from the
|
||||||
|
# repo root.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/_env.sh
|
||||||
|
. scripts/ci/_env.sh
|
||||||
|
|
||||||
|
cd server
|
||||||
|
mkdir -p out
|
||||||
|
|
||||||
|
echo "==> compile-backend: building pangolin-server"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/pangolin-server ./cmd/server
|
||||||
|
|
||||||
|
echo "==> compile-backend: building pangolin-agent"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/pangolin-agent ./cmd/agent
|
||||||
|
|
||||||
|
echo "==> compile-backend: building pangolin-migrate"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/pangolin-migrate ./cmd/migrate
|
||||||
|
|
||||||
|
echo "==> compile-backend: done — out/ contents:"
|
||||||
|
ls -lh out/
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# compile-site.sh — build the Astro 官网 (web/website) as a static site.
|
||||||
|
# Output: web/website/dist/. Canonical domain is injected via SITE_URL (see
|
||||||
|
# web/website/astro.config.mjs) — must match the deploy target host name so
|
||||||
|
# canonical URLs / sitemap resolve correctly.
|
||||||
|
#
|
||||||
|
# Run inside a node:20 container by .gitea/workflows/deploy-site.yml; this
|
||||||
|
# script itself just runs npm and assumes it is invoked from the repo root.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SITE_URL="${SITE_URL:-https://pangolin.yanmeiai.com}"
|
||||||
|
export SITE_URL
|
||||||
|
echo "==> compile-site: SITE_URL=${SITE_URL}"
|
||||||
|
|
||||||
|
cd web/website
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "==> compile-site: done — dist/ contents:"
|
||||||
|
ls -lh dist/
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy-server.sh <tag> — deploy the pangolin control-plane binaries
|
||||||
|
# (pangolin-server / pangolin-agent / pangolin-migrate) to pangolin1, in the
|
||||||
|
# exact manual sequence used for F3/F4: stop → wal checkpoint → backup db →
|
||||||
|
# migrate (rollback db + restart old binary on failure) → swap binaries →
|
||||||
|
# start → healthcheck. Assumes compile-backend.sh has already produced
|
||||||
|
# server/out/{pangolin-server,pangolin-agent,pangolin-migrate}.
|
||||||
|
#
|
||||||
|
# Usage: scripts/ci/deploy-server.sh <tag> (e.g. server-v1.2.3)
|
||||||
|
# Requires env: DEPLOY_SSH_KEY (see lib-ssh.sh).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/lib-ssh.sh
|
||||||
|
. scripts/ci/lib-ssh.sh
|
||||||
|
|
||||||
|
DB=/var/lib/pangolin/pangolin.db
|
||||||
|
BIN=/usr/local/bin
|
||||||
|
TAG="${1:?usage: deploy-server.sh <tag>}"
|
||||||
|
|
||||||
|
# Refuse anything that isn't a strict server-vX.Y.Z[-suffix] tag before it can
|
||||||
|
# reach the remote heredoc / backup paths below (command-injection guard).
|
||||||
|
# An anchored regex is used instead of a `case` glob: a trailing `*` in a
|
||||||
|
# case pattern matches ANY trailing characters (including shell metachars
|
||||||
|
# like `; rm -rf /`), which would defeat the point of this check.
|
||||||
|
if ! [[ "$TAG" =~ ^server-v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$ ]]; then
|
||||||
|
echo "deploy-server: refusing unexpected tag '$TAG'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# setup_ssh registers the EXIT cleanup trap itself (before writing the key),
|
||||||
|
# so a mid-setup failure still cleans up — see lib-ssh.sh. It exports
|
||||||
|
# SSH_KEY_FILE / DEPLOY_PORT / SSH_KNOWN_HOSTS_FILE / DEPLOY_HOST used below
|
||||||
|
# to build SCP (mirroring the SSH/RSYNC_SSH command-string convention).
|
||||||
|
setup_ssh
|
||||||
|
SCP="scp -i ${SSH_KEY_FILE} -P ${DEPLOY_PORT} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}"
|
||||||
|
|
||||||
|
echo "==> deploy-server: tag=${TAG} host=${DEPLOY_HOST}"
|
||||||
|
echo "==> deploy-server: uploading binaries to ${DEPLOY_HOST}:/tmp/"
|
||||||
|
$SCP server/out/pangolin-server server/out/pangolin-agent server/out/pangolin-migrate "root@${DEPLOY_HOST}:/tmp/"
|
||||||
|
|
||||||
|
$SSH "root@${DEPLOY_HOST}" "bash -s" <<REMOTE
|
||||||
|
set -euo pipefail
|
||||||
|
systemctl stop pangolin-server
|
||||||
|
runuser -u pangolin -- sqlite3 "$DB" 'PRAGMA wal_checkpoint(TRUNCATE);'
|
||||||
|
cp -p "$DB" "$DB.bak-pre-$TAG"
|
||||||
|
if ! runuser -u pangolin -- env DB_DRIVER=sqlite DB_DSN=$DB /tmp/pangolin-migrate up; then
|
||||||
|
echo "!! migrate 失败,回滚"; cp -p "$DB.bak-pre-$TAG" "$DB"; systemctl start pangolin-server; exit 1
|
||||||
|
fi
|
||||||
|
cp -p "$BIN/pangolin-server" "$BIN/pangolin-server.bak-$TAG" || true
|
||||||
|
install -m755 /tmp/pangolin-server "$BIN/pangolin-server"
|
||||||
|
install -m755 /tmp/pangolin-agent "$BIN/pangolin-agent"
|
||||||
|
install -m755 /tmp/pangolin-migrate "$BIN/pangolin-migrate"
|
||||||
|
systemctl start pangolin-server
|
||||||
|
systemctl is-active pangolin-server
|
||||||
|
REMOTE
|
||||||
|
|
||||||
|
curl -fsS -m 10 --retry 5 --retry-connrefused "http://${DEPLOY_HOST}:8080/healthz" >/dev/null && echo "healthz OK"
|
||||||
|
|
||||||
|
echo "==> deploy-server: done"
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy-site.sh — 部署构建好的 Astro 官网 (web/website/dist/) 到 Cloudflare Pages。
|
||||||
|
#
|
||||||
|
# 官网托管在 CF Pages(项目 pangolin-site,自定义域 pangolin.yanmeiai.com),纯静态、
|
||||||
|
# 全程 HTTPS、CSP(_headers)自动生效,不落在 VPS 上 —— 故与节点 :443(sing-box)无冲突。
|
||||||
|
#
|
||||||
|
# 需环境变量:
|
||||||
|
# CLOUDFLARE_API_TOKEN 带 Account > Cloudflare Pages > Edit 权限的 CF token
|
||||||
|
# CLOUDFLARE_ACCOUNT_ID CF 账户 ID
|
||||||
|
# 由 compile-site.sh 先产出 web/website/dist/;从 repo 根调用。
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ ! -d web/website/dist ]; then
|
||||||
|
echo "==> deploy-site: web/website/dist/ 不存在 — 拒绝部署" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! find web/website/dist -mindepth 1 -print -quit | grep -q .; then
|
||||||
|
echo "==> deploy-site: web/website/dist/ 为空 — 拒绝部署" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
: "${CLOUDFLARE_API_TOKEN:?deploy-site: CLOUDFLARE_API_TOKEN 未设(需带 Pages:Edit)}"
|
||||||
|
: "${CLOUDFLARE_ACCOUNT_ID:?deploy-site: CLOUDFLARE_ACCOUNT_ID 未设}"
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -59,18 +59,36 @@ forgejo_release_ensure() {
|
|||||||
rm -f "$get_body_file"
|
rm -f "$get_body_file"
|
||||||
|
|
||||||
echo "==> forgejo: creating release ${tag}"
|
echo "==> forgejo: creating release ${tag}"
|
||||||
local create_code_file create_body_file create_code
|
local create_code_file create_body_file create_code create_req_file
|
||||||
create_code_file="/tmp/forgejo_create_code.$$"
|
create_code_file="/tmp/forgejo_create_code.$$"
|
||||||
create_body_file="/tmp/forgejo_create_body.$$.json"
|
create_body_file="/tmp/forgejo_create_body.$$.json"
|
||||||
|
create_req_file="/tmp/forgejo_create_req.$$.json"
|
||||||
|
|
||||||
|
# Build the JSON request body via python3's json.dumps rather than raw
|
||||||
|
# string interpolation, so a tag/title containing `"` / `\` / control
|
||||||
|
# characters can't break out of the JSON structure (json-injection guard).
|
||||||
|
# Values are piped in NUL-separated on stdin — never interpolated into the
|
||||||
|
# python source — and no $() command substitution is used.
|
||||||
|
printf '%s\0%s\0' "$tag" "$title" | python3 -c '
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
raw = sys.stdin.buffer.read()
|
||||||
|
tag, title = (part.decode() for part in raw.split(b"\0")[:2])
|
||||||
|
json.dump(
|
||||||
|
{"tag_name": tag, "name": title, "draft": False, "prerelease": False},
|
||||||
|
sys.stdout,
|
||||||
|
)
|
||||||
|
' > "$create_req_file"
|
||||||
|
|
||||||
curl "${FORGEJO_CURL_TLS[@]}" -s -o "$create_body_file" -w '%{http_code}' \
|
curl "${FORGEJO_CURL_TLS[@]}" -s -o "$create_body_file" -w '%{http_code}' \
|
||||||
-X POST "${FORGEJO_URL}/api/v1/repos/${FORGEJO_REPO}/releases" \
|
-X POST "${FORGEJO_URL}/api/v1/repos/${FORGEJO_REPO}/releases" \
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"tag_name\":\"${tag}\",\"name\":\"${title}\",\"draft\":false,\"prerelease\":false}" \
|
--data @"$create_req_file" \
|
||||||
> "$create_code_file"
|
> "$create_code_file"
|
||||||
read -r create_code < "$create_code_file"
|
read -r create_code < "$create_code_file"
|
||||||
rm -f "$create_code_file"
|
rm -f "$create_code_file" "$create_req_file"
|
||||||
|
|
||||||
if [ "$create_code" -lt 200 ] || [ "$create_code" -ge 300 ]; then
|
if [ "$create_code" -lt 200 ] || [ "$create_code" -ge 300 ]; then
|
||||||
echo "==> forgejo: release create FAILED (HTTP ${create_code})" >&2
|
echo "==> forgejo: release create FAILED (HTTP ${create_code})" >&2
|
||||||
|
|||||||
Executable
+69
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib-ssh.sh — shared SSH deploy-key helpers for the pangolin deploy pipelines
|
||||||
|
# (site / server). `source` this from deploy-*.sh.
|
||||||
|
#
|
||||||
|
# Provides:
|
||||||
|
# setup_ssh -> registers the EXIT cleanup trap first, then writes
|
||||||
|
# $DEPLOY_SSH_KEY to a temp private key (mode 600),
|
||||||
|
# registers the deploy host in a dedicated known_hosts
|
||||||
|
# file, and exports SSH / RSYNC_SSH (ssh command strings)
|
||||||
|
# + SSH_KEY_FILE.
|
||||||
|
# teardown_ssh -> removes the temp private key + known_hosts file.
|
||||||
|
#
|
||||||
|
# Requires env: DEPLOY_SSH_KEY (PEM content of the deploy private key,
|
||||||
|
# authorized for root on the target host).
|
||||||
|
#
|
||||||
|
# Target host is hardcoded to the pangolin1 VPS IP (103.119.13.48): the CI
|
||||||
|
# runner has no access to the user's local ~/.ssh/config, so the `pangolin1`
|
||||||
|
# alias cannot be resolved there — the bare IP is used instead. Override with
|
||||||
|
# DEPLOY_HOST / DEPLOY_PORT if a caller needs to retarget.
|
||||||
|
#
|
||||||
|
# No command substitution ($()) is used anywhere, per repo bash conventions.
|
||||||
|
|
||||||
|
DEPLOY_HOST="${DEPLOY_HOST:-103.119.13.48}"
|
||||||
|
DEPLOY_PORT="${DEPLOY_PORT:-22}"
|
||||||
|
SSH_KEY_FILE="${SSH_KEY_FILE:-/tmp/pangolin_deploy_key.$$}"
|
||||||
|
SSH_KNOWN_HOSTS_FILE="${SSH_KNOWN_HOSTS_FILE:-/tmp/pangolin_deploy_known_hosts.$$}"
|
||||||
|
|
||||||
|
# setup_ssh — write the deploy key, register known_hosts, export SSH/RSYNC_SSH.
|
||||||
|
setup_ssh() {
|
||||||
|
# Register cleanup FIRST: if anything below fails mid-setup (e.g. a
|
||||||
|
# transient ssh-keyscan error under `set -e`), the private key file must
|
||||||
|
# still be removed on exit rather than leaking.
|
||||||
|
trap teardown_ssh EXIT
|
||||||
|
|
||||||
|
if [ -z "${DEPLOY_SSH_KEY:-}" ]; then
|
||||||
|
echo "==> setup_ssh: DEPLOY_SSH_KEY is empty" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
chmod 700 ~/.ssh
|
||||||
|
|
||||||
|
# Pre-create the key file with restrictive perms *before* writing any key
|
||||||
|
# material into it, so there is no window at the default umask between
|
||||||
|
# file creation and chmod.
|
||||||
|
install -m 600 /dev/null "${SSH_KEY_FILE}"
|
||||||
|
|
||||||
|
# `printf '%s\n'` 末尾补一个换行:Forgejo/Gitea 存 secret 会去掉结尾换行,
|
||||||
|
# 而缺结尾换行的 OpenSSH 格式私钥会被判为 "invalid format" 拒绝加载,
|
||||||
|
# 退化成无密钥 → Permission denied。多补的换行对已含结尾换行的 PEM 无害。
|
||||||
|
printf '%s\n' "${DEPLOY_SSH_KEY}" > "${SSH_KEY_FILE}"
|
||||||
|
|
||||||
|
# Populate a dedicated known_hosts file via TOFU keyscan. This is
|
||||||
|
# belt-and-suspenders: `accept-new` below will pin the host key on first
|
||||||
|
# real connection regardless, so a transient keyscan failure must not
|
||||||
|
# abort the deploy.
|
||||||
|
ssh-keyscan -p "${DEPLOY_PORT}" -H "${DEPLOY_HOST}" >> "${SSH_KNOWN_HOSTS_FILE}" 2>/dev/null || true
|
||||||
|
|
||||||
|
SSH="ssh -i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}"
|
||||||
|
RSYNC_SSH="ssh -i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}"
|
||||||
|
export SSH RSYNC_SSH SSH_KEY_FILE SSH_KNOWN_HOSTS_FILE DEPLOY_HOST DEPLOY_PORT
|
||||||
|
echo "==> setup_ssh: key written to ${SSH_KEY_FILE}, known_hosts pinned (accept-new) for ${DEPLOY_HOST}:${DEPLOY_PORT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# teardown_ssh — remove the temp private key + known_hosts file.
|
||||||
|
teardown_ssh() {
|
||||||
|
rm -f "${SSH_KEY_FILE}" "${SSH_KNOWN_HOSTS_FILE}"
|
||||||
|
echo "==> teardown_ssh: removed ${SSH_KEY_FILE}"
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# release-server.sh — ensure the Forgejo release for a server-v* tag exists
|
||||||
|
# and upload the three compiled binaries as release assets. Assumes
|
||||||
|
# compile-backend.sh has already produced
|
||||||
|
# server/out/{pangolin-server,pangolin-agent,pangolin-migrate}.
|
||||||
|
#
|
||||||
|
# Usage: scripts/ci/release-server.sh <tag> (e.g. server-v1.2.3)
|
||||||
|
# Requires env: FORGEJO_URL, FORGEJO_TOKEN (see lib-forgejo.sh).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TAG="${1:?usage: release-server.sh <tag>}"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/_env.sh
|
||||||
|
. scripts/ci/_env.sh
|
||||||
|
# shellcheck source=scripts/ci/lib-forgejo.sh
|
||||||
|
. scripts/ci/lib-forgejo.sh
|
||||||
|
|
||||||
|
# No command substitution ($()): ver_from_tag prints to stdout, captured via
|
||||||
|
# a temp file + `read`, same no-substitution pattern as lib-forgejo.sh.
|
||||||
|
ver_file="/tmp/release_server_ver.$$"
|
||||||
|
ver_from_tag server "$TAG" > "$ver_file"
|
||||||
|
VER=""
|
||||||
|
read -r VER < "$ver_file"
|
||||||
|
rm -f "$ver_file"
|
||||||
|
|
||||||
|
echo "==> release-server: tag=${TAG} ver=${VER}"
|
||||||
|
|
||||||
|
forgejo_release_ensure "$TAG" "server ${VER}"
|
||||||
|
|
||||||
|
forgejo_upload_asset "$TAG" server/out/pangolin-server
|
||||||
|
forgejo_upload_asset "$TAG" server/out/pangolin-agent
|
||||||
|
forgejo_upload_asset "$TAG" server/out/pangolin-migrate
|
||||||
|
|
||||||
|
echo "==> release-server: done"
|
||||||
Executable
+49
@@ -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} 通过"
|
||||||
Reference in New Issue
Block a user