Skip to content

Commit ec227c0

Browse files
committed
Store Postgres backup artifacts in R2 with temporary local staging only - PR_26168_246-r2-backed-postgres-backupsc
1 parent 03bbd49 commit ec227c0

17 files changed

Lines changed: 2130 additions & 8308 deletions

.env.example

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ GAMEFOUNDRY_SUPABASE_ANON_KEY=
1818
# Do not expose these values to browser JavaScript, HTML, reports, or screenshots.
1919
GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY=
2020
GAMEFOUNDRY_DATABASE_URL=
21+
22+
# Server-only Postgres backup storage.
23+
# Copy this file to .env and keep the active deployment target's prefix:
24+
# DEV /dev/backups/postgres/
25+
# IST /ist/backups/postgres/
26+
# UAT /uat/backups/postgres/
27+
# PRD /prd/backups/postgres/
28+
# R2 object prefixes are created by object upload; no manual folder creation is required.
29+
GAMEFOUNDRY_DB_BACKUP_STORAGE_PROVIDER=r2
30+
GAMEFOUNDRY_DB_BACKUP_PREFIX=/dev/backups/postgres/
31+
32+
# Optional temporary server-side pg_dump staging override.
33+
# Leave blank to use the OS temp directory. Do not point this at repo tmp/.
34+
GAMEFOUNDRY_DB_BACKUP_STAGING_DIR=
35+
36+
# Deprecated: final backup artifacts are uploaded to R2, not stored here.
2137
GAMEFOUNDRY_DB_BACKUP_DIR=
2238

2339
# Server-only project asset storage configuration.

docs_build/database/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ The database promotion lane is documented in [promotion-lane.md](promotion-lane.
3737

3838
## Backup And Restore
3939

40-
The operator-controlled backup and restore lane is documented in [backup-restore-lane.md](backup-restore-lane.md). Backup and restore use `GAMEFOUNDRY_DATABASE_URL` from `.env`; restore requires an explicit operator checklist and confirmation phrase before any destructive command runs.
40+
The operator-controlled backup and restore lane is documented in [backup-restore-lane.md](backup-restore-lane.md). Backup uses `GAMEFOUNDRY_DATABASE_URL` from `.env`, temporary server-side `pg_dump --format=custom` staging, and the configured R2 backup prefix for final artifacts. Restore remains scaffold-only until safe R2 restore is explicitly implemented and still requires an operator checklist and confirmation phrase before any future destructive command can run.
4141

4242
## Runbook
4343

docs_build/database/backup-restore-lane.md

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,77 @@ GAMEFOUNDRY_DATABASE_URL
1010

1111
Do not paste database URLs, passwords, service keys, or dump contents into reports.
1212

13-
## Backup Command Path
13+
## Backup Operation Path
1414

15-
Use the active `.env` as the source of the database connection. Do not pass deployment-target parameters.
15+
Use the active `.env` as the source of the database connection and R2 backup
16+
storage configuration. Do not pass deployment-target parameters.
1617

17-
PowerShell operator flow:
18+
Required backup storage variables:
1819

19-
```powershell
20-
$databaseUrl = (Get-Content .env |
21-
Where-Object { $_ -match '^GAMEFOUNDRY_DATABASE_URL=' } |
22-
Select-Object -First 1) -replace '^GAMEFOUNDRY_DATABASE_URL=', ''
23-
24-
$backupPath = Join-Path (Get-Location) 'backups\gamefoundry-backup.dump'
25-
pg_dump --dbname $databaseUrl --format custom --file $backupPath
20+
```text
21+
GAMEFOUNDRY_DB_BACKUP_STORAGE_PROVIDER=r2
22+
GAMEFOUNDRY_DB_BACKUP_PREFIX=/dev/backups/postgres/
2623
```
2724

25+
Copy-source target files must use the matching prefix:
26+
27+
- DEV: `/dev/backups/postgres/`
28+
- IST: `/ist/backups/postgres/`
29+
- UAT: `/uat/backups/postgres/`
30+
- PRD: `/prd/backups/postgres/`
31+
32+
R2 prefixes are object-key prefixes. They are created by upload and do not
33+
require manual folder creation.
34+
35+
Create Backup runs through Admin Operations and the Local API:
36+
37+
1. Validate the current database connection.
38+
2. Run server-side `pg_dump --format=custom`.
39+
3. Write the `.dump` only to temporary server-side staging.
40+
4. Upload the `.dump` to the configured R2 backup prefix.
41+
5. Delete the temporary local dump after upload.
42+
6. Report PASS/FAIL without exposing database passwords or storage secrets.
43+
44+
`GAMEFOUNDRY_DB_BACKUP_DIR` is deprecated for final backup storage. If
45+
temporary staging must be overridden, use
46+
`GAMEFOUNDRY_DB_BACKUP_STAGING_DIR` and keep it outside repo `tmp/`.
47+
2848
Validation expectation:
2949

3050
- `.env` exists
3151
- `GAMEFOUNDRY_DATABASE_URL` is present
32-
- `pg_dump` is available on the operator machine
33-
- the backup file is written outside tracked source paths
34-
- reports state only the backup path and success/failure, not the connection string
52+
- `GAMEFOUNDRY_DB_BACKUP_STORAGE_PROVIDER` is `r2`
53+
- `GAMEFOUNDRY_DB_BACKUP_PREFIX` matches the active target prefix
54+
- R2 endpoint, bucket, access key, and secret key are configured server-side
55+
- `pg_dump` is available on the Local API host
56+
- the local dump is temporary staging only and is deleted after upload
57+
- reports state only R2 key, filename, size, timestamp, environment, and
58+
success/failure, not the connection string or storage secrets
3559

3660
Backup metadata must be recorded without secrets:
3761

3862
- `backupName`
3963
- `databaseName`
4064
- `createdAt`
4165
- `migrationVersion`
66+
- `environment`
67+
- `fileName`
68+
- `r2Key`
69+
- `sizeBytes`
4270

4371
Use `schema_migrations` to derive `migrationVersion`; do not use
4472
`platform_settings` for backup migration state.
4573

4674
## Restore Checklist
4775

48-
Restore is destructive. Do not run restore unless every checklist item is complete:
76+
Restore is destructive. Admin Operations Restore From Backup remains
77+
scaffold-only until a later PR explicitly implements safe R2 restore. Do not
78+
run restore unless every checklist item is complete:
4979

5080
- Confirm the target `.env` is the intended database.
5181
- Run `node .\scripts\validate-runtime-connections.mjs` and record PASS/FAIL.
5282
- Validate the current `schema_migrations` state before applying restore.
53-
- Confirm the backup file path and checksum.
83+
- Confirm the R2 backup key, filename, size, timestamp, and checksum.
5484
- Confirm the backup metadata `migrationVersion`.
5585
- Confirm the application is stopped or in a maintenance window.
5686
- Confirm owner approval.
@@ -61,27 +91,15 @@ Restore is destructive. Do not run restore unless every checklist item is comple
6191

6292
## Restore Command Path
6393

64-
PowerShell operator flow:
65-
66-
```powershell
67-
$confirmation = Read-Host 'Type RESTORE CONFIRMED to restore the configured database'
68-
if ($confirmation -ne 'RESTORE CONFIRMED') {
69-
throw 'Restore cancelled because confirmation did not match.'
70-
}
71-
72-
$databaseUrl = (Get-Content .env |
73-
Where-Object { $_ -match '^GAMEFOUNDRY_DATABASE_URL=' } |
74-
Select-Object -First 1) -replace '^GAMEFOUNDRY_DATABASE_URL=', ''
75-
76-
$backupPath = Join-Path (Get-Location) 'backups\gamefoundry-backup.dump'
77-
pg_restore --dbname $databaseUrl --clean --if-exists --single-transaction $backupPath
78-
```
94+
No active Restore From Backup command path is approved in this PR. Safe R2
95+
restore must be explicitly implemented and reviewed before `pg_restore` is
96+
available through Admin Operations.
7997

8098
Restore reports must include:
8199

82100
- target host, port, and database name only
83101
- backup metadata: `backupName`, `databaseName`, `createdAt`, and `migrationVersion`
84-
- backup file path
102+
- R2 backup key and filename
85103
- backup checksum
86104
- migration state validation before restore
87105
- confirmation checklist status

docs_build/database/runbook.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,32 +95,41 @@ execution is allowed.
9595

9696
## Backup
9797

98-
Use the backup path documented in
98+
Use the backup operation path documented in
9999
[backup-restore-lane.md](backup-restore-lane.md). Backups use
100-
`GAMEFOUNDRY_DATABASE_URL` from `.env`.
100+
`GAMEFOUNDRY_DATABASE_URL` from `.env`, write `pg_dump --format=custom` only
101+
to temporary server-side staging, and upload the final `.dump` artifact to the
102+
configured R2 backup prefix.
101103

102104
Minimum backup checklist:
103105

104106
- Confirm `.env` points at the intended database.
105107
- Confirm `pg_dump` is installed.
108+
- Confirm `GAMEFOUNDRY_DB_BACKUP_STORAGE_PROVIDER=r2`.
109+
- Confirm `GAMEFOUNDRY_DB_BACKUP_PREFIX` matches the active target:
110+
`/dev/backups/postgres/`, `/ist/backups/postgres/`,
111+
`/uat/backups/postgres/`, or `/prd/backups/postgres/`.
106112
- Record backup metadata: `backupName`, `databaseName`, `createdAt`, and
107113
`migrationVersion`.
108-
- Write backup files outside tracked source paths.
109-
- Record backup path and checksum only.
114+
- Confirm R2 prefixes require no manual folder creation.
115+
- Confirm temporary local staging is deleted after successful upload.
116+
- Record R2 key, filename, size, timestamp, environment, and checksum only.
110117
- Do not write secrets, full URLs, service role keys, or dump contents to
111118
reports.
112119

113120
## Restore
114121

115122
Use the restore path documented in
116-
[backup-restore-lane.md](backup-restore-lane.md). Restore is destructive and
117-
must require explicit operator confirmation.
123+
[backup-restore-lane.md](backup-restore-lane.md). Restore is destructive,
124+
remains scaffold-only until safe R2 restore is explicitly implemented, and must
125+
require explicit operator confirmation before any future destructive command can
126+
run.
118127

119128
Minimum restore checklist:
120129

121130
- Confirm `.env` points at the intended target database.
122131
- Validate the current `schema_migrations` state before applying restore.
123-
- Confirm the backup file path and checksum.
132+
- Confirm the R2 backup key, filename, size, timestamp, and checksum.
124133
- Confirm backup metadata, including `migrationVersion`.
125134
- Stop the application or enter a maintenance window.
126135
- Confirm owner approval.

docs_build/dev/codex_commands.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2613,3 +2613,33 @@ Required reports:
26132613

26142614
Packaging:
26152615
- `tmp/PR_26168_235-owner-operations-scope-cleanup_delta.zip`
2616+
2617+
2618+
## PR_26168_246-r2-backed-postgres-backups
2619+
2620+
Changes:
2621+
- Read `docs_build/dev/PROJECT_INSTRUCTIONS.md`.
2622+
- Verified the current branch is `main`.
2623+
- Updated Create Backup to stage `pg_dump --format=custom` output in temporary server-side storage only.
2624+
- Uploaded final `.dump` artifacts to the configured R2 backup prefix.
2625+
- Added backup storage provider and prefix configuration for R2-backed Postgres backups.
2626+
- Deprecated `GAMEFOUNDRY_DB_BACKUP_DIR` for final backup storage.
2627+
- Kept Restore From Backup scaffold-only.
2628+
2629+
Validation:
2630+
- `node --check` for changed JS/MJS and Playwright files.
2631+
- `node --test tests/dev-runtime/PostgresBackupService.test.mjs`
2632+
- `npx playwright test tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs --grep "Admin Operations" --reporter=line`
2633+
- `git diff --check -- touched files`
2634+
- Static checks for no `.dump` artifacts and no inline Admin Operations HTML violations.
2635+
- Full samples smoke skipped because sample JSON and sample runtime behavior were not touched.
2636+
2637+
Required reports:
2638+
- `docs_build/dev/reports/codex_review.diff`
2639+
- `docs_build/dev/reports/codex_changed_files.txt`
2640+
- `docs_build/dev/reports/PR_26168_246-r2-backed-postgres-backups.md`
2641+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
2642+
- `docs_build/dev/reports/coverage_changed_js_guardrail.txt`
2643+
2644+
Packaging:
2645+
- `tmp/PR_26168_246-r2-backed-postgres-backups_delta.zip`

docs_build/dev/commit_comment.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Admin operations order and safety - PR_26168_236-admin-operations-order-and-safety
1+
R2 backed Postgres backups - PR_26168_246-r2-backed-postgres-backups
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# PR_26168_246-r2-backed-postgres-backups
2+
3+
## Branch Validation
4+
5+
PASS - Current branch is `main`; expected branch is `main`.
6+
7+
Local branches found:
8+
9+
```text
10+
main
11+
```
12+
13+
## Summary
14+
15+
Create Backup now validates the current database connection through Admin Operations, runs server-side `pg_dump --format=custom`, writes the dump only to temporary server-side staging, uploads the final `.dump` to the configured R2 backup prefix, and removes staging after upload. `GAMEFOUNDRY_DB_BACKUP_DIR` is deprecated for final backup storage.
16+
17+
Restore From Backup remains scaffold-only.
18+
19+
## Requirement Checklist
20+
21+
| Requirement | Status | Evidence |
22+
| --- | --- | --- |
23+
| Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` first | PASS | Read before implementation and followed BUILD/report/ZIP rules. |
24+
| Hard stop unless current branch is `main` | PASS | `git branch --show-current` returned `main`; local branch list contained `main`. |
25+
| Upload final Create Backup artifacts to R2 instead of permanently storing in `GAMEFOUNDRY_DB_BACKUP_DIR` | PASS | `createPostgresBackup` uploads via configured R2 storage and only reports the R2 key. |
26+
| Validate current DB connection before backup | PASS | `createBackupAction()` still calls `validateBackupDatabaseConnection()` before `createPostgresBackup()`. |
27+
| Run server-side `pg_dump --format=custom` | PASS | Backup service keeps `--format=custom`; Node test asserts the dump command args. |
28+
| Write dump to temporary server-side staging only | PASS | Backup service uses OS temp or `GAMEFOUNDRY_DB_BACKUP_STAGING_DIR`; repo `tmp/` staging is rejected. |
29+
| Upload `.dump` to configured R2 backup prefix | PASS | `createConfiguredBackupStorage()` loads `GAMEFOUNDRY_DB_BACKUP_PREFIX`; configured fake R2 validation captured the PUT. |
30+
| Delete temporary local dump after successful upload | PASS | Node test asserts the staged file and staging directory are removed after PASS. |
31+
| Report PASS/FAIL with R2 key, filename, size, timestamp, and environment | PASS | Success and upload-failure results include backup metadata; Playwright asserts visible R2 key PASS/FAIL behavior. |
32+
| Add safe env placeholders and copy-source guidance | PASS | `.env.example` includes provider, prefix, staging note, and deprecated backup-dir guidance. |
33+
| Use environment-specific prefixes for DEV, IST, UAT, PRD | PASS | `.env.example` and database docs list `/dev/`, `/ist/`, `/uat/`, and `/prd/backups/postgres/`; config validates allowed prefixes. |
34+
| R2 prefixes must not require manual folder creation | PASS | `.env.example` and database docs state prefixes are object-key prefixes created by upload. |
35+
| Treat `GAMEFOUNDRY_DB_BACKUP_DIR` as deprecated final storage | PASS | Code ignores it for final storage and only appends a deprecation note when present. |
36+
| Rename/document local staging as temporary only | PASS | Added `GAMEFOUNDRY_DB_BACKUP_STAGING_DIR` as optional temporary staging override. |
37+
| Do not store backups in repo `tmp/` | PASS | Staging root under repo `tmp/` returns FAIL; no `.dump` files were found. |
38+
| Do not commit backup files | PASS | `rg --files -g '*.dump'` returned no matches. |
39+
| Do not expose DB password, storage keys, or secrets | PASS | DB password stays in `PGPASSWORD`; tests assert command args and UI/report messages do not expose secret fixture values. |
40+
| Restore From Backup remains guarded/scaffold-only | PASS | Restore action remains `SKIP`; docs state no approved safe R2 restore path exists in this PR. |
41+
| Use existing storage provider contract where possible | PASS | Backup uses the existing R2 signed storage module and `putObject` contract, with backup-specific config. |
42+
| Do not add unrelated Project Package, migration, or workspace behavior | PASS | Changes are limited to backup storage, Admin Operations backup diagnostics/tests, env/docs, and required reports. |
43+
| Do not run full samples smoke | PASS | Full samples smoke was skipped; no sample JSON or sample runtime behavior changed. |
44+
45+
## Validation Lane Report
46+
47+
Impacted lane: Admin Operations backup action, Local API backup service, R2 storage provider config, and database backup docs.
48+
49+
Skipped broad lanes: full samples, engine-wide runtime, Project Package, migration, and Project Workspace lanes. They were safe to skip because no sample JSON, engine core, package import/export behavior, migration runner, or Project Workspace runtime files changed.
50+
51+
PASS - Syntax/static checks:
52+
53+
```text
54+
node --check src/dev-runtime/storage/storage-config.mjs
55+
node --check src/dev-runtime/storage/r2-project-asset-storage.mjs
56+
node --check src/dev-runtime/database/postgres-backup-service.mjs
57+
node --check src/dev-runtime/server/local-api-router.mjs
58+
node --check assets/theme-v2/js/admin-operations.js
59+
node --check src/engine/api/admin-operations-api-client.js
60+
node --check tests/dev-runtime/PostgresBackupService.test.mjs
61+
node --check tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs
62+
```
63+
64+
PASS - Targeted Local API backup and R2 upload validation:
65+
66+
```text
67+
node --test tests/dev-runtime/PostgresBackupService.test.mjs
68+
```
69+
70+
Result: 7 passed.
71+
72+
Covered:
73+
74+
- Missing R2 backup storage visible FAIL.
75+
- Missing `pg_dump` visible FAIL.
76+
- Repo `tmp/` staging rejection.
77+
- `pg_dump --format=custom` command construction without DB password in args.
78+
- Successful R2 upload metadata with R2 key, filename, size, timestamp, and environment.
79+
- Temporary staged dump and staging directory cleanup after successful upload.
80+
- R2 upload failure reports key metadata and cleans staging.
81+
- Configured R2 provider path against a fake local R2-compatible server.
82+
83+
PASS - Targeted Admin Operations Playwright / visible PASS/FAIL behavior:
84+
85+
```text
86+
npx playwright test tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs --grep "Admin Operations" --reporter=line
87+
```
88+
89+
Result: 4 passed.
90+
91+
Covered:
92+
93+
- Create Backup visible PASS message includes the R2 key.
94+
- Create Backup visible FAIL message is actionable when backup storage config is missing.
95+
- Restore From Backup remains scaffold-only.
96+
- Admin Operations order, safety table, non-admin block, and DEV-only reseed gating remain intact.
97+
98+
PASS - Static artifact and HTML checks:
99+
100+
```text
101+
rg --files -g '*.dump' -g '!node_modules' -g '!tests/results' -g '!tmp'
102+
rg --pcre2 -n "<script(?![^>]+src=)|<style|\s(onclick|onchange|oninput|onsubmit|onkeydown|onkeyup|onload)=" admin/operations.html
103+
git diff --check
104+
```
105+
106+
Results:
107+
108+
- No `.dump` files found.
109+
- No inline Admin Operations HTML violations found.
110+
- `git diff --check` passed with line-ending warnings only.
111+
112+
PASS/WARN - Playwright V8 coverage report generated:
113+
114+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
115+
- `docs_build/dev/reports/coverage_changed_js_guardrail.txt`
116+
- WARN entries are expected for Node-only Local API/storage modules that browser V8 coverage cannot collect.
117+
118+
## Manual Validation Notes
119+
120+
PASS - The tested R2 path used a local fake R2-compatible server and the real configured storage factory, not real production credentials.
121+
122+
PASS - Successful backup tests verified that temporary local staging is removed after upload.
123+
124+
PASS - No real database password, storage access key, storage secret key, service role key, full database URL, or raw dump content was written to reports or UI messages.
125+
126+
PASS - Actual live backup creation still requires a real `.env`, reachable Postgres database, installed PostgreSQL client tools on the Local API host, and configured R2 credentials.
127+
128+
SKIP - Full samples smoke was not run because this PR only changes Admin Operations backup feedback, Local API backup service/storage behavior, env/docs, and targeted tests.
129+
130+
## Artifacts
131+
132+
PASS - Required reports:
133+
134+
- `docs_build/dev/reports/codex_review.diff`
135+
- `docs_build/dev/reports/codex_changed_files.txt`
136+
- `docs_build/dev/reports/PR_26168_246-r2-backed-postgres-backups.md`
137+
138+
PASS - Repo-structured delta ZIP:
139+
140+
- `tmp/PR_26168_246-r2-backed-postgres-backups_delta.zip`

0 commit comments

Comments
 (0)