Backup Strategy¶
Independent backup of D1 databases and KV namespaces — outside Cloudflare.
| Status | Implemented |
| Version | 1.0 |
| Date | March 2026 |
| Classification | INTERNAL |
| Scripts | infra/ucca-infra/scripts/backup/ |
Priority 1 Threat Model Gap
This backup system closes the Priority 1 gap identified in the Threat Model: no independent backup of D1 databases or R2 objects. Cloudflare internal redundancy is not a backup strategy — if the account is compromised or locked, all data is lost without independent copies.
What Gets Backed Up¶
| Resource | Type | ID | Frequency |
|---|---|---|---|
| ops-db | D1 database | 00daba3d-2d65-4ae2-b85a-e56d25ec2b02 |
Daily |
| rtopacks-db | D1 database | 334ac8fb-9850-48c0-9da0-b56c55640e98 |
Daily |
| LEADS | KV namespace | d4434c5cf1b3436f9cf6882e22a70af7 |
Daily |
Not yet backed up:
- rtopacks-media (R2 bucket) — currently empty, not in use. Add when media uploads begin.
Where Backups Go¶
Google Drive via the existing Google Workspace for Education account (admin@ucca.online). Free storage, independent of Cloudflare.
Google Drive Structure¶
UCCA Backups/
d1/
ops-db/
2026-03-05T03-00-00Z.sql.gz
2026-03-06T03-00-00Z.sql.gz
rtopacks-db/
2026-03-05T03-00-00Z.sql.gz
2026-03-06T03-00-00Z.sql.gz
kv/
leads/
2026-03-05T03-00-00Z.json.gz
manifests/
2026-03-05T03-00-00Z.json
2026-03-06T03-00-00Z.json
Files are named with ISO 8601 UTC timestamps. All files are gzip compressed.
How It Works¶
The backup system is a local shell script running on the Mac Mini. It uses wrangler to export data from Cloudflare and rclone to upload to Google Drive. No Cloudflare Workers are involved — credentials must not live inside Cloudflare.
Architecture¶
Mac Mini (launchd, daily 03:00 AEST)
→ wrangler d1 export (D1 → local SQL file)
→ wrangler kv key list/get (KV → local JSON file)
→ gzip compress
→ rclone copy to Google Drive
→ manifest JSON uploaded
→ local temp files cleaned up
One-Time Setup¶
Prerequisites¶
# Install rclone if not installed
brew install rclone
# Configure rclone with Google Drive
rclone config
# → New remote
# → Name: gdrive
# → Type: drive (Google Drive)
# → Follow OAuth2 browser flow for admin@ucca.online
# → Done
# Create the root folder in Google Drive
# (rclone will auto-create subfolders)
Install launchd Job¶
# Copy plist to LaunchAgents
cp infra/ucca-infra/scripts/backup/com.ucca.backup.plist ~/Library/LaunchAgents/
# Load the job (will run daily at 03:00 local time)
launchctl load ~/Library/LaunchAgents/com.ucca.backup.plist
# Verify it's loaded
launchctl list | grep com.ucca.backup
Uninstall launchd Job¶
launchctl unload ~/Library/LaunchAgents/com.ucca.backup.plist
rm ~/Library/LaunchAgents/com.ucca.backup.plist
Running Backups¶
Manual Backup¶
cd infra/ucca-infra/scripts/backup/
# Full backup
./cf-backup.sh
# Dry run (show what would happen, don't upload)
./cf-backup.sh --dry-run
Verify Backups¶
Verification checks:
- Each database has a backup file in Google Drive
- Latest backup is within 48 hours
- File sizes are above minimum thresholds
- Gzip integrity is valid
- D1 backups contain SQL statements
- KV backups contain valid JSON
Restore from Backup¶
# List available backups
./cf-restore.sh list d1 ops-db
./cf-restore.sh list kv leads
# Restore latest
./cf-restore.sh d1 ops-db latest
./cf-restore.sh kv leads latest
# Restore specific timestamp
./cf-restore.sh d1 rtopacks-db 2026-03-05T03-00-00Z
Destructive Operation
Restore overwrites existing data. You must type RESTORE to confirm. Test restores on a throwaway D1 database first.
KV Restore Behaviour¶
KV restore is additive — it writes all keys from the backup but does not delete keys that aren't in the backup. If you need a clean slate, delete the namespace and recreate it before restoring.
Retention¶
Manual for now. Daily backups at current data sizes are tiny (KBs). Clean up files older than 90 days periodically:
Automated retention will be implemented when the backup migrates to S3 (lifecycle rules).
Logs¶
launchd output goes to:
Check for errors:
Migration Path to AWS S3¶
When AWS startup credits are approved:
- Create S3 bucket
ucca-backupsinap-southeast-2 - Swap
rclone copy gdrive:...foraws s3 cp s3://...in the script - Add GitHub Actions workflow for automated daily runs (eliminates Mac Mini dependency)
- S3 lifecycle rules replace manual retention (auto-delete after 90 days)
- Enable S3 versioning for additional protection
- Update this document
Scripts Reference¶
All scripts live in infra/ucca-infra/scripts/backup/:
| Script | Purpose |
|---|---|
cf-backup.sh |
Main backup — exports D1 and KV, uploads to Google Drive |
cf-verify.sh |
Verification — checks backups exist, are recent, and are valid |
cf-restore.sh |
Restore — downloads from Google Drive and restores to D1/KV |
com.ucca.backup.plist |
launchd job definition for daily 03:00 AEST execution |
Version History¶
| Version | Date | Change | Author |
|---|---|---|---|
| 1.0 | 2026-03-06 | Initial creation — backup strategy and scripts | Claude Code |