Solanasis Backup Strategy

Last updated: 2026-03-26


What’s Backed Up

ServiceMethodDestinationScheduleRetentionTypical Size
Baserow (CRM)pg_dump (-Fc) + media tarCloudflare R2 + localDaily 2:30 AM7 daily, 4 weekly~6.4 MB encrypted
Infisical (Secrets)pg_dump + .envGitHub (encrypted) + localDaily 2:00 AM7 daily, 4 weekly~1 MB encrypted
SilverBullet (Wiki)Git (mounts solanasis-docs)GitHubOn commitFull historyN/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=false added to docker-compose.yml to 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 --verify

Technical Notes

  • Peer authentication: PostgreSQL in the all-in-one container runs as the postgres OS user. All docker exec commands use -u postgres for peer auth (no password needed)
  • Secrets loading: Secrets are loaded from .env files at runtime (not manage_secrets.py, which has ~30s startup overhead from Infisical auth). The .env files are synced from Infisical via sync_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=1 to prevent Unix path conversion (e.g., /baserow/data being rewritten to C:/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_PASSPHRASE in 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 restart

Note: 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.dump

Verification

  • Automatic: Weekly (Sunday 6:00 AM) via backup-baserow.py --verify
  • What it checks:
    1. Latest R2 backup exists and is < 25 hours old
    2. File size is reasonable (> 1 KB)
    3. Downloads, decrypts, and extracts the archive
    4. Runs pg_restore --list to verify dump structure
    5. 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.yml directory .env) — contains ENCRYPTION_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.py

Storage

  • GitHub (primary — offsite): dzinreach/infisical-backups private 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_PASSPHRASE in Infisical /shared/

Verification

  • Automatic: Weekly (Sunday 6:30 AM) via backup-infisical.py --verify
  • What it checks:
    1. Latest backup exists and is < 25 hours old
    2. File size is reasonable (> 1 KB)
    3. Decrypts the archive with the passphrase
    4. Confirms infisical_dump.sql is present and non-trivial
    5. Confirms dot_env contains ENCRYPTION_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

SecretLocationPurpose
BASEROW_BACKUP_PASSPHRASEInfisical /shared/ + BitwardenDecrypt backup archives
CLOUDFLARE_API_TOKENInfisical /solanasis-site/Download from R2
CLOUDFLARE_ACCOUNT_IDInfisical /solanasis-site/R2 API endpoint

Infisical

SecretLocationPurpose
INFISICAL_BACKUP_PASSPHRASEInfisical /shared/ + BitwardenDecrypt backup archives
GitHub SSH keyServer ~/.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)

ScheduleTaskCron Entry
Daily 2:00 AMInfisical Backupcron-wrapper.py shared -- backup-infisical.py
Daily 2:30 AMSupabase Backuprun_backup_cron.py
Sunday 6:30 AMInfisical Backup Verifycron-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 NameScheduleScript
Solanasis - Baserow BackupDaily 2:30 AMbackup-baserow.py
Solanasis - Baserow Backup VerifySunday 6:00 AMbackup-baserow.py --verify

Logs

All backup scripts log to solanasis-docs/logs/ with 30-day auto-rotation:

Log PatternSource
baserow-backup-YYYY-MM-DD_HHMMSS.logDaily Baserow backup
baserow-verify-YYYY-MM-DD_HHMMSS.logWeekly Baserow verification
infisical-backup-YYYY-MM-DD_HHMMSS.logDaily Infisical backup

Adding Future Services

To add backup for a new service (e.g., ERPNext), follow this pattern:

  1. Copy backup-baserow.py as a template
  2. Replace the pg_dump / media backup functions with the service-specific commands
  3. Update constants (container name, R2 prefix, passphrase env var)
  4. Create a new passphrase in Infisical and Bitwarden
  5. Add to register-tasks.ps1 and re-run it as Administrator
  6. Add to this document and service-inventory.md