Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8e870fd6a | |||
| 84b042394d | |||
| e557a94ed0 | |||
| e1a17acbe4 | |||
| 3efd67297e | |||
| f6e565ffdc | |||
| ba5f71b716 |
@@ -0,0 +1,39 @@
|
||||
name: DB Backup
|
||||
|
||||
on:
|
||||
# 每日北京 02:00(= UTC 18:00)自动备份;也可手动触发。
|
||||
schedule:
|
||||
- cron: '0 18 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: db-backup
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
backup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Backup pangolin1 SQLite → NAS
|
||||
env:
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
run: |
|
||||
mkdir -p /volume1/docker/backups/pangolin
|
||||
bash scripts/ci/backup-db.sh 2>&1 | tee /volume1/docker/backups/pangolin/_last-run.log
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Notify (Telegram)
|
||||
if: always()
|
||||
env:
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
run: |
|
||||
. scripts/ci/notify.sh
|
||||
if [ "${{ job.status }}" = "success" ]; then
|
||||
notify_ok "pangolin1 SQLite 备份成功(NAS)"
|
||||
else
|
||||
notify_fail "pangolin1 SQLite 备份失败"
|
||||
fi
|
||||
+12
-11
@@ -21,7 +21,7 @@ jobs:
|
||||
# ── Job 1: Lint (shellcheck) ─────────────────────────────────────────────
|
||||
lint:
|
||||
name: Lint — shellcheck
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -54,12 +54,13 @@ jobs:
|
||||
/mnt/scripts/ci/compile-backend.sh \
|
||||
/mnt/scripts/ci/release-server.sh \
|
||||
/mnt/scripts/ci/deploy-server.sh \
|
||||
/mnt/scripts/ci/test.sh
|
||||
/mnt/scripts/ci/test.sh \
|
||||
/mnt/scripts/ci/backup-db.sh
|
||||
|
||||
# ── Job 2: OpenAPI Sync Check ────────────────────────────────────────────
|
||||
openapi-check:
|
||||
name: OpenAPI Sync Check
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -76,7 +77,7 @@ jobs:
|
||||
# ── Job 3: Redline Word Scan (脱敏) ──────────────────────────────────────
|
||||
redline-scan:
|
||||
name: Redline Scan — 脱敏 (UI 文案)
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -87,7 +88,7 @@ jobs:
|
||||
# ── Job 4: Flutter 客户端(分析 + 单测/组件测试)────────────────────────
|
||||
flutter-client:
|
||||
name: Flutter — analyze + test
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -110,7 +111,7 @@ jobs:
|
||||
# 规则与豁免见 ci/scan-portable-sql.sh 头注 + docs/dev-conventions.html 支柱 3。
|
||||
portable-sql-scan:
|
||||
name: Portable SQL — 可移植性 (mysql/sqlite)
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -123,7 +124,7 @@ jobs:
|
||||
# (改了 design/colors_and_type.css 没重生成,或手改了生成物)。
|
||||
codegen-drift:
|
||||
name: Codegen Drift — token 生成物未漂移
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -140,7 +141,7 @@ jobs:
|
||||
# -tags integration(需 docker 起 mysql/redis),见 go-integration job。
|
||||
go-server:
|
||||
name: Go — build + test
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -166,7 +167,7 @@ jobs:
|
||||
# 详见 scripts/e2e-smoke.sh + server/test/e2e/。
|
||||
e2e-smoke:
|
||||
name: E2E Smoke — L4 进程级端到端
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -190,7 +191,7 @@ jobs:
|
||||
# · -p 1 串行:一次只起一个 mysql 容器,避免并发把 Docker Desktop 压垮/端口资源争用。
|
||||
go-integration:
|
||||
name: Go — integration (mysql/redis testcontainers)
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -205,7 +206,7 @@ jobs:
|
||||
# tablet/desktop-stats golden 与 stats-overhaul 工作区耦合,待其合并后并入本 job。
|
||||
golden:
|
||||
name: Golden — 视觉回归 (components + auth)
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -12,18 +12,15 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
deploy-server:
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
# 直接在 runner 跑(不嵌套 docker,避免 DinD 挂载失败)。runner 镜像的 go
|
||||
# 若旧于 go.mod 要求(1.25.10),Go 工具链会经 GOPROXY=goproxy.cn 自动下载对应版本。
|
||||
- name: Compile (Go 控制面)
|
||||
run: bash scripts/ci/compile-backend.sh
|
||||
|
||||
- name: Test (go test)
|
||||
run: bash scripts/ci/test.sh server
|
||||
|
||||
@@ -12,22 +12,20 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
deploy-site:
|
||||
runs-on: nas
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
# runner 镜像 catthehacker/ubuntu:act-latest 自带 node/npx,直接跑;
|
||||
# 不用嵌套 docker run(job 容器内的 $PWD 在宿主上不存在,DinD 挂载会失败)。
|
||||
- name: Compile (Astro 官网)
|
||||
env:
|
||||
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: |
|
||||
docker run --rm -v "$PWD:/w" -w /w \
|
||||
-e CLOUDFLARE_API_TOKEN -e CLOUDFLARE_ACCOUNT_ID \
|
||||
node:20 bash scripts/ci/deploy-site.sh
|
||||
run: bash scripts/ci/deploy-site.sh
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# backup-db.sh — pangolin1 控制面 SQLite 每日备份到 NAS(异地容灾,#26 / F2)。
|
||||
#
|
||||
# 经 ssh 在 pangolin1 上用 sqlite3 online `.backup` 取 WAL 一致快照(不阻塞服务、
|
||||
# 不像裸 cp 那样可能拿到半写状态),远端先跑 PRAGMA integrity_check 确认没坏,再把
|
||||
# 快照 gzip 流回 runner,落到 NAS 备份盘(默认 /volume1/docker/backups/pangolin,
|
||||
# 该盘已挂进 runner 容器)。保留 30 天。异地 = 家里 NAS ≠ VPS 机房,构成容灾。
|
||||
#
|
||||
# 不在 pangolin1 上安装/常驻任何东西:调度在 Gitea Actions(backup.yml),每次经
|
||||
# ssh 临时跑一次 sqlite3。需环境:DEPLOY_SSH_KEY(secret)。从 repo 根调用。
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=scripts/ci/lib-ssh.sh
|
||||
. scripts/ci/lib-ssh.sh
|
||||
|
||||
BACKUP_DIR="${BACKUP_DIR:-/volume1/docker/backups/pangolin}"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# 时间戳文件名(bash 内建 strftime,免 $() 命令替换)。
|
||||
printf -v TS '%(%Y%m%d_%H%M%S)T' -1
|
||||
DEST="${BACKUP_DIR}/pangolin_${TS}.db.gz"
|
||||
|
||||
# setup_ssh 内部先注册 EXIT trap 清理临时私钥,再写 key/known_hosts,导出 $SSH。
|
||||
setup_ssh
|
||||
|
||||
echo "==> backup-db: sqlite3 .backup pangolin1(${DEPLOY_HOST})-> ${DEST}"
|
||||
# 远端单引号 heredoc:$BK/$$ 在 pangolin1 上展开。以 pangolin 用户(DB 属主)跑
|
||||
# online .backup + integrity_check;校验通过才 cat 回流;临时文件用完即删。
|
||||
# integrity_check 失败 → 远端非零退出 → 本地 pipefail 令整条命令失败 → set -e 中止。
|
||||
$SSH "root@${DEPLOY_HOST}" 'bash -s' <<'ENDSSH' | gzip > "$DEST"
|
||||
set -euo pipefail
|
||||
BK="/tmp/pangolin-bk-$$.db"
|
||||
trap 'rm -f "$BK"' EXIT
|
||||
runuser -u pangolin -- sqlite3 "/var/lib/pangolin/pangolin.db" ".backup ${BK}"
|
||||
if ! runuser -u pangolin -- sqlite3 "$BK" "PRAGMA integrity_check;" | grep -q '^ok$'; then
|
||||
echo "backup-db(remote): integrity_check 失败" >&2
|
||||
exit 1
|
||||
fi
|
||||
cat "$BK"
|
||||
ENDSSH
|
||||
|
||||
# 本地二次校验:gzip 完好 + 非空 sanity。空 gzip 仅 ~20 字节;真备份(即便很小的库)
|
||||
# gzip 后也远大于 1KB,故用 >1k 区分「空/断流」与「有效备份」(不用 50k:小库压缩后可能 <50k)。
|
||||
gzip -t "$DEST"
|
||||
if ! find "$DEST" -size +1k -print -quit | grep -q .; then
|
||||
echo "==> backup-db: 备份异常小(<1k,疑似空/断流),中止" >&2
|
||||
rm -f "$DEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 保留最近 30 天。
|
||||
find "$BACKUP_DIR" -name 'pangolin_*.db.gz' -mtime +30 -delete
|
||||
|
||||
echo "==> backup-db: 备份完成 ${DEST}"
|
||||
@@ -56,8 +56,12 @@ setup_ssh() {
|
||||
# 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}"
|
||||
# IdentitiesOnly=yes:只用上面 -i 指定的 key,不把 agent/默认 key 也递上去
|
||||
# (否则会触发服务器 MaxAuthTries「Too many authentication failures」)。
|
||||
# BatchMode=yes:纯非交互,认证失败即退出,不回落到密码提示卡住。
|
||||
_ssh_opts="-i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}"
|
||||
SSH="ssh ${_ssh_opts}"
|
||||
RSYNC_SSH="ssh ${_ssh_opts}"
|
||||
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}"
|
||||
}
|
||||
|
||||
+4
-9
@@ -21,15 +21,10 @@ 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 ./...
|
||||
# 直接在 runner 跑(不嵌套 docker,避免 DinD 挂载失败)。go.mod 要求的
|
||||
# go 1.25.x 若高于 runner 自带版本,Go 工具链会经 GOPROXY(_env.sh)自动下载。
|
||||
echo "==> test: go test ./..."
|
||||
( cd server && go test ./... )
|
||||
;;
|
||||
client)
|
||||
mkdir -p "$HOME/.cache/pangolin-ci/pubcache"
|
||||
|
||||
Reference in New Issue
Block a user