ci: 修复数据库备份 workflow——改用 mac runner、EC2 端读密码、本机落盘、每日定时
backup.yml 原 runs-on ubuntu-latest 无对应 runner、落盘 NAS 路径在 mac runner 不存在,长期跑不起来。 改为 runs-on mac + scripts/ci/backup-db.sh:复用 lib-forgejo 的 ssh 封装连 EC2, 在 EC2 端从 production.env 读 DSN 密码 mysqldump(零额外 secret),流回本机 ~/jiu-db-backups gzip 落盘、保留 30 天、完整性校验,恢复每日 02:00 定时 + Telegram 成败通知。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZ4DskSRKsSiheQonFtQvx
This commit is contained in:
+21
-23
@@ -1,37 +1,35 @@
|
|||||||
name: DB Backup
|
name: DB Backup
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# 定时备份已暂停(保留手动触发)。恢复时取消下面 schedule 的注释即可。
|
# 每日定时备份(北京时间 02:00)。手动触发亦可。
|
||||||
# schedule:
|
schedule:
|
||||||
# - cron: '0 18 * * *' # UTC 18:00 = 北京时间 02:00
|
- cron: '0 18 * * *' # UTC 18:00 = 北京时间 02:00
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: db-backup
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backup:
|
backup:
|
||||||
runs-on: ubuntu-latest
|
runs-on: mac
|
||||||
steps:
|
steps:
|
||||||
- name: Setup SSH
|
- uses: actions/checkout@v4
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
printf '%s' "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2.pem
|
|
||||||
chmod 600 ~/.ssh/ec2.pem
|
|
||||||
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Dump MySQL to NAS
|
- name: Dump MySQL to local backup dir
|
||||||
env:
|
env:
|
||||||
|
EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }}
|
||||||
EC2_HOST: ${{ secrets.EC2_HOST }}
|
EC2_HOST: ${{ secrets.EC2_HOST }}
|
||||||
EC2_USER: ${{ secrets.EC2_USER }}
|
EC2_USER: ${{ secrets.EC2_USER }}
|
||||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
run: sh scripts/ci/backup-db.sh
|
||||||
run: |
|
|
||||||
BACKUP_DIR=/volume1/docker/backups/jiu-db
|
|
||||||
mkdir -p $BACKUP_DIR
|
|
||||||
FILENAME="jiu_db_$(date +%Y%m%d_%H%M%S).sql.gz"
|
|
||||||
ssh -i ~/.ssh/ec2.pem ${EC2_USER}@${EC2_HOST} \
|
|
||||||
"docker exec jiu_mysql mysqldump -uroot -p${DB_PASSWORD} jiu_db" \
|
|
||||||
| gzip > ${BACKUP_DIR}/${FILENAME}
|
|
||||||
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
|
|
||||||
echo "Saved: ${BACKUP_DIR}/${FILENAME}"
|
|
||||||
|
|
||||||
- name: Cleanup SSH key
|
- name: Notify (Telegram)
|
||||||
if: always()
|
if: always()
|
||||||
run: rm -f ~/.ssh/ec2.pem
|
env:
|
||||||
|
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ job.status }}" = "success" ]; then ICON="✅"; LABEL="数据库备份成功"; else ICON="❌"; LABEL="数据库备份失败"; fi
|
||||||
|
curl -f -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
|
||||||
|
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
||||||
|
--data-urlencode "text=${ICON} 岩美 ${LABEL}" > /dev/null || true
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# backup-db.sh — dump the production MySQL (jiu_db) from EC2 and keep a gzip
|
||||||
|
# snapshot on the mac (host) runner's local disk (~/jiu-db-backups), retaining
|
||||||
|
# the last 30 days. The DB password is read on EC2 from production.env's
|
||||||
|
# DATABASE_DSN, so no DB-password secret is needed.
|
||||||
|
#
|
||||||
|
# Requires env (same as deploy-server): EC2_SSH_KEY, EC2_HOST, EC2_USER.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=scripts/ci/lib-forgejo.sh
|
||||||
|
. "$(dirname "$0")/lib-forgejo.sh"
|
||||||
|
|
||||||
|
BACKUP_DIR="${HOME}/jiu-db-backups"
|
||||||
|
mkdir -p "${BACKUP_DIR}"
|
||||||
|
FILENAME="jiu_db_$(date +%Y%m%d_%H%M%S).sql.gz"
|
||||||
|
DEST="${BACKUP_DIR}/${FILENAME}"
|
||||||
|
|
||||||
|
setup_ssh
|
||||||
|
trap teardown_ssh EXIT
|
||||||
|
|
||||||
|
echo "==> backup-db: dumping jiu_db from ${EC2_HOST}"
|
||||||
|
# On EC2: parse the DSN password from production.env, then mysqldump the
|
||||||
|
# container to stdout; stream back over ssh and gzip locally. The heredoc is
|
||||||
|
# single-quoted, so it runs verbatim on EC2 (no local expansion). ${SSH} carries
|
||||||
|
# no -t, keeping stdout a clean dump stream.
|
||||||
|
${SSH} "${EC2_USER}@${EC2_HOST}" 'bash -s' <<'ENDSSH' | gzip > "${DEST}"
|
||||||
|
set -euo pipefail
|
||||||
|
PW=$(python3 -c 'import re;e=open("/opt/jiu/config/production.env").read();d=re.search(r"DATABASE_DSN=(.*)",e).group(1).strip().strip(chr(34)).strip(chr(39));print(re.match(r"[^:]+:([^@]+)@",d).group(1))')
|
||||||
|
exec docker exec -e MYSQL_PWD="${PW}" jiu_mysql mysqldump -uroot --single-transaction --no-tablespaces jiu_db
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
# Integrity + sanity + retention.
|
||||||
|
gzip -t "${DEST}"
|
||||||
|
SIZE=$(stat -f%z "${DEST}" 2>/dev/null || stat -c%s "${DEST}")
|
||||||
|
if [ "${SIZE}" -lt 100000 ]; then
|
||||||
|
echo "==> backup-db: dump suspiciously small (${SIZE} bytes), aborting" >&2
|
||||||
|
rm -f "${DEST}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
find "${BACKUP_DIR}" -name '*.sql.gz' -mtime +30 -delete
|
||||||
|
echo "==> backup-db: saved ${DEST} (${SIZE} bytes)"
|
||||||
Reference in New Issue
Block a user