# .gitea/workflows/manual-db-restore.yml # # DANGER: This workflow is DESTRUCTIVE. It restores the production database from a backup file. # It should be run manually with extreme caution. name: Manual - Restore Production Database from Backup on: workflow_dispatch: inputs: backup_filename: description: 'The exact filename of the backup (.sql.gz) located in /var/www/backups/' required: true confirmation: description: 'DANGER: This will WIPE the production DB. Type "restore-production-db" to confirm.' required: true default: 'do-not-run' jobs: restore-database: runs-on: projectium.com # This job runs on your self-hosted Gitea runner. env: # Use production database credentials for this entire job. DB_HOST: ${{ secrets.DB_HOST }} DB_USER: ${{ secrets.DB_USER_PROD }} DB_PASSWORD: ${{ secrets.DB_PASSWORD_PROD }} DB_NAME: ${{ secrets.DB_DATABASE_PROD }} BACKUP_DIR: '/var/www/backups' # Define a dedicated directory for backups steps: - name: Validate Secrets and Inputs run: | if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then echo "ERROR: One or more production database secrets are not set in Gitea repository settings." exit 1 fi if [ "${{ gitea.event.inputs.confirmation }}" != "restore-production-db" ]; then echo "ERROR: Confirmation phrase did not match. Aborting database restore." exit 1 fi if [ -z "${{ gitea.event.inputs.backup_filename }}" ]; then echo "ERROR: Backup filename cannot be empty." exit 1 fi echo "✅ Confirmation accepted. Proceeding with database restore." - name: 🚨 FINAL WARNING & PAUSE 🚨 run: | echo "*********************************************************************" echo "WARNING: YOU ARE ABOUT TO WIPE AND RESTORE THE PRODUCTION DATABASE." echo "This action is IRREVERSIBLE. Press Ctrl+C in the runner terminal NOW to cancel." echo "Restoring from file: ${{ gitea.event.inputs.backup_filename }}" echo "Sleeping for 10 seconds..." echo "*********************************************************************" sleep 10 - name: Step 1 - Stop Application Server run: | echo "Stopping all PM2 processes to release database connections..." pm2 stop all || echo "PM2 processes were not running." echo "✅ Application server stopped." - name: Step 2 - Drop and Recreate Database run: | echo "Dropping and recreating the production database..." # Connect as the superuser (postgres) to drop the database. # First, terminate all active connections to the database. sudo -u postgres psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_NAME}';" # Now, drop and recreate it. sudo -u postgres psql -c "DROP DATABASE IF EXISTS \"${DB_NAME}\";" sudo -u postgres psql -c "CREATE DATABASE \"${DB_NAME}\" WITH OWNER = ${DB_USER};" echo "✅ Database dropped and recreated successfully." - name: Step 3 - Restore Database from Backup run: | BACKUP_FILE_PATH="${BACKUP_DIR}/${{ gitea.event.inputs.backup_filename }}" echo "Restoring database from: $BACKUP_FILE_PATH" if [ ! -f "$BACKUP_FILE_PATH" ]; then echo "ERROR: Backup file not found at $BACKUP_FILE_PATH" exit 1 fi # Uncompress the gzipped file and pipe the SQL commands directly into psql. # This is efficient as it doesn't require an intermediate uncompressed file. gunzip < "$BACKUP_FILE_PATH" | PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" echo "✅ Database restore completed successfully." - name: Step 4 - Restart Application Server run: | echo "Restarting application server..." cd /var/www/flyer-crawler.projectium.com pm2 startOrReload ecosystem.config.cjs --env production && pm2 save echo "✅ Application server restarted."