chore: 接入 Prisma 迁移部署基线
This commit is contained in:
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SRC="${1:-}"
|
||||
APP_DIR="/srv/www/devops-platform-api"
|
||||
BACKUP_DIR="/srv/www/devops-platform-api-backups"
|
||||
ENV_FILE="/etc/devops-platform-api.env"
|
||||
PM2_NAME="devops-platform-api"
|
||||
HEALTH_URL="http://127.0.0.1:4300/health"
|
||||
TS="$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
if [ -z "$SRC" ] || [ ! -d "$SRC" ]; then
|
||||
echo "Usage: deploy-devops-platform-api-from-jenkins <jenkins-workspace>"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required in "$SRC/dist/main.js" "$SRC/package.json" "$SRC/pnpm-lock.yaml" "$SRC/prisma" "$ENV_FILE"; do
|
||||
if [ ! -e "$required" ]; then
|
||||
echo "Missing $required"
|
||||
exit 3
|
||||
fi
|
||||
done
|
||||
|
||||
run_prisma_migrations() {
|
||||
if [ "${PRISMA_MIGRATE_DEPLOY_ENABLED:-true}" != "true" ]; then
|
||||
echo "Skipping Prisma migrations because PRISMA_MIGRATE_DEPLOY_ENABLED is not true"
|
||||
return
|
||||
fi
|
||||
|
||||
set -a
|
||||
. ./.env.production
|
||||
set +a
|
||||
|
||||
local migration_state
|
||||
migration_state="$(node <<'NODE'
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
const requiredTables = [
|
||||
'agent_invocations',
|
||||
'audit_logs',
|
||||
'deploy_jobs',
|
||||
'deploy_runs',
|
||||
'environments',
|
||||
'outbox_messages',
|
||||
'platform_messages',
|
||||
'project_member_permissions',
|
||||
'projects',
|
||||
'run_steps',
|
||||
'secrets',
|
||||
'users',
|
||||
];
|
||||
const baselineMigrations = [
|
||||
'20260611000000_baseline_current_schema',
|
||||
'20260611120000_auth_members_messages',
|
||||
];
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const tables = await prisma.$queryRaw`
|
||||
select table_name
|
||||
from information_schema.tables
|
||||
where table_schema = database()
|
||||
`;
|
||||
const existingTables = new Set(tables.map((row) => row.TABLE_NAME || row.table_name));
|
||||
|
||||
if (existingTables.size === 0) {
|
||||
console.log('fresh');
|
||||
return;
|
||||
}
|
||||
|
||||
const missingTables = requiredTables.filter((name) => !existingTables.has(name));
|
||||
if (missingTables.length > 0) {
|
||||
console.error(`Existing database schema is incomplete; missing tables: ${missingTables.join(', ')}`);
|
||||
process.exit(12);
|
||||
}
|
||||
|
||||
const hasMigrationTable = existingTables.has('_prisma_migrations');
|
||||
const appliedMigrations = hasMigrationTable
|
||||
? await prisma.$queryRaw`select migration_name from _prisma_migrations`
|
||||
: [];
|
||||
const appliedNames = new Set(
|
||||
appliedMigrations.map((row) => row.MIGRATION_NAME || row.migration_name),
|
||||
);
|
||||
const missingMigrations = baselineMigrations.filter((name) => !appliedNames.has(name));
|
||||
|
||||
if (missingMigrations.length > 0) {
|
||||
console.log(`bootstrap:${missingMigrations.join(',')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('ready');
|
||||
}
|
||||
|
||||
main()
|
||||
.finally(() => prisma.$disconnect());
|
||||
NODE
|
||||
)"
|
||||
|
||||
if [[ "$migration_state" == bootstrap:* ]]; then
|
||||
local migrations_csv="${migration_state#bootstrap:}"
|
||||
IFS=',' read -r -a missing_migrations <<< "$migrations_csv"
|
||||
for migration in "${missing_migrations[@]}"; do
|
||||
echo "Marking existing production schema migration as applied: $migration"
|
||||
pnpm prisma migrate resolve --schema prisma/schema.prisma --applied "$migration"
|
||||
done
|
||||
else
|
||||
echo "Prisma migration bootstrap state: $migration_state"
|
||||
fi
|
||||
|
||||
echo "Running Prisma migrate deploy"
|
||||
pnpm prisma migrate deploy --schema prisma/schema.prisma
|
||||
}
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
TMP="$(mktemp -d /srv/www/devops-platform-api.deploy.XXXXXX)"
|
||||
OLD="$APP_DIR.old.$TS"
|
||||
|
||||
cp -a "$SRC/dist" "$TMP/dist"
|
||||
cp -a "$SRC/prisma" "$TMP/prisma"
|
||||
cp -a "$SRC/package.json" "$TMP/package.json"
|
||||
cp -a "$SRC/pnpm-lock.yaml" "$TMP/pnpm-lock.yaml"
|
||||
cp -a "$ENV_FILE" "$TMP/.env.production"
|
||||
|
||||
cd "$TMP"
|
||||
corepack enable
|
||||
corepack prepare pnpm@9.15.4 --activate
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm prisma:generate
|
||||
run_prisma_migrations
|
||||
|
||||
if [ -d "$APP_DIR" ]; then
|
||||
tar -C "$APP_DIR" -czf "$BACKUP_DIR/$TS.tgz" .
|
||||
mv "$APP_DIR" "$OLD"
|
||||
fi
|
||||
|
||||
mv "$TMP" "$APP_DIR"
|
||||
cd "$APP_DIR"
|
||||
set -a
|
||||
. ./.env.production
|
||||
set +a
|
||||
|
||||
if ! pm2 restart "$PM2_NAME" --update-env; then
|
||||
pm2 start dist/main.js --name "$PM2_NAME" --update-env
|
||||
fi
|
||||
|
||||
sleep 8
|
||||
if ! curl -fsS "$HEALTH_URL" >/dev/null; then
|
||||
echo "Health check failed; rolling back $PM2_NAME"
|
||||
pm2 stop "$PM2_NAME" || true
|
||||
rm -rf "$APP_DIR.failed.$TS"
|
||||
mv "$APP_DIR" "$APP_DIR.failed.$TS"
|
||||
if [ -d "$OLD" ]; then
|
||||
mv "$OLD" "$APP_DIR"
|
||||
cd "$APP_DIR"
|
||||
set -a
|
||||
. ./.env.production
|
||||
set +a
|
||||
pm2 restart "$PM2_NAME" --update-env || pm2 start dist/main.js --name "$PM2_NAME" --update-env
|
||||
fi
|
||||
exit 5
|
||||
fi
|
||||
|
||||
rm -rf "$OLD"
|
||||
pm2 save >/dev/null
|
||||
echo "Deployed devops-platform-api from $SRC to $APP_DIR"
|
||||
Reference in New Issue
Block a user