-
Notifications
You must be signed in to change notification settings - Fork 116
176 lines (159 loc) · 6.23 KB
/
Copy pathbackup-verification.yml
File metadata and controls
176 lines (159 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
name: Database Backup Verification
on:
schedule:
- cron: '0 3 * * 0' # Every Sunday at 03:00 UTC
workflow_dispatch:
jobs:
backup-verify:
name: Create & Restore Backup
runs-on: ubuntu-latest
services:
postgres_source:
image: postgres:15-alpine
env:
POSTGRES_DB: future_source
POSTGRES_USER: future_admin
POSTGRES_PASSWORD: bkp_test_pw
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres_restore:
image: postgres:15-alpine
env:
POSTGRES_DB: future_restore
POSTGRES_USER: future_admin
POSTGRES_PASSWORD: bkp_test_pw
ports:
- 5433:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.3.0
with:
node-version: '20'
cache: npm
- name: Install dependencies
run: npm ci
- name: Install PostgreSQL client tools
run: |
sudo apt-get update -q
sudo apt-get install -y -q postgresql-client
- name: Apply migrations to source database
run: npx prisma migrate deploy
working-directory: backend
env:
DATABASE_URL: postgresql://future_admin:bkp_test_pw@localhost:5432/future_source
- name: Seed known data into source database
run: |
PGPASSWORD=bkp_test_pw psql \
-h localhost -p 5432 -U future_admin -d future_source \
-c "INSERT INTO \"User\" (id, email, \"createdAt\", \"updatedAt\")
VALUES ('seed-user-1', 'backup-test@example.com', NOW(), NOW())
ON CONFLICT DO NOTHING;"
continue-on-error: true # table name may differ; real seed should use prisma seed
- name: Create backup via backup manager
id: create_backup
run: |
node --input-type=module <<'EOF'
import { createBackup } from './src/backup/manager.js';
const meta = await createBackup({ tag: 'ci-verify' });
const fs = await import('fs/promises');
await fs.appendFile(process.env.GITHUB_OUTPUT, `backup_file=${meta.file}\n`);
console.log('Backup created:', meta.file, 'size:', meta.size);
EOF
working-directory: backend
env:
DATABASE_URL: postgresql://future_admin:bkp_test_pw@localhost:5432/future_source
BACKUP_DIR: /tmp/ci-backups
- name: Verify backup checksum
run: |
node --input-type=module <<'EOF'
import { verifyBackup } from './src/backup/manager.js';
const file = process.env.BACKUP_FILE;
const result = await verifyBackup(file);
if (!result.valid) {
console.error('Checksum mismatch — backup is corrupt');
process.exit(1);
}
console.log('Checksum valid:', result.current);
EOF
working-directory: backend
env:
BACKUP_FILE: ${{ steps.create_backup.outputs.backup_file }}
BACKUP_DIR: /tmp/ci-backups
- name: Restore backup to fresh database
run: |
node --input-type=module <<'EOF'
import { restoreBackup } from './src/backup/manager.js';
const result = await restoreBackup(process.env.BACKUP_FILE, {
targetDatabase: 'future_restore',
});
console.log('Restore result:', result.status);
EOF
working-directory: backend
env:
DATABASE_URL: postgresql://future_admin:bkp_test_pw@localhost:5433/future_restore
BACKUP_FILE: ${{ steps.create_backup.outputs.backup_file }}
BACKUP_DIR: /tmp/ci-backups
- name: Run migrations against restored database
run: npx prisma migrate deploy
working-directory: backend
env:
DATABASE_URL: postgresql://future_admin:bkp_test_pw@localhost:5433/future_restore
- name: Query restored database for expected data
run: |
COUNT=$(PGPASSWORD=bkp_test_pw psql \
-h localhost -p 5433 -U future_admin -d future_restore \
-tA -c "SELECT COUNT(*) FROM \"User\";")
echo "Row count in restored User table: $COUNT"
if [ -z "$COUNT" ]; then
echo "ERROR: could not query restored database"
exit 1
fi
echo "Restored database is queryable."
- name: Upload backup artifact for audit
if: always()
uses: actions/upload-artifact@v4
with:
name: backup-verify-${{ github.run_id }}
path: /tmp/ci-backups/
retention-days: 7
alert-on-failure:
name: Alert on Failure
runs-on: ubuntu-latest
needs: backup-verify
if: failure()
steps:
- name: Open GitHub issue for backup failure
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const title = `[Backup Alert] Weekly backup verification failed — ${new Date().toISOString().slice(0,10)}`;
const body = [
'## Database Backup Verification Failure',
'',
`The weekly backup verification job failed on run [#${context.runId}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`,
'',
'**Immediate action required:** a backup that cannot be restored is worthless.',
'',
'### Checklist',
'- [ ] Review the failed workflow run logs',
'- [ ] Verify `DATABASE_URL` and `BACKUP_DIR` secrets are correct',
'- [ ] Run a manual restore drill against the latest backup',
'- [ ] Resolve root cause and re-run the workflow',
].join('\n');
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['incident', 'backup'],
});