How SQLite Backup and Restore Works in TREK: Complete Technical Guide
TREK implements SQLite backup and restore by packaging the live travel.db database into a compressed ZIP archive alongside encryption keys and user uploads, then validates and atomically swaps the database file during restoration.
All persistent data in TREK resides in a single SQLite file located at data/travel.db. The backup subsystem, implemented in server/src/services/backupService.ts and exposed via server/src/nest/backup/backup.controller.ts, handles atomic archiving, integrity verification, and safe restoration to prevent data corruption.
Creating a Backup (SQLite to ZIP)
The backup creation process ensures database consistency before archiving and bundles all necessary assets for a complete restoration.
WAL Checkpoint for Consistency
Before archiving, the service forces SQLite to flush its write-ahead log to ensure the on-disk file is fully consistent:
try { db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch (e) {}
This pragma truncates the WAL file and persists all pending transactions to travel.db before the file is copied into the archive.
Archive Assembly
The service creates a ZIP archive with maximum compression (zlib:{level:9}) and sequentially adds:
- The database file –
data/travel.dbis added astravel.dbin the archive root - Encryption key – If present,
.encryption_keyis bundled to preserve encrypted secrets - User uploads – All files under
uploads/are included, while caches and existing backups are explicitly excluded to prevent archive bloat
The resulting file is written to data/backups/ with a timestamped filename pattern (backup-*.zip or auto-backup-*.zip), and the API returns a BackupInfo object containing the filename, size, and creation timestamp.
Restoring a Backup (ZIP to SQLite)
Restoration performs multi-layer validation before atomically replacing the live database to prevent corruption from invalid archives.
Size Validation and Extraction
The service first calculates the total uncompressed size of all ZIP entries using unzipper. If the sum exceeds MAX_BACKUP_DECOMPRESSED_SIZE (5 GB), the operation aborts immediately to prevent disk exhaustion. Valid archives are extracted to a temporary directory (data/restore-<timestamp>).
Database Integrity Verification
Before accepting the backup, TREK performs three validation checks in backupService.ts:
- Presence check – The archive must contain
travel.dbat the root level - Integrity check – The file is opened read-only with
better-sqlite3andPRAGMA integrity_checkis executed; any result other thanokcauses immediate failure - Schema validation – The service verifies the presence of core TREK tables (
users,trips,trip_members,places,days) to ensure the file is a valid TREK database and not an arbitrary SQLite file
Atomic Database Swap
Once validated, the restoration proceeds atomically:
- The existing
travel.dband its-wal/-shmsidecar files are deleted - The validated
travel.dbfrom the archive is copied intodata/ - If the archive contains
.encryption_key, it overwrites the server's key file - The
uploads/directory is replaced entirely: existing files are deleted and archive contents are copied
Post-Restore Initialization
After the file swap, the global database connection is reinitialized via reinitialize(), and the in-process permissions cache is cleared to reflect the new database state. The temporary extraction directory is removed, and the API returns { success: true }.
API Usage Examples
Creating and Downloading Backups
Create a backup via the admin API:
curl -X POST -H "Authorization: Bearer <admin-jwt>" \
https://your-trek.example.com/api/backup/create
Download the resulting archive:
curl -OJ -H "Authorization: Bearer <admin-jwt>" \
https://your-trek.example.com/api/backup/download/backup-2024-10-22T14-30-00.zip
Restoring from Backup
Restore a server-side backup file:
curl -X POST -H "Authorization: Bearer <admin-jwt>" \
https://your-trek.example.com/api/backup/restore/backup-2024-10-22T14-30-00.zip
Upload and restore an external backup file:
curl -X POST -H "Authorization: Bearer <admin-jwt>" \
-F "backup=@/path/to/backup.zip" \
https://your-trek.example.com/api/backup/upload-restore
Programmatic Usage
Interact with the backup service directly in TypeScript:
import * as backup from './services/backupService';
// Create backup programmatically
const info = await backup.createBackup();
console.log('Created:', info.filename, info.sizeText);
// Restore from file path
const result = await backup.restoreFromZip('/tmp/backup.zip');
if (!result.success) {
console.error('Restore failed:', result.error);
}
Rate Limiting and Security Controls
The backup controller enforces rate limiting via BackupService.checkRateLimit, tracking requests per IP in a rolling one-hour window (BACKUP_RATE_WINDOW).
Filename validation restricts operations to backup-*.zip or auto-backup-*.zip patterns via isValidBackupFilename. Upload size is capped by the BACKUP_UPLOAD_LIMIT_MB environment variable (defaulting to 500 MiB) to prevent resource exhaustion.
Summary
- Atomic archiving – TREK uses
PRAGMA wal_checkpoint(TRUNCATE)to ensure SQLite consistency before bundlingtravel.db, encryption keys, and uploads into a compressed ZIP - Validation layers – Restoration enforces 5 GB size limits, SQLite integrity checks, and schema validation against required tables (
users,trips,trip_members,places,days) - Safe swapping – The live database is replaced atomically after extraction to a temporary directory, with automatic connection reinitialization and permissions cache clearing
- Rate protection – Built-in rate limiting and filename validation prevent abuse of backup endpoints
Frequently Asked Questions
How does TREK ensure the SQLite database is not corrupted during backup?
TREK executes PRAGMA wal_checkpoint(TRUNCATE) immediately before archiving, forcing SQLite to persist all pending write-ahead log entries to disk and truncate the WAL file. This ensures the travel.db file on disk is in a consistent state before being copied into the ZIP archive, preventing backup corruption from uncommitted transactions.
What is the maximum size for backup uploads and extractions?
Uploads are limited by the BACKUP_UPLOAD_LIMIT_MB environment variable (default 500 MB). During restoration, the service calculates the total uncompressed size of all ZIP entries and aborts if the total exceeds MAX_BACKUP_DECOMPRESSED_SIZE (5 GB), protecting the server from ZIP bomb attacks and disk space exhaustion.
Why does TREK include the encryption key in the backup archive?
The .encryption_key file is bundled with the database when present because TREK uses it to encrypt sensitive data stored in SQLite. Without this key, the encrypted values in travel.db would be unrecoverable even if the database file were restored successfully. The key is only included when using file-based encryption key storage.
What happens if a restore operation fails midway through?
If validation fails (integrity check, missing tables, or size limits), the temporary extraction directory is removed and the live database remains untouched. If the database swap fails after validation, the service attempts to reinitialize the connection; if that fails, it returns an error indicating that a manual server restart is required to recover the database state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →