11-Apr-2026 — consolidated MongoDB + S3 incremental backup with cross-environment restore #187
What changed for users
- A single Start Backup action now captures both the MongoDB database AND the site's S3 objects in one timestamp-aligned operation, surfaced through the existing Backup/Restore admin tab
- A single Start Restore action rolls both Mongo and the S3 bucket back to the chosen snapshot, automatically walking cross-environment when the selected backup originated from a different environment
- Backup/Restore History gains an S3 Objects column showing per-session copied/skipped counts, bytes transferred, and status
- Backup and restore completion emails now include a structured S3 summary table (per-site status, totals, copied/restored and skipped counts, copied and total size) so notifications reflect the full scope of the consolidated operation
- S3 backups are incremental after the first run: unchanged objects (matched by
ETag against the previous manifest) are recorded as SKIPPED rather than re-copied, dramatically reducing cost and time
- Each backup site has its own Manifest History view with Restore, Delete, and per-row status indicators
- The Delete button for a historical manifest is automatically disabled with an explanatory tooltip when deleting it would orphan
SKIPPED references in any later manifest
- Legacy
/api/backup route renamed to /api/mongo-backup and new /api/s3-backup introduced, so the two services are peer top-level endpoints rather than nested; frontend service uses matching MONGO_BASE_URL and S3_BASE_URL constants
Technical — new service and data model
- Add
s3-backup-service with:
- Incremental backup (
ETag comparison against the previous manifest)
- Point-in-time restore that walks back through prior manifests via
$elemMatch on key + eTag + action to find the physical source of SKIPPED entries, bounded by timestamp <= snapshot
- Cross-site restore via a separate target
SiteConfig that supplies destination credentials and bucket
- Add
s3-backup-controller, s3-backup-routes, and the s3BackupManifest Mongoose model with a (site, timestamp) compound index
- Add
startS3Backup / startS3Restore with per-site SiteConfig resolution that supports global-credentials inheritance (target env inherits global credentials when per-env credentials absent) and falls back to <bucket>-backups when no global backup bucket
Technical — consolidated flow
- Extend
BackupAndRestoreService.executeBackup / executeRestore to trigger the S3 phase after Mongo succeeds (honouring options.includeS3, defaulting true) and store the summary on BackupSession as s3Backups / s3Restores
- Derive source environment from Mongo backup filenames via
extractSourceEnvironmentFromBackupName(name, dbName) which uses the database name as a hint to disambiguate multi-segment environment names and multi-segment databases (e.g. ngx-ramblers)
BackupSession model and Mongoose schema gain s3Backups?, s3Restores? (S3BackupSummary[]) and options.includeS3?
- CLI
backup / restore commands inherit consolidation automatically because they share BackupAndRestoreService; CLI still exposes a dedicated s3-backup sub-command for S3-only operations
Technical — deletability guard
collectCopiedKeys + countBlockingLaterManifests compute whether deleting a candidate manifest would orphan SKIPPED references in any later manifest on the same site
manifests() and manifest() enrich returned docs with deletable and blockReason
deleteManifests returns {deleted, blocked} and refuses blocked ones
S3BackupManifest model gains runtime-only deletable? and blockReason? fields populated by the enrichment helpers
Technical — notifications
BackupNotificationService renders a structured S3 summary HTML table inside the completion emails for both backup and restore, driven by session.s3Backups / session.s3Restores
- New
buildS3SummarySection helper keeps the formatting consistent across backup and restore emails
Technical — route and script cleanup
- Rename
backup-routes.ts to mongo-backup-routes.ts and export mongoBackupRoutes to match the /api/mongo-backup mount; mount new s3BackupRoutes at /api/s3-backup
- Delete standalone
aws-backup/backup-s3-images.ts script plus its npm script since the new service fully supersedes it
Technical — UI
- Admin UI pulls in ngx-bootstrap
TooltipDirective and switches the per-manifest Delete button to [disabled] + [tooltip] when blocked, with inline button-swap confirmation (no native confirm() dialogs)
Technical — type safety
- Replace all stringly-typed
BackupSessionStatus and BackupSessionType comparisons with enum references (BackupSessionStatus.COMPLETED / .FAILED / .IN_PROGRESS / .PENDING and BackupSessionType.BACKUP / .RESTORE) across the service, ws handler, notification builder, and admin component
- The admin component exposes both enums as
protected readonly members so the template can reference them directly
- Remove dry-run persistence bug where manifests were saved to Mongo even when
dryRun was true; dry runs now return an in-memory manifest
Technical — tests
- 8 new unit tests (
backup-paths: 6 extractSourceEnvironmentFromBackupName cases including path prefix stripping, multi-segment db, and missing timestamp; s3-backup-service: 2 collectCopiedKeys cases)
- Full server suite now 575 passing