Solanasis Backup Strategy
Last updated: 2026-03-26
What’s Backed Up
| Service | Method | Destination | Schedule | Retention | Typical Size |
|---|---|---|---|---|---|
| Baserow (CRM) | pg_dump (-Fc) + media tar | Cloudflare R2 + local | Daily 2:30 AM | 7 daily, 4 weekly | ~6.4 MB encrypted |
| Infisical (Secrets) | pg_dump + .env | GitHub (encrypted) + local | Daily 2:00 AM | 7 daily, 4 weekly | ~1 MB encrypted |
| SilverBullet (Wiki) | Git (mounts solanasis-docs) | GitHub | On commit | Full history | N/A |
| ERPNext (ERP) | Not yet backed up | — | — | — | — |
Baserow Backup
What’s Captured
- PostgreSQL database (compressed custom format via
pg_dump -Fc) — all tables, schema, formulas, views, permissions, field configurations (~13 MB uncompressed) - Media files (
/baserow/data/media/) — uploaded file field attachments (currently minimal — ~300 KB after template cleanup)
What’s NOT Captured (By Design)
- Docker container image (reproducible from
docker pull baserow/baserow:2.1.6) - Caddy reverse proxy config (regenerated on container start)
- Redis cache (regenerated automatically)
- Container configuration (
docker-compose.yml— tracked in git on server at/home/zasage/_solanasis/baserow/)
Template Cleanup (2026-03-26)
Baserow ships with 132 built-in template workspaces (e.g., “Santa’s Logistics”, “Car Dealership Inventory”) containing ~128 MB of sample media files and 752 tables. These are not user data — they’re demo databases bundled for the template gallery.
- Deleted: All 132 template workspaces + 1,859 orphaned media files via Django ORM cascading delete
- Prevention:
SYNC_TEMPLATES_ON_STARTUP=falseadded todocker-compose.ymlto prevent re-creation on container restart - Impact: Encrypted backup size dropped from ~136 MB to ~6.4 MB
Backup Script
Location: solanasis-docs/scheduled-tasks/scripts/backup-baserow.py
# Run backup manually (from solanasis-docs directory)
python scheduled-tasks/scripts/backup-baserow.py
# Run verification
python scheduled-tasks/scripts/backup-baserow.py --verifyTechnical Notes
- Peer authentication: PostgreSQL in the all-in-one container runs as the
postgresOS user. Alldocker execcommands use-u postgresfor peer auth (no password needed) - Secrets loading: Secrets are loaded from
.envfiles at runtime (notmanage_secrets.py, which has ~30s startup overhead from Infisical auth). The.envfiles are synced from Infisical viasync_env.py - R2 API: Uses Cloudflare REST API (Bearer token auth via
CLOUDFLARE_API_TOKEN), not S3-compatible API — no separate R2/S3 credentials needed - Git Bash path conversion: If running Docker commands manually from Git Bash on Windows, prefix with
MSYS_NO_PATHCONV=1to prevent Unix path conversion (e.g.,/baserow/databeing rewritten toC:/Program Files/Git/baserow/data) - OpenSSL location: Uses Git for Windows bundled OpenSSL at
C:\Program Files\Git\mingw64\bin\openssl.exe
Storage Locations
Cloudflare R2 (primary — offsite):
solanasis-backups/
└── baserow/
├── daily/baserow-backup-YYYY-MM-DD.tar.gz.enc
└── weekly/baserow-backup-YYYY-WNN.tar.gz.enc
Local (secondary — fast restore):
C:\_my\baserow-backups\
├── daily\baserow-backup-YYYY-MM-DD.tar.gz.enc
└── weekly\baserow-backup-YYYY-WNN.tar.gz.enc
Encryption
- AES-256-CBC with PBKDF2 (600,000 iterations)
- Passphrase:
BASEROW_BACKUP_PASSPHRASEin Infisical/shared/ - Also store passphrase in Bitwarden as a backup
Restore Procedure
Prerequisites: Docker running, fresh Baserow container started with SYNC_TEMPLATES_ON_STARTUP=false
# 1. Decrypt the backup
openssl enc -aes-256-cbc -d -salt -pbkdf2 -iter 600000 \
-in baserow-backup-YYYY-MM-DD.tar.gz.enc \
-out baserow-backup.tar.gz \
-pass pass:YOUR_PASSPHRASE
# 2. Extract the archive
tar xzf baserow-backup.tar.gz
# Produces: baserow_dump.dump, baserow-media.tar.gz
# 3. Restore the database (must use -u postgres for peer auth)
docker cp baserow_dump.dump baserow:/tmp/
docker exec -u postgres baserow pg_restore -d baserow --clean --if-exists /tmp/baserow_dump.dump
docker exec baserow rm /tmp/baserow_dump.dump
# 4. Restore media files (if present)
docker cp baserow-media.tar.gz baserow:/tmp/
docker exec baserow tar xzf /tmp/baserow-media.tar.gz -C /baserow/data/
docker exec baserow rm /tmp/baserow-media.tar.gz
# 5. Restart Baserow to pick up restored data
docker compose restartNote: If running from Git Bash on Windows, prefix docker exec commands with MSYS_NO_PATHCONV=1 to prevent path mangling:
MSYS_NO_PATHCONV=1 docker exec -u postgres baserow pg_restore -d baserow --clean --if-exists /tmp/baserow_dump.dumpVerification
- Automatic: Weekly (Sunday 6:00 AM) via
backup-baserow.py --verify - What it checks:
- Latest R2 backup exists and is < 25 hours old
- File size is reasonable (> 1 KB)
- Downloads, decrypts, and extracts the archive
- Runs
pg_restore --listto verify dump structure - Confirms media archive is present and extractable
- Logs:
solanasis-docs/logs/baserow-verify-*.log
Infisical Backup
What’s Captured
- PostgreSQL database (compressed custom format via
pg_dump -Fc) — all secrets, projects, users, audit logs - Environment file (
docker-compose.ymldirectory.env) — containsENCRYPTION_KEY, database credentials, and other runtime config critical for restore
Backup Script
Location: solanasis-docs/scheduled-tasks/scripts/backup-infisical.py
# Run backup manually
python3 backup-infisical.py
# Run verification
python3 backup-infisical.py --verify
# Via cron wrapper (injects secrets from Infisical)
python3 cron-wrapper.py shared -- python3 backup-infisical.pyStorage
- GitHub (primary — offsite):
dzinreach/infisical-backupsprivate repo (encrypted archives committed and pushed) - Local (secondary):
infisical-backups/with daily/ and weekly/ subdirectories
Encryption
- Same pattern as Baserow: AES-256-CBC with PBKDF2 (600,000 iterations)
- Passphrase:
INFISICAL_BACKUP_PASSPHRASEin Infisical/shared/
Verification
- Automatic: Weekly (Sunday 6:30 AM) via
backup-infisical.py --verify - What it checks:
- Latest backup exists and is < 25 hours old
- File size is reasonable (> 1 KB)
- Decrypts the archive with the passphrase
- Confirms
infisical_dump.sqlis present and non-trivial - Confirms
dot_envcontainsENCRYPTION_KEY
- Logs:
solanasis-docs/logs/infisical-verify-*.log
Technical Notes
- Container:
infisical-db(postgres:14-alpine, separate from Baserow’s embedded PostgreSQL) - DB user:
infisical, DB name:infisical - Cross-platform: runs from both Windows (Task Scheduler) and WSL (cron)
Cloudflare R2 Details
- Bucket:
solanasis-backups - Region: WNAM (Western North America)
- API access: Uses existing
CLOUDFLARE_API_TOKEN(same token as Pages deployment, Cloudflare REST API with Bearer auth) - Cost: $0/month (10 GB free tier, zero egress fees)
- Current usage: ~6.4 MB per daily backup, ~45 MB for 7 daily retention = well under 10 GB
- Dashboard: Cloudflare → R2 → solanasis-backups
Credentials Needed for Restore
Baserow
| Secret | Location | Purpose |
|---|---|---|
BASEROW_BACKUP_PASSPHRASE | Infisical /shared/ + Bitwarden | Decrypt backup archives |
CLOUDFLARE_API_TOKEN | Infisical /solanasis-site/ | Download from R2 |
CLOUDFLARE_ACCOUNT_ID | Infisical /solanasis-site/ | R2 API endpoint |
Infisical
| Secret | Location | Purpose |
|---|---|---|
INFISICAL_BACKUP_PASSPHRASE | Infisical /shared/ + Bitwarden | Decrypt backup archives |
| GitHub SSH key | Server ~/.ssh/ | Push/pull from backup repo |
Chicken-and-egg note: If Infisical itself is down, you need the backup passphrase from Bitwarden (not Infisical) to restore it. Always keep passphrases in Bitwarden as a secondary store.
Scheduling
WSL Cron (Primary — migrated 2026-03-28)
| Schedule | Task | Cron Entry |
|---|---|---|
| Daily 2:00 AM | Infisical Backup | cron-wrapper.py shared -- backup-infisical.py |
| Daily 2:30 AM | Supabase Backup | run_backup_cron.py |
| Sunday 6:30 AM | Infisical Backup Verify | cron-wrapper.py shared -- backup-infisical.py --verify |
# View cron entries
crontab -l
# Edit cron entries
crontab -e
# cron-wrapper.py injects Infisical secrets as env vars before running the command
# (zero-disk secrets — no .env files needed)Windows Task Scheduler (Legacy — being migrated to WSL)
| Task Name | Schedule | Script |
|---|---|---|
| Solanasis - Baserow Backup | Daily 2:30 AM | backup-baserow.py |
| Solanasis - Baserow Backup Verify | Sunday 6:00 AM | backup-baserow.py --verify |
Logs
All backup scripts log to solanasis-docs/logs/ with 30-day auto-rotation:
| Log Pattern | Source |
|---|---|
baserow-backup-YYYY-MM-DD_HHMMSS.log | Daily Baserow backup |
baserow-verify-YYYY-MM-DD_HHMMSS.log | Weekly Baserow verification |
infisical-backup-YYYY-MM-DD_HHMMSS.log | Daily Infisical backup |
Adding Future Services
To add backup for a new service (e.g., ERPNext), follow this pattern:
- Copy
backup-baserow.pyas a template - Replace the
pg_dump/ media backup functions with the service-specific commands - Update constants (container name, R2 prefix, passphrase env var)
- Create a new passphrase in Infisical and Bitwarden
- Add to
register-tasks.ps1and re-run it as Administrator - Add to this document and
service-inventory.md