A lightweight, native macOS menu bar app for syncing with self-hosted Seafile servers.
The official Seafile desktop client crashes on macOS 15 Sequoia and macOS 26 Tahoe due to compatibility issues with the Qt UI framework. The crash occurs in QWidget::ensurePolished() before the app even fully launches:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Signal: Segmentation fault: 11
Crashed Thread: 0
Application Specific Information:
QWidget::ensurePolished() → MainWindow::showWindow()
This is a known issue reported on the Seafile Community Forum with no fix available as of late 2024.
SeaSync bypasses the problematic Qt UI entirely by implementing a pure Swift/SwiftUI sync client that:
- Uses native macOS APIs (no Qt dependencies)
- Runs as a lightweight menu bar app
- Connects directly to the Seafile REST API
- Provides bidirectional sync with your self-hosted server
- Menu Bar Integration: Unobtrusive menu bar icon showing sync status
- Bidirectional Sync: Upload local changes, download server changes
- Deletion Sync: Optionally sync file deletions both ways
- Encrypted Library Support: Works with password-protected libraries
- Last-Modified-Wins: Automatic conflict resolution
- FSEvents Watching: Real-time detection of local file changes
- Secure Credentials: API tokens stored in macOS Keychain
- No External Dependencies: Pure Apple frameworks
- macOS 14.0 (Sonoma) or later
- Swift 5.9+
- A self-hosted Seafile server (or seafile.com account)
git clone https://github.com/yourusername/SeaSync.git
cd SeaSync
swift build -c releaseThe executable will be at .build/release/SeaSync
.build/release/SeaSyncOr move to Applications and run from there.
On first launch, SeaSync will prompt you for:
- Server URL: Your Seafile server (e.g.,
https://seafile.example.com) - Username: Your email/username
- Password: Your account password
Credentials are securely stored in the macOS Keychain.
Files sync to: /Volumes/Normal stor/Seafile/ (configurable in SyncConfig.swift)
Each library creates a subfolder:
/Volumes/Normal stor/Seafile/
├── My Library/
│ ├── Documents/
│ └── Photos/
├── Work Files/
└── Shared Projects/
┌─────────────────────────────────────────────────────────────┐
│ SeaSync App │
├─────────────────────────────────────────────────────────────┤
│ MenuBarExtra (SwiftUI) │
│ ↓ │
│ AppState → SyncEngine → FileService → Seafile REST API │
│ ↓ │
│ FileWatcher (FSEvents) │
│ ↓ │
│ SyncDatabase (SQLite) │
└─────────────────────────────────────────────────────────────┘
- Authentication: POST to
/api2/auth-token/with credentials - List Libraries: GET
/api2/repos/to fetch all repositories - List Files: GET
/api2/repos/{id}/dir/?p=/recursively - Compare: Match remote files against local files and last sync state
- Download: GET download link, then fetch file content
- Upload: GET upload link, then POST multipart form
- Track: Save file states to SQLite for deletion detection
| Operation | Method | Endpoint |
|---|---|---|
| Login | POST | /api2/auth-token/ |
| List Libraries | GET | /api2/repos/ |
| List Directory | GET | /api2/repos/{id}/dir/?p={path} |
| Download File | GET | /api2/repos/{id}/file/?p={path} |
| Upload File | POST | {upload-link} (multipart) |
| Delete File | DELETE | /api2/repos/{id}/file/?p={path} |
SeaSync/
├── SeaSyncApp.swift # Main app entry, MenuBarExtra
├── Models/
│ ├── Account.swift # Server URL, token, username
│ ├── Library.swift # Seafile library/repo model
│ ├── SeafFile.swift # File/directory entry
│ └── SyncState.swift # Local sync tracking
├── Services/
│ ├── AuthService.swift # Login, token management
│ ├── LibraryService.swift # List/manage libraries
│ ├── FileService.swift # Download/upload files
│ ├── SyncEngine.swift # Core sync logic
│ └── FileWatcher.swift # FSEvents monitoring
├── Storage/
│ ├── KeychainManager.swift # Secure credential storage
│ └── SyncDatabase.swift # Local state persistence
├── Views/
│ ├── MenuBarView.swift # Menu bar dropdown UI
│ ├── SetupView.swift # First-run configuration
│ └── SettingsView.swift # Settings window
└── Config/
└── SyncConfig.swift # Sync settings
Edit SyncConfig.swift:
static let localSyncPath = "/your/preferred/path"static let syncIntervalSeconds = 300 // 5 minutesstatic let conflictStrategy: ConflictStrategy = .lastModifiedWins
// Options: .lastModifiedWins, .keepBoth, .serverWins, .localWins- No selective sync: All libraries are synced (planned feature)
- No encrypted library creation: Must create on server/web
- No file history/versions: Only current state synced
- Single account: One server connection at a time
This project was built by studying:
- Seafile Client - Official C++/Qt desktop client
- Seafile Web API - REST API documentation
- Seafile Server - Server implementation
- python-seafile - Python API client
Special thanks to the Seafile team for their open-source file sync platform.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
MIT License - see LICENSE file.
- Check your server URL includes
https:// - Verify the server is accessible from your network
- Ensure you're using your email address, not username
- Check password is correct
- Check the sync folder exists and is writable
- Verify external drive is mounted (if using external storage)
- Check Console.app for SeaSync logs
- SeaSync will prompt for the library password
- Password is stored in Keychain for future access
Built with Swift & SwiftUI for macOS 🍎