A native macOS app for safely pulling, sanitizing, and sharing production database backups.
Overview * Features * Requirements * Getting started * Architecture * Security * Contributing
Pulling a production database dump to a laptop is normally a trade-off between speed and safety: either you skip sanitization to save time, or you sanitize by hand and risk missing a column. Backup Manager removes that trade-off by turning the whole flow into a single, auditable pipeline:
Production DB --SSH/SSM tunnel--> Dump --> Temp import --> Sanitize PII --> Export --> Upload to S3 --> Share
Every step -- the tunnel, the dump, the column-level sanitization rules, the upload, and the share link handed to a teammate -- is driven from one SwiftUI app, logged to an audit trail, and never touches your shell history with a plaintext password.
| Area | What it does |
|---|---|
| Bastion connections | SSH, AWS SSM, or direct TCP tunnels into RDS/MySQL clusters, grouped by bastion so one host can serve multiple databases |
| Sanitization engine | 9 column-level strategies -- fake email/name/phone, SHA-256 hash, mask, nullify, static value, character scramble, and full-table truncation |
| DEFINER rewriting | Rewrites view/trigger/routine DEFINER clauses to the local MySQL user on import, so dumps from a shared RDS account import cleanly on a laptop |
| Turbo import mode | Temporarily tunes InnoDB (buffer pool, redo log, flush behavior) for fast bulk import, then restores safe defaults automatically |
| Pipeline orchestration | Multi-stage runner with live progress, retry-from-failed-stage, and a persistent audit log per run |
| S3 distribution | Uploads the sanitized dump and generates a time-boxed presigned URL with a generated password |
| Team distribution | Looks up members of a GitHub organization and notifies a recipient directly -- no more pasting links into chat |
| Parallel compression | Uses pigz for multi-core gzip, so large dumps compress/decompress in a fraction of the single-threaded time |
| Encrypted local state | Profile data is encrypted at rest with AES-GCM; RDS credentials are read from and written to the macOS Keychain, never stored in plaintext |
| Bilingual UI | Full English and Spanish localization |
System
- macOS 14.0 (Sonoma) or later
- Apple Silicon or Intel
CLI tools -- auto-detected on first launch, with an in-app install hint if missing:
| Tool | Purpose | Install |
|---|---|---|
mysql / mysqldump |
Database connection and dumps | brew install mysql-client |
pigz |
Parallel gzip compression | brew install pigz |
pv |
Transfer progress monitoring | brew install pv |
aws |
S3 upload and presigned URLs | brew install awscli |
gh |
GitHub org lookup and distribution | brew install gh |
ssh |
SSH tunnels | Pre-installed on macOS |
mydumper / myloader (optional) |
Faster parallel dump/restore for large databases | brew install mydumper |
brew install mysql-client pigz pv awscli ghGrab the latest .zip from Releases,
unzip it, and drag Backup Manager.app into /Applications. Every release
is signed with a Developer ID Application certificate and notarized by
Apple, so it opens with a plain double-click -- no Gatekeeper warning, no
right-click -> Open workaround.
Every tagged release (vX.Y.Z) is built, signed, and notarized automatically
by the release workflow -- the binary you
download is built directly from that tag's source, not uploaded by hand.
git clone https://github.com/mherrera53/backup-manager.git
cd backup-manager
./build.shbuild.sh compiles a universal binary (arm64 + x86_64), assembles a signed
.app bundle, and installs it to /Applications/Backup Manager.app. On
first launch, the onboarding flow checks for missing CLI tools and walks you
through creating your first bastion profile.
Local development
swift build # debug build
swift build -c release --arch arm64 # release build, single archPush a tag matching vX.Y.Z and CI builds the universal binary, packages
the .app, and publishes a GitHub Release with the zip attached:
git tag v1.0.0
git push origin v1.0.0Sources/BackupManager/
+-- Models/
| +-- SanitizationProfile.swift AppConfig, Bastion, DatabaseConfig, TableRule
| +-- BackupFile.swift PipelineRun, PipelineStage, LogEntry
| +-- DefaultProfiles.swift First-run defaults, rule templates
| +-- Distribution.swift Distribution records and config
+-- Services/
| +-- DatabaseService.swift MySQL operations, tunnels, turbo mode
| +-- ProfileStore.swift Encrypted persistence (AES-GCM)
| +-- KeychainService.swift macOS Keychain integration
| +-- S3Service.swift AWS S3 upload/list
| +-- ShellService.swift Shell command execution
| +-- ShareLinkService.swift Presigned URL + shortlink generation
| +-- GitHubOrgService.swift GitHub org member lookup and distribution
| +-- CLIDetector.swift CLI tool auto-detection
+-- ViewModels/
| +-- AppViewModel.swift Main app state and pipeline orchestration
+-- Views/
| +-- ContentView.swift App shell, sidebar, navigation
| +-- ProfileEditorView.swift Bastion editor
| +-- PipelineView.swift Pipeline execution with activity feed
| +-- SanitizationRulesView.swift Rule editor with schema browser
| +-- BackupsView.swift Local backup file browser
| +-- S3BrowserView.swift S3 object browser with share links
| +-- DistributionView.swift Team distribution via GitHub
| +-- OnboardingView.swift First-run setup wizard
+-- Styles/GlassStyles.swift Shared view modifiers
+-- Localization.swift Bilingual string helper
+-- Utilities.swift Formatters and helpers
+-- App.swift Entry point
Data model
AppConfig (global)
+-- local: LocalConnection One MySQL local config
+-- s3: S3Config One S3 config
+-- backupDirectory: String
+-- bastions: [Bastion]
+-- Bastion
+-- connection: SSH | SSM | Direct
+-- RDS endpoint + credentials (Keychain-backed)
+-- databases: [DatabaseConfig]
+-- DatabaseConfig
+-- name
+-- tableRules: [TableRule]
+-- columns: [ColumnRule]
+-- strategy: SanitizationStrategy
Pipeline modes
| Mode | Flow |
|---|---|
| Backup Pipeline | Tunnel -> remote dump -> import to temp DB -> sanitize -> export -> upload to S3 -- the full end-to-end run |
| Sanitize Local | Clone local DB -> sanitize -> export -- sanitizes an existing local database without touching remote servers |
| Refresh Local | Import dump -> replace local DB -- imports a backup file directly into local MySQL, with a safety backup taken first |
Sanitization strategies
| Strategy | Applied as | Typical use |
|---|---|---|
| Fake Email | CONCAT('user_', id, '@sanitized.test') |
Email columns |
| Fake Name | CONCAT('Persona_', id) |
Name columns |
| Fake Phone | CONCAT('+1555', LPAD(id, 7, '0')) |
Phone numbers |
| SHA-256 Hash | SHA2(column, 256) |
Tokens, password hashes |
| Mask | a***z (first + last char kept) |
IDs, addresses |
| Nullify | NULL |
Coordinates, secrets |
| Static Value | Custom replacement | Company/tenant names |
| Scramble | Reversed substring | Usernames |
| Truncate Table | TRUNCATE TABLE |
Logs, audit trails |
- No credentials in source. All sensitive data lives in user config files (outside the repo, under
~/Library/Application Support/BackupManager/) and the macOS Keychain. - Encrypted local config.
ProfileStoreseals profile data with AES-GCM using a key derived from the machine hostname and username -- the encrypted blob is portable to nowhere else by design. - Keychain-backed passwords.
KeychainServicereads/writes RDS credentials through the macOS Keychain rather than storing them in any JSON file. - Shell and SQL escaping. All user-controlled strings are escaped before shell interpolation or SQL generation.
MYSQL_PWDover argv. Passwords are passed tomysql/mysqldumpvia environment variable, never as a command-line argument (which would leak intops).- Audit trail. Every pipeline run is logged to
~/Library/Application Support/BackupManager/audit/audit_YYYY-MM-DD.log. - Signed and notarized releases. Every tagged release is signed with a Developer ID Application certificate and notarized by Apple's notary service before publishing -- verify any release with
spctl -a -vvv --type execute "Backup Manager.app", which should printsource=Notarized Developer ID.
Found a security issue? Please see SECURITY.md before opening a public issue.
~/Library/Application Support/BackupManager/
+-- app_config.json Bastion and global config
+-- profiles.enc Encrypted legacy profiles (AES-GCM)
+-- default_rules.json Sanitization rule templates
+-- distribution_history.json Distribution records
+-- distribution_config.json Distribution settings
+-- audit/
+-- audit_YYYY-MM-DD.log Daily audit logs
None of the above ever leaves ~/Library/Application Support except the
sanitized dump itself, which is uploaded to the S3 bucket you configure.
Contributions are welcome -- see CONTRIBUTING.md for the development workflow, coding conventions, and PR checklist.
See CHANGELOG.md.
MIT -- see LICENSE.