feat(ci): SQLite 每日备份到 NAS(异地容灾,#26/F2)
backup-db.sh:ssh pangolin1 → sqlite3 online .backup 取 WAL 一致快照 + 远端 integrity_check → gzip 流回 → 存 NAS /volume1/docker/backups/pangolin,保留 30 天。 backup.yml:每日 02:00(北京)cron + 手动,ubuntu-latest,复用 DEPLOY_SSH_KEY, Telegram 通知。异地=家里 NAS≠VPS 机房。不在 pangolin1 装/常驻任何东西。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
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: bash scripts/ci/backup-db.sh
|
||||
|
||||
- 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
|
||||
@@ -54,7 +54,8 @@ 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:
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/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 -t "$DEST"
|
||||
if ! find "$DEST" -size +50k -print -quit | grep -q .; then
|
||||
echo "==> backup-db: 备份异常小(<50k),中止" >&2
|
||||
rm -f "$DEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 保留最近 30 天。
|
||||
find "$BACKUP_DIR" -name 'pangolin_*.db.gz' -mtime +30 -delete
|
||||
|
||||
echo "==> backup-db: 备份完成 ${DEST}"
|
||||
Reference in New Issue
Block a user