Skip to content

Commit e04514d

Browse files
author
ebreen
committed
docs: rewrite README for v2.0 pure Swift + FSKit architecture
1 parent 3369091 commit e04514d

1 file changed

Lines changed: 74 additions & 103 deletions

File tree

README.md

Lines changed: 74 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,109 @@
1-
# CloudMount Mount cloud storage as native macOS drives.
1+
# CloudMount -- Mount cloud storage as native macOS volumes.
22

3-
macOS 12+ menu bar app that mounts Backblaze B2 buckets as local FUSE volumes in Finder. Browse, read, write, and delete files as if they were on a local disk. Native SwiftUI interface, Rust FUSE daemon, no Electron. Free and open source alternative to Mountain Duck.
3+
Pure Swift macOS 26+ menu bar app that mounts Backblaze B2 buckets as local Finder volumes using Apple's FSKit framework. Browse, read, write, and delete files as if they were on a local disk. No FUSE, no kernel extensions, no Electron.
44

55
<!-- <img src="screenshot.png" alt="CloudMount menu bar screenshot" width="520" /> -->
66

77
## Install
88

99
### Requirements
10-
- macOS 12+ (Monterey)
11-
- [macFUSE](https://osxfuse.github.io/) (CloudMount will guide you through installation on first launch)
10+
- macOS 26 (Tahoe) or later
11+
- Enable the FSKit extension after install: System Settings -> General -> Login Items & Extensions -> CloudMount
1212

13-
### Build from source
14-
15-
**Swift UI app:**
13+
### Homebrew
1614
```bash
17-
swift build
15+
brew install ebreen/cloudmount/cloudmount
1816
```
1917

20-
**Rust FUSE daemon:**
18+
### GitHub Releases
19+
Download the latest DMG: <https://github.com/ebreen/cloudmount/releases>
20+
21+
Open the DMG and drag CloudMount to Applications.
22+
23+
### Build from source
2124
```bash
22-
cd Daemon/CloudMountDaemon
23-
cargo build --release
25+
brew install xcodegen create-dmg
26+
xcodegen generate
27+
xcodebuild archive \
28+
-project CloudMount.xcodeproj \
29+
-scheme CloudMount \
30+
-configuration Release \
31+
-archivePath build/CloudMount.xcarchive
2432
```
2533

2634
### First run
27-
- Launch CloudMount — it appears in your menu bar (no Dock icon).
28-
- If macFUSE isn't installed, you'll see a guided installation dialog.
29-
- Open Settings → Credentials and add your Backblaze B2 application key ID + key.
30-
- Add a bucket in Settings → Buckets with name and mount point.
31-
- Click Mount from the menu bar. Your bucket appears in Finder under `/Volumes/`.
35+
- Launch CloudMount -- it appears in your menu bar (no Dock icon).
36+
- If the FSKit extension isn't enabled, an onboarding screen guides you to System Settings.
37+
- Open Settings -> Credentials and add your Backblaze B2 application key ID + key.
38+
- Add a bucket in Settings -> Buckets, choose a mount point (default: `/Volumes/<bucket>`).
39+
- Click Mount. Your bucket appears in Finder.
40+
41+
### CLI
42+
```bash
43+
mount -t b2 b2://my-bucket /Volumes/my-bucket
44+
diskutil unmount /Volumes/my-bucket
45+
```
3246

3347
## Features
34-
- Mount B2 buckets as native macOS volumes visible in Finder.
35-
- Browse directories, open files, drag-and-drop it's just a folder.
36-
- Write files locally, upload to B2 on close (write-on-close strategy).
48+
- Mount B2 buckets as native macOS volumes visible in Finder and `diskutil`.
49+
- Browse directories, open files, drag-and-drop -- it's a regular folder.
50+
- Read files on open, write back to B2 on close (download-on-open / write-on-close).
3751
- Delete files and create directories through Finder.
38-
- Metadata caching with Moka reduces B2 API calls by 80%+.
39-
- Secure credential storage in macOS Keychain — never in config files.
40-
- Bucket configuration persists between restarts (`~/Library/Application Support/CloudMount/`).
41-
- Retry with exponential backoff for transient network failures.
42-
- macOS metadata file suppression (`.DS_Store`, `._*`) to minimize API calls.
52+
- On-disk file cache with LRU eviction (default 1 GB, `~/Library/Caches/CloudMount/`).
53+
- In-memory metadata cache reduces B2 API calls (default 5 min TTL).
54+
- macOS metadata suppression (`.DS_Store`, `._*`, `.Spotlight-V100`, etc.) to minimize API noise.
55+
- Secure credential storage in macOS Keychain -- never in config files.
56+
- Automatic B2 token refresh with retry on auth expiry.
57+
- Upload retry with fresh upload URL on transient failures.
58+
- Sparkle auto-updates -- checks for new versions automatically.
59+
- Launch at Login option in Settings.
60+
- Signed, notarized, and stapled for Gatekeeper.
4361
- No Dock icon, minimal UI, lives entirely in the menu bar.
4462

4563
## Architecture
4664

47-
CloudMount is a dual-process architecture:
65+
CloudMount is a three-target Xcode project generated by XcodeGen:
4866

4967
```
50-
┌─────────────────────┐ Unix Socket ┌──────────────────────┐
51-
│ Swift/SwiftUI │ ◄──── JSON Protocol ────► │ Rust Daemon │
52-
│ │ /tmp/cloudmount.sock │ │
53-
│ • Menu bar UI │ │ • FUSE filesystem │
54-
│ • Settings window │ │ • B2 API client │
55-
│ • Credential mgmt │ │ • Metadata cache │
56-
│ • Daemon client │ │ • Mount manager │
57-
└─────────────────────┘ └──────────────────────┘
68+
CloudMount.app
69+
├── CloudMount Menu bar app (SwiftUI)
70+
│ Manages accounts, settings, mount/unmount via Process,
71+
│ monitors volume events via NSWorkspace notifications.
72+
73+
├── CloudMountExtension FSKit filesystem extension (XPC)
74+
│ Implements FSUnaryFileSystem for the b2:// URL scheme.
75+
│ Handles all file operations: lookup, enumerate, read,
76+
│ write, create, delete, rename. Runs sandboxed.
77+
78+
└── CloudMountKit Shared framework
79+
B2 API client, auth manager, credential store,
80+
metadata cache, file cache, shared config.
5881
```
5982

60-
- **Swift app** (`Sources/CloudMount/`) — SwiftUI MenuBarExtra, settings, Keychain access, daemon communication via actor-based `DaemonClient`.
61-
- **Rust daemon** (`Daemon/CloudMountDaemon/`) — fuser-based FUSE filesystem, B2 API client with reqwest, Moka metadata cache, Unix socket IPC server.
62-
- **IPC** — JSON protocol over Unix domain socket. Commands: `Mount`, `Unmount`, `GetStatus`. 2-second polling for status updates.
63-
64-
## How it works
65-
66-
1. Swift app sends `Mount` command via Unix socket with bucket name, credentials, and mount point.
67-
2. Rust daemon authenticates with B2 API (`b2_authorize_account`).
68-
3. Daemon creates a FUSE filesystem and mounts it at the requested path.
69-
4. Finder sees the mount as a native volume.
70-
5. File operations (read/write/delete) are handled by FUSE callbacks that proxy to B2 API calls.
71-
6. Metadata is cached (10min for attrs, 5min for directories) to keep Finder responsive.
83+
The extension runs as an XPC service managed by macOS. When you mount a `b2://` URL, the kernel routes filesystem operations to the extension. No daemon process, no socket IPC, no polling -- it's native.
7284

7385
## File operations
7486

75-
| Operation | Strategy | Notes |
76-
|-----------|----------|-------|
77-
| Read | Download + local cache | Cached for performance |
78-
| Write | Buffer locally, upload on close | Avoids partial uploads |
79-
| Delete | Permanent delete | B2 versioning ignored for MVP |
80-
| Mkdir | Create empty `.bzEmpty` marker | B2 has no real directories |
81-
| Rename | Server-side copy + delete | B2 has no rename API |
82-
| Dir rename | Not supported | Returns `ENOSYS` (MVP limitation) |
87+
| Operation | Strategy |
88+
|-----------|----------|
89+
| Read | Download to local staging on open, read from disk |
90+
| Write | Write to local staging, upload to B2 on close |
91+
| Delete | `b2_delete_file_version` (permanent) |
92+
| Mkdir | Zero-byte marker with `application/x-directory` content type |
93+
| Rename | Server-side copy + delete original |
94+
| Dir rename | Not supported (B2 limitation) |
8395

84-
## Project structure
85-
86-
```
87-
Sources/CloudMount/
88-
├── CloudMountApp.swift # Main app, BucketConfigStore, persistence
89-
├── DaemonClient.swift # Actor-based IPC client
90-
├── CredentialStore.swift # Keychain storage
91-
├── MacFUSEDetector.swift # macFUSE installation detection
92-
├── MenuContentView.swift # Menu bar with mount controls
93-
└── SettingsView.swift # Credentials + Buckets tabs
94-
95-
Daemon/CloudMountDaemon/src/
96-
├── main.rs # Daemon entry point
97-
├── b2/
98-
│ ├── client.rs # B2 API client (auth, list, upload, delete)
99-
│ └── types.rs # B2 API types with serde
100-
├── cache/
101-
│ └── metadata.rs # Moka-based metadata cache
102-
├── fs/
103-
│ ├── b2fs.rs # FUSE filesystem implementation
104-
│ └── inode.rs # Stable path-to-inode mapping
105-
├── ipc/
106-
│ ├── protocol.rs # JSON protocol definitions
107-
│ └── server.rs # Unix socket IPC server
108-
└── mount/
109-
└── manager.rs # Mount lifecycle management
110-
```
96+
## Known limitations
97+
- **Backblaze B2 only** -- S3-compatible providers planned.
98+
- **Directory rename not supported** -- B2 has no rename primitive; file rename uses copy+delete.
99+
- **No symlinks or hard links** -- B2 is a flat object store.
111100

112-
## Known limitations (v1.0)
113-
- **Backblaze B2 only** — generic S3 support planned for v2.
114-
- **Disk usage shows N/A** — B2 has no bucket size API; protocol is wired for when computation is added.
115-
- **Directory rename not supported** — complex operation deferred.
116-
- **Single-bucket mount** — multi-bucket simultaneous mount planned for v2.
117-
- **No auto-mount** — manual mount from menu bar required each session.
118-
119-
## Roadmap
120-
- **v1.1** — Multi-bucket support, auto-mount on startup, connection status indicators.
121-
- **v2.0** — Generic S3 provider support (AWS S3, Wasabi, DigitalOcean Spaces), custom endpoints.
122-
123-
## Tech stack
124-
- **Swift/SwiftUI** — Native menu bar app, Keychain integration
125-
- **Rust** — FUSE daemon, async I/O with Tokio
126-
- **fuser 0.16** — FUSE filesystem trait implementation
127-
- **reqwest** — HTTP client for B2 API
128-
- **moka** — High-performance concurrent cache (sync mode for FUSE compatibility)
129-
- **serde** — JSON serialization for IPC protocol and B2 API types
130-
- **tracing** — Structured logging
101+
## Privacy
102+
CloudMount reads/writes only to the Keychain (credentials), App Group UserDefaults (mount configs), and your chosen mount points. No telemetry, no analytics, no network calls except to the B2 API and Sparkle update feed.
131103

132104
## Related
133-
- [Mountain Duck](https://mountainduck.io/) — Commercial alternative ($39) that inspired this project.
134-
- [macFUSE](https://osxfuse.github.io/) — Required kernel extension for userspace filesystems on macOS.
135-
- [rclone](https://rclone.org/) — CLI tool for cloud storage (different approach — sync, not mount).
105+
- [Mountain Duck](https://mountainduck.io/) -- Commercial alternative that inspired this project.
106+
- [rclone](https://rclone.org/) -- CLI tool for cloud storage (sync approach, not native mount).
136107

137108
## License
138-
MIT Eirik Breen ([ebreen](https://github.com/ebreen))
109+
MIT -- Eirik Breen ([ebreen](https://github.com/ebreen))

0 commit comments

Comments
 (0)