chore: track legacy pg_dump for Actions migrate workflow

- Allow life_echo_20260313_182756.sql under api/backups via .gitignore exception
- Legacy migrate workflow: checkout, verify file, scp to server before migrate
- Point docker-build-deploy readers to one-shot legacy workflow

Made-with: Cursor
This commit is contained in:
Kevin
2026-03-26 15:30:10 +08:00
parent aa6df9eb55
commit c23931ec91
4 changed files with 2001 additions and 0 deletions

View File

@@ -7,6 +7,8 @@
# 从旧版迁移:若仓库里仍是 SSH_HOST、SSH_PRIVATE_KEY、DEPLOY_PATH 等无前缀名称,
# 请把「预发机」对应值迁移为 STAGING_*,「新生产机」填 PROD_*,并删除旧的无前缀 Secret。
#
# 旧库 pg_dump 一次性迁入当前 schema见 workflow「Legacy DB migrate (one-shot)」(手动运行,非每次构建)。
#
# 发布策略:
# - merge / push 到 main构建并部署到 Staging 机;使用仓库中的 api/.env.staging上传后切换为运行时 .env
# - 手动创建并推送 tag vMAJOR.MINOR.PATCH构建并部署到 Production使用仓库中的 api/.env.production上传后切换为运行时 .env

View File

@@ -0,0 +1,161 @@
# 一次性:将旧 pg_dump 数据迁入当前 Alembic schemaapi/scripts/migrate_legacy_to_current.py
#
# 不会在 push / 部署时自动运行,仅手动 workflow_dispatch避免每次构建误迁库。
# 远端需已用 docker compose 部署(目录约定与 docker-build-deploy 一致STAGING_DEPLOY_PATH / PROD_DEPLOY_PATH
#
# 备份文件:提交在仓库 api/backups/<dump_filename>(默认 life_echo_20260313_182756.sql
# workflow 会先 scp 到远端再迁移。其他 *.sql 仍被 gitignore需按需增加 ! 例外行。
#
# Secrets与 Docker Build and Deploy 相同STAGING_* / PROD_*)。
name: Legacy DB migrate (one-shot)
on:
workflow_dispatch:
inputs:
environment:
description: 'staging 或 production'
required: true
type: choice
options:
- staging
- production
confirm:
description: '确认执行迁移(必须完整输入 MIGRATE'
required: true
type: string
dump_filename:
description: '仓库 api/backups/ 与远端同名 SQL 文件(须已提交)'
required: true
type: string
default: life_echo_20260313_182756.sql
recreate_legacy_database:
description: 'true=DROP 并重建 life_echo_legacy + 重新导入 SQLfalse=仅运行迁移脚本'
required: true
type: boolean
default: true
concurrency:
group: legacy-migrate-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
migrate:
name: Legacy migrate on server
runs-on: ubuntu-latest
steps:
- name: Require confirm MIGRATE
run: |
if [ "${{ github.event.inputs.confirm }}" != "MIGRATE" ]; then
echo "::error::确认字段必须精确为 MIGRATE当前未执行任何操作"
exit 1
fi
- name: Checkout
uses: actions/checkout@v5
- name: Verify backup file in repo
env:
DUMP_FILENAME: ${{ github.event.inputs.dump_filename }}
run: |
set -euo pipefail
f="api/backups/$DUMP_FILENAME"
if [ ! -f "$f" ]; then
echo "::error::仓库中不存在 $f请提交该文件或改正 dump_filename并在 .gitignore 中为该文件增加 ! 例外"
exit 1
fi
ls -la "$f"
- name: Set up SSH (production)
if: github.event.inputs.environment == 'production'
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
- name: Set up SSH (staging)
if: github.event.inputs.environment != 'production'
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.STAGING_SSH_PRIVATE_KEY }}
- name: Export deploy connection env
run: |
if [ "${{ github.event.inputs.environment }}" = "production" ]; then
{
echo "SSH_HOST=${{ secrets.PROD_SSH_HOST }}"
echo "SSH_USER=${{ secrets.PROD_SSH_USER }}"
echo "SSH_PORT=${{ secrets.PROD_SSH_PORT || '22' }}"
echo "COMPOSE_DIR=${{ secrets.PROD_DEPLOY_PATH || '/opt/life-echo' }}"
} >> "$GITHUB_ENV"
else
{
echo "SSH_HOST=${{ secrets.STAGING_SSH_HOST }}"
echo "SSH_USER=${{ secrets.STAGING_SSH_USER }}"
echo "SSH_PORT=${{ secrets.STAGING_SSH_PORT || '22' }}"
echo "COMPOSE_DIR=${{ secrets.STAGING_DEPLOY_PATH || '/opt/life-echo' }}"
} >> "$GITHUB_ENV"
fi
- name: Add server to known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H -p "${SSH_PORT:-22}" "${SSH_HOST}" >> ~/.ssh/known_hosts
- name: Upload dump to server
env:
DUMP_FILENAME: ${{ github.event.inputs.dump_filename }}
run: |
set -euo pipefail
ssh -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "mkdir -p \"$COMPOSE_DIR/api/backups\""
scp -P "$SSH_PORT" "api/backups/$DUMP_FILENAME" "$SSH_USER@$SSH_HOST:$COMPOSE_DIR/api/backups/$DUMP_FILENAME"
echo "已上传 api/backups/$DUMP_FILENAME -> $COMPOSE_DIR/api/backups/"
- name: Run legacy migrate on remote
env:
DUMP_FILENAME: ${{ github.event.inputs.dump_filename }}
RECREATE_LEGACY: ${{ github.event.inputs.recreate_legacy_database }}
run: |
set -euo pipefail
# 通过环境变量传入远端,避免 heredoc 引号问题
ssh -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"export COMPOSE_DIR=$(printf '%q' "$COMPOSE_DIR"); export DUMP_FILENAME=$(printf '%q' "$DUMP_FILENAME"); export RECREATE_LEGACY=$(printf '%q' "$RECREATE_LEGACY"); bash -s" << 'REMOTE'
set -euo pipefail
cd "$COMPOSE_DIR/api"
DUMP="$PWD/backups/$DUMP_FILENAME"
if [ ! -f "$DUMP" ]; then
echo "::error::远端缺少备份文件: $DUMP"
exit 1
fi
if [ ! -f .env ]; then
echo "::error::缺少 $PWD/.env"
exit 1
fi
# DATABASE_URL 可能含特殊字符,取第一个等号后的整行
DB_URL=$(grep -m1 '^DATABASE_URL=' .env | cut -d= -f2- | tr -d '\r' | sed 's/^"//;s/"$//;s/^'"'"'//;s/'"'"'$//')
PG_USER=$(grep -m1 '^POSTGRES_USER=' .env 2>/dev/null | cut -d= -f2- | tr -d '\r' | sed 's/^"//;s/"$//' || true)
PG_PASS=$(grep -m1 '^POSTGRES_PASSWORD=' .env 2>/dev/null | cut -d= -f2- | tr -d '\r' | sed 's/^"//;s/"$//' || true)
PG_USER=${PG_USER:-postgres}
PG_PASS=${PG_PASS:-postgres}
LEGACY_URL="postgresql://${PG_USER}:${PG_PASS}@postgres:5432/life_echo_legacy"
if [ "$RECREATE_LEGACY" = "true" ]; then
echo "重建 life_echo_legacy 并导入 SQL..."
docker compose exec -T postgres psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c \
"DROP DATABASE IF EXISTS life_echo_legacy WITH (FORCE);"
docker compose exec -T postgres psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c \
"CREATE DATABASE life_echo_legacy;"
docker exec -i life-echo-postgres psql -U postgres -d life_echo_legacy -v ON_ERROR_STOP=1 < "$DUMP"
else
echo "跳过 DROP/导入,假定 life_echo_legacy 已就绪"
fi
echo "执行 Python 迁移api 容器内)..."
docker compose exec -T api uv run python scripts/migrate_legacy_to_current.py \
--legacy-url "$LEGACY_URL" \
--target-url "$DB_URL"
echo "完成。"
REMOTE