Skip to content

Automated Production Backup #26

Automated Production Backup

Automated Production Backup #26

Workflow file for this run

name: Automated Production Backup
on:
# Run weekly on Sunday at 2 AM UTC
schedule:
- cron: '0 2 * * 0'
# Allow manual trigger
workflow_dispatch:
inputs:
include_database:
description: '⚠️ Include database backup (uses Neon compute - only needed for monthly archives or before migrations)'
required: false
type: boolean
default: false
keep_uncompressed:
description: 'Keep uncompressed backup files (debugging only - uses extra disk space)'
required: false
type: boolean
default: false
jobs:
backup:
name: Backup Application Data
runs-on: ubuntu-latest
steps:
- name: Notify backup started
if: always()
run: |
curl -H "Content-Type: application/json" \
-d '{
"embeds": [{
"title": "📝 [Blog] Backup Started",
"description": "Daily backup process initiated",
"color": 3447003,
"fields": [
{"name": "Time", "value": "'"$(date -u)"'", "inline": true},
{"name": "Server", "value": "Production VPS", "inline": true}
]
}]
}' \
${{ secrets.DISCORD_WEBHOOK_URL }}
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
- name: Test SSH connection
run: |
ssh -i ~/.ssh/id_ed25519 cheloniixd@${{ secrets.SERVER_IP }} "echo '✅ SSH connection successful'"
- name: Run backup script
run: |
ssh -i ~/.ssh/id_ed25519 cheloniixd@${{ secrets.SERVER_IP }} \
"cd /opt/cpta_blog && bash deployment/backup.sh --upload ${{ inputs.include_database == true && '--include-database' || '' }} ${{ inputs.keep_uncompressed == true && '--keep-uncompressed' || '' }}"
- name: Notify backup success
if: success()
run: |
DB_STATUS="${{ inputs.include_database == true && '✅ Backed up (manual)' || '⏭️ Skipped (Neon auto-backup)' }}"
curl -H "Content-Type: application/json" \
-d '{
"embeds": [{
"title": "✅ [Blog] Backup Completed",
"description": "All data has been backed up",
"color": 5763719,
"fields": [
{"name": "Time", "value": "'"$(date -u)"'", "inline": true},
{"name": "Retention", "value": "30 days", "inline": true},
{"name": "📦 Database", "value": "'"$DB_STATUS"'", "inline": false},
{"name": "💾 Redis", "value": "✅ Backed up", "inline": true},
{"name": "⚙️ Config", "value": "✅ Backed up", "inline": true},
{"name": "🔒 SSL Certs", "value": "✅ Backed up", "inline": true},
{"name": "📋 Logs", "value": "✅ Last 24h backed up", "inline": true},
{"name": "☁️ Google Drive", "value": "✅ Uploaded & Verified", "inline": true}
],
"footer": {"text": "Blog - Automated Backup"}
}]
}' \
${{ secrets.DISCORD_WEBHOOK_URL }}
- name: Notify backup failure
if: failure()
run: |
curl -H "Content-Type: application/json" \
-d '{
"embeds": [{
"title": "❌ [Blog] Backup Failed",
"description": "The automated backup process has failed",
"color": 15158332,
"fields": [
{"name": "Time", "value": "'"$(date -u)"'", "inline": true},
{"name": "Server", "value": "Production VPS", "inline": true},
{"name": "⚠️ Action Required", "value": "1. SSH to server and check logs\n2. Verify containers running\n3. Check disk space\n4. Run manual backup", "inline": false}
],
"footer": {"text": "Blog - Automated Backup"}
}]
}' \
${{ secrets.DISCORD_WEBHOOK_URL }}
- name: Backup summary
if: success()
run: |
echo "## 🗄️ Backup Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Time:** $(date -u)" >> $GITHUB_STEP_SUMMARY
echo "- **Server:** ${{ secrets.SERVER_IP }}" >> $GITHUB_STEP_SUMMARY
echo "- **Database included:** ${{ inputs.include_database || 'false' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Keep uncompressed:** ${{ inputs.keep_uncompressed || 'false' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### What was backed up:" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.include_database }}" == "true" ]; then
echo "- ✅ PostgreSQL database (manually included)" >> $GITHUB_STEP_SUMMARY
else
echo "- ⏭️ PostgreSQL database (SKIPPED - Neon provides automatic 7-day point-in-time recovery)" >> $GITHUB_STEP_SUMMARY
fi
echo "- ✅ Redis data (rate limiting, cache)" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Configuration files (.env, nginx, docker-compose)" >> $GITHUB_STEP_SUMMARY
echo "- ✅ SSL certificates" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Application logs (last 24 hours)" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Git commit reference" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Uploaded to Google Drive" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Retention:** 30 days (local VPS + Google Drive)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.include_database }}" != "true" ]; then
echo "**Note:** Database backup was skipped to save Neon compute hours. Neon provides automatic 7-day point-in-time recovery. Check the '⚠️ Include database backup' option when running manually if you need a database snapshot." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Backup completed successfully!" >> $GITHUB_STEP_SUMMARY
- name: Summary on failure
if: failure()
run: |
echo "## ❌ Backup Failed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The automated backup process has failed." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Possible reasons:**" >> $GITHUB_STEP_SUMMARY
echo "- Database connection issues" >> $GITHUB_STEP_SUMMARY
echo "- Docker containers not running" >> $GITHUB_STEP_SUMMARY
echo "- Disk space issues" >> $GITHUB_STEP_SUMMARY
echo "- SSH connection problems" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Action required:**" >> $GITHUB_STEP_SUMMARY
echo "1. SSH to server and check logs" >> $GITHUB_STEP_SUMMARY
echo "2. Verify containers are running: \`docker compose -f docker-compose.prod.yml ps\`" >> $GITHUB_STEP_SUMMARY
echo "3. Check disk space: \`df -h\`" >> $GITHUB_STEP_SUMMARY
echo "4. Run manual backup: \`bash deployment/backup.sh\`" >> $GITHUB_STEP_SUMMARY