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:
2
.github/workflows/docker-build-deploy.yml
vendored
2
.github/workflows/docker-build-deploy.yml
vendored
@@ -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
|
||||
|
||||
161
.github/workflows/legacy-data-migrate.yml
vendored
Normal file
161
.github/workflows/legacy-data-migrate.yml
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
# 一次性:将旧 pg_dump 数据迁入当前 Alembic schema(api/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 + 重新导入 SQL;false=仅运行迁移脚本'
|
||||
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
|
||||
Reference in New Issue
Block a user