IceVault: From Prototype to Production
Building a macOS menu bar app for cold backups — architecture decisions, Swift concurrency patterns, and shipping signed releases.
Over the past few weeks, IceVault evolved from a "what if?" idea into a production-ready macOS app for automated backups to AWS S3 Glacier Deep Archive. Here's the story of the technical decisions, the architecture, and the path to shipping.
The Problem
Cloud backups are either expensive (real-time sync services) or cumbersome (manual upload workflows). Glacier Deep Archive is incredibly cheap ($0.00099/GB/month) but has a high friction barrier: AWS CLI, multipart uploads, credential management, and no native macOS integration.
I wanted something that sat in the menu bar, watched folders, and quietly archived files without thinking about it.
Architecture Decisions
Bounded Concurrency with Swift Structured Concurrency
The first challenge: uploading thousands of small files without saturating the network or melting the CPU. Swift's TaskGroup makes this elegant:
static func run<Input: Sendable, Output: Sendable>(
inputs: [Input],
maxConcurrentTasks: Int,
operation: @escaping @Sendable (Input) async throws -> Output,
onSuccess: @escaping (Output) async throws -> Void
) async throws
The BoundedTaskRunner schedules work with back-pressure — as tasks complete, new ones start. This prevents the "fork bomb" problem of unbounded concurrency while still saturating available bandwidth.
Actor-Based Progress Tracking
Upload progress is inherently stateful. Rather than fighting Swift's concurrency model, I leaned into it:
private actor UploadProgressTracker {
private var completedRecordIDs: Set<Int64> = []
private var inFlightBytesByRecordID: [Int64: Int64] = [:]
private var completedBytes: Int64 = 0
func update(recordID: Int64, uploadedBytes: Int64) -> UploadProgressSnapshot { ... }
}
The actor isolates mutable state. The UI observes snapshots. No data races, no locks, no @MainActor gymnastics.
Multipart Uploads for Large Files
Files over 100MB get chunked into parts and uploaded via S3's multipart API. This serves two purposes:
- Resumability: If a 5GB upload fails at 4.9GB, you don't start over
- Parallelization: Multiple parts upload concurrently
There's also stale upload cleanup — abandoned multipart uploads older than 24 hours get purged automatically.
The Production Pipeline
Code Signing and Notarization
A menu bar app that uploads files to the cloud triggers every Gatekeeper alarm. To ship a trustworthy app:
- Developer ID signing: App bundles signed with Apple-issued certificates
- Notarization: Apple scans and approves each release
- Stapling: The notarization ticket is embedded in the DMG
The result: users double-click the DMG, drag to Applications, and the app opens without scary security warnings.
GitHub Actions Release Flow
Tagging v* triggers the pipeline:
- Build
arm64andx86_64binaries on separate runners - Sign both app bundles
- Notarize and staple
- Package into DMGs
- Publish to GitHub Releases
- Update the Homebrew tap
The entire release process is automated. Push a tag, get signed binaries.
Homebrew Distribution
brew install lydakis/tap/icevault
The Homebrew tap auto-updates on each release. Users get updates via brew upgrade.
What's Working
- Incremental sync: SQLite inventory tracks what's already uploaded
- Credential resolution: Keychain →
~/.aws/credentials→ environment variables - Scheduled backups: LaunchAgent support for daily/weekly automation
- Resume-safe: Cancel and resume backups without losing progress
- Remote validation: Compares local checksums with S3 to detect corruption
What's Next
- Restore UI (currently headless only)
- Exclude patterns (
.git,node_modules) - Bandwidth limiting
- Versioned backups (keep N versions of each file)
The Economics
Back up 1TB to Glacier Deep Archive: ~$1/month. Compare to Dropbox ($120/year for 2TB) or Backblaze ($70/year unlimited). The tradeoff is retrieval time (12-48 hours) and egress fees — but for true cold storage, it's unbeatable.
IceVault is open source at github.com/lydakis/icevault. If you're the kind of person who has terabytes of photos, videos, or archives that you rarely touch but never want to lose, it might be for you.