167 lines
4.3 KiB
Bash
Executable File
167 lines
4.3 KiB
Bash
Executable File
#!/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"
|