feat: Linux desktop app with FUSE mount#220
Conversation
Phase 11.3: Linux Desktop - Implementation decisions documented - Phase boundary established
- Move PKG_CONFIG_PATH from global [env] to macOS-only target sections so Linux builds use system pkg-config to find fuse3.pc from libfuse3-dev - Add linux-native-sync-persistent feature to keyring crate for Secret Service API (D-Bus) and keyutils backend on Linux Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: f5ed94b91d4f
…d special files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 2da25e26c658
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 17bbc8ad4d04
- Add linux.deb.depends: libfuse3-3, fuse3, libwebkit2gtk-4.1-0, libayatana-appindicator3-1 - Add linux.appimage.bundleMediaFramework: false - WinFsp resource glob left as-is (placeholder MSI tracked in git, harmless on Linux) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: b542a6051fe9
- cargo-check-linux: ubuntu-22.04, system deps, fuse feature check - build-desktop-linux: full Tauri build producing .deb and AppImage - Both jobs install libwebkit2gtk-4.1-dev, libfuse3-dev, and all Tauri deps - Cache key prefix: linux-cargo- (distinct from macos/windows) - Matches existing macOS/Windows job patterns exactly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 10e7a38cc434
Tasks completed: 2/2 - Linux bundle config in tauri.conf.json - CI workflow with Linux cargo-check and build jobs SUMMARY: .planning/phases/11.3-linux-desktop/11.3-02-SUMMARY.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 18f9f17781f4
…ng feature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: d198ece01424
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: bccd15e4d631
Phase completion reverted — code was never compiled or tested locally. Added Plan 03: build, run, and full UAT against staging API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 54357797e2c3
- Fix vendored fuser channel.rs: use read() on Linux /dev/fuse instead of recv(MSG_PEEK) which returns ENOTSOCK on device files (not sockets) - Keep recv(MSG_PEEK) loop-read for macOS FUSE-T socket transport - Remove AutoUnmount from Linux mount options to avoid requiring user_allow_other in /etc/fuse.conf - Fix DefaultPermission -> DefaultPermissions typo Desktop app now compiles, authenticates via dev-key, and mounts FUSE successfully on Linux. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ad race Three interconnected bugs discovered during Linux UAT: 1. Vendored fuser channel.rs used recv(MSG_PEEK) on /dev/fuse, which returns ENOTSOCK on Linux device files. Fixed with cfg(target_os) to use simple read() on Linux and keep recv() loop-read for macOS FUSE-T sockets. 2. File overwrite (echo "new" > file) produced stale content because: - Linux kernel calls open() BEFORE setattr(size=0) for O_TRUNC - open() pre-populated temp file with old content before truncation - setattr(size=0) with fh=None couldn't find the temp file to truncate Fixed: setattr now scans open_files by ino when fh=None. 3. Stale upload race: old upload completing after truncate would overwrite the cleared CID with the old CID. Fixed with write_generation counter on InodeData — uploads carry generation at spawn time, drain skips mismatched generations. Additional fixes: - Remove AutoUnmount from Linux mount options (requires user_allow_other) - Add FOPEN_DIRECT_IO to bypass kernel page cache (prevents stale reads) - Fix DefaultPermission -> DefaultPermissions typo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ility - Create tray-icon-linux.png and tray-icon-linux@2x.png with green (#00D084) icon - Use Linux-specific icon via cfg(not(any(macos, windows))) in tray/mod.rs - Set icon_as_template to macOS-only via cfg!(target_os = "macos") - Black template icon was invisible on dark Linux desktop panels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 4447935e6dd7
Tasks completed: 7/7 - Build desktop app on Linux with cargo build --features fuse - Dev-key auth against staging, FUSE mount verified - File operations UAT (create, read, overwrite, delete, rename) - Directory operations UAT (create, nest, rename, delete) - Large file round-trip (1MB, 10MB MD5 match) and batch (20 files) - Platform special files filtered (.Trash-*, .gvfs, .xdg-volume-info) - Tray, sync, and unmount verified (human-verified) UAT: 18/18 tests passed Bugs fixed: 4 + 1 UX fix SUMMARY: .planning/phases/11.3-linux-desktop/11.3-03-SUMMARY.md UAT: .planning/phases/11.3-linux-desktop/11.3-UAT.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: a9df03e4a06d
18/18 UAT tests passed. 4 bugs fixed during testing. PLAT-01 requirement marked Complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 5de19067603d
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
WalkthroughThis pull request completes Phase 11.3 - Linux Desktop by implementing Linux support for the CipherBox desktop application. Changes include platform-specific FUSE code gating, Linux CI/CD workflows, tauri.conf.json packaging configuration, write-generation tracking for upload staleness detection, and comprehensive planning documentation. Phase is marked complete across planning artifacts. Changes
Sequence DiagramsequenceDiagram
actor User
participant App as CipherBox App
participant FUSE as FUSE Kernel
participant BackgroundSync as Background Sync
participant UploadQueue as Upload Queue
User->>App: Open file for writing (O_TRUNC)
App->>FUSE: write_generation = N (initialize)
App->>FUSE: Create temp file, mount at CipherBox home
User->>FUSE: Write data to file
FUSE->>App: onWrite (with FOPEN_DIRECT_IO bypass)
App->>App: Update inode, increment write_generation to N+1
App->>App: Clear file CID (stale)
App->>UploadQueue: Queue upload with write_generation = N+1
par Background Operations
BackgroundSync->>UploadQueue: Poll for uploads
UploadQueue->>App: Retrieve upload (write_generation = N+1)
App->>App: Check: current write_generation == N+1?
App->>App: Yes → Update remote CID, publish IPNS
App->>BackgroundSync: Upload complete
and User Continues
User->>FUSE: Write more data before upload finishes
FUSE->>App: onWrite
App->>App: Increment write_generation to N+2
end
Note over App,BackgroundSync: Stale upload detected: N+1 ≠ N+2
App->>App: Discard N+1 upload, mark as stale
App->>UploadQueue: Queue new upload with write_generation = N+2
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…mpat [target.<triple>.env] is not supported in Rust 1.88 (pinned in rust-toolchain.toml). Global [env] is safe because the custom pkg-config/ only contains fuse.pc (macOS FUSE-T), not fuse3.pc, so Linux builds fall through to system pkg-config for libfuse3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 9eb28f8f5a66
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/desktop/src-tauri/.cargo/config.toml (1)
1-5: Clarify PKG_CONFIG_PATH behavior withforce = true.The comment explains the intent well, but
force = truewill override any user-setPKG_CONFIG_PATH. On Linux, this works becausefuse3.pcisn't in the localpkg-config/directory, sopkg-configwill search system paths as a fallback.However, if a Linux developer has a custom
PKG_CONFIG_PATHfor other dependencies, this could cause issues. Consider whetherforce = falsewould work (allowing user paths to take precedence while still adding this as a fallback).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src-tauri/.cargo/config.toml` around lines 1 - 5, The PKG_CONFIG_PATH entry in the [env] table currently uses force = true which will overwrite any user-set PKG_CONFIG_PATH; change it to force = false (or remove the force key) so the repo-provided "pkg-config" path is appended/used as a fallback rather than overriding developer environments; update the PKG_CONFIG_PATH declaration (the PKG_CONFIG_PATH key in the [env] block in config.toml) and adjust the comment to mention it acts as a fallback when not present in the user path..github/workflows/ci.yml (1)
340-352: Deduplicate Linux dependency install steps to reduce CI drift.The apt package block is duplicated in two jobs. Extracting it into a shared anchor/composite action will prevent divergence over time.
♻️ Example with YAML anchor
+ x-linux-deps: &linux_deps | + sudo apt-get update + sudo apt-get install -y \ + libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libssl-dev \ + libxdo-dev \ + libfuse3-dev \ + pkg-config \ + build-essential ... - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - libssl-dev \ - libxdo-dev \ - libfuse3-dev \ - pkg-config \ - build-essential + run: *linux_deps ... - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - libssl-dev \ - libxdo-dev \ - libfuse3-dev \ - pkg-config \ - build-essential + run: *linux_depsAlso applies to: 481-493
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml around lines 340 - 352, The "Install system dependencies" apt-get install block is duplicated; extract that package list into a single reusable definition (either a YAML anchor or a composite action) and replace both occurrences with a reference to it to avoid drift; specifically refactor the step named "Install system dependencies" that installs libwebkit2gtk-4.1-dev, libayatana-appindicator3-dev, librsvg2-dev, libssl-dev, libxdo-dev, libfuse3-dev, pkg-config and build-essential (appearing around the shown block and again at lines 481-493) into one shared anchor or action and update both job steps to use that anchor/action.apps/desktop/src-tauri/src/fuse/mod.rs (1)
1199-1199: Consider handling potential non-UTF-8 paths gracefully.The
unwrap()onmount_path.to_str()could panic if the home directory contains non-UTF-8 characters. While this is rare and consistent with the macOS implementation, consider usingto_string_lossy()or returning an error for robustness.♻️ Optional: Handle non-UTF-8 paths more gracefully
- let mount_str = mount_path.to_str().unwrap(); + let mount_str = mount_path.to_str() + .ok_or_else(|| format!("Mount path contains invalid UTF-8: {:?}", mount_path))?;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src-tauri/src/fuse/mod.rs` at line 1199, The code calls mount_path.to_str().unwrap() which can panic on non-UTF-8 paths; replace the unwrap with a safe conversion (e.g., use mount_path.to_string_lossy() and assign into mount_str) or propagate a Result/error from the enclosing function instead of unwrapping so non-UTF-8 home dirs are handled gracefully; update any downstream uses of mount_str accordingly (look for the mount_path and mount_str variables where this conversion occurs).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src-tauri/vendor/fuser/src/channel.rs`:
- Around line 66-115: The header framing is broken: stop breaking out after the
first MSG_PEEK and instead loop calling libc::recv(fd, ..., MSG_PEEK) until
header_read == 4 (or return EOF/error) so header_buf contains all 4 bytes before
parsing; parse expected = u32::from_ne_bytes(header_buf) and then set to_read =
expected (not min(buffer.len())) and ensure you either resize/validate buffer to
expected or fail if buffer is too small, then loop-read exactly to_read bytes
using libc::read into buffer until total == to_read (handling errors/EOF as
before); update the header_buf/header_read, expected, to_read and total logic
accordingly.
---
Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 340-352: The "Install system dependencies" apt-get install block
is duplicated; extract that package list into a single reusable definition
(either a YAML anchor or a composite action) and replace both occurrences with a
reference to it to avoid drift; specifically refactor the step named "Install
system dependencies" that installs libwebkit2gtk-4.1-dev,
libayatana-appindicator3-dev, librsvg2-dev, libssl-dev, libxdo-dev,
libfuse3-dev, pkg-config and build-essential (appearing around the shown block
and again at lines 481-493) into one shared anchor or action and update both job
steps to use that anchor/action.
In `@apps/desktop/src-tauri/.cargo/config.toml`:
- Around line 1-5: The PKG_CONFIG_PATH entry in the [env] table currently uses
force = true which will overwrite any user-set PKG_CONFIG_PATH; change it to
force = false (or remove the force key) so the repo-provided "pkg-config" path
is appended/used as a fallback rather than overriding developer environments;
update the PKG_CONFIG_PATH declaration (the PKG_CONFIG_PATH key in the [env]
block in config.toml) and adjust the comment to mention it acts as a fallback
when not present in the user path.
In `@apps/desktop/src-tauri/src/fuse/mod.rs`:
- Line 1199: The code calls mount_path.to_str().unwrap() which can panic on
non-UTF-8 paths; replace the unwrap with a safe conversion (e.g., use
mount_path.to_string_lossy() and assign into mount_str) or propagate a
Result/error from the enclosing function instead of unwrapping so non-UTF-8 home
dirs are handled gracefully; update any downstream uses of mount_str accordingly
(look for the mount_path and mount_str variables where this conversion occurs).
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
apps/desktop/src-tauri/Cargo.lockis excluded by!**/*.lockapps/desktop/src-tauri/icons/tray-icon-linux.pngis excluded by!**/*.pngapps/desktop/src-tauri/icons/tray-icon-linux@2x.pngis excluded by!**/*.png
📒 Files selected for processing (24)
.github/workflows/ci.yml.planning/REQUIREMENTS.md.planning/ROADMAP.md.planning/STATE.md.planning/phases/11.3-linux-desktop/11.3-01-PLAN.md.planning/phases/11.3-linux-desktop/11.3-01-SUMMARY.md.planning/phases/11.3-linux-desktop/11.3-02-PLAN.md.planning/phases/11.3-linux-desktop/11.3-02-SUMMARY.md.planning/phases/11.3-linux-desktop/11.3-03-PLAN.md.planning/phases/11.3-linux-desktop/11.3-03-SUMMARY.md.planning/phases/11.3-linux-desktop/11.3-CONTEXT.md.planning/phases/11.3-linux-desktop/11.3-RESEARCH.md.planning/phases/11.3-linux-desktop/11.3-UAT.md.planning/phases/11.3-linux-desktop/11.3-VERIFICATION.mdapps/desktop/src-tauri/.cargo/config.tomlapps/desktop/src-tauri/Cargo.tomlapps/desktop/src-tauri/src/fuse/inode.rsapps/desktop/src-tauri/src/fuse/mod.rsapps/desktop/src-tauri/src/fuse/operations.rsapps/desktop/src-tauri/src/fuse/windows/operations.rsapps/desktop/src-tauri/src/registry/mod.rsapps/desktop/src-tauri/src/tray/mod.rsapps/desktop/src-tauri/tauri.conf.jsonapps/desktop/src-tauri/vendor/fuser/src/channel.rs
- Loop MSG_PEEK until all 4 header bytes are received (previously broke after first recv, risking incomplete header parse) - Error instead of silently truncating if FUSE message exceeds buffer size (prevents socket desync on next read) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 98b7b28a889b
Summary
cargo-check-linuxandbuild-desktop-linuxjobs on ubuntu-22.04/dev/fuse(useread()instead ofrecv(MSG_PEEK))open()beforesetattr(size=0)for O_TRUNC)UAT Results
18/18 tests passed against staging API on Linux:
Bugs Fixed During UAT
DefaultPermission→DefaultPermissions(plural)recv(MSG_PEEK)fails on/dev/fuse— cfg-gated toread()on LinuxAutoUnmountrequires/etc/fuse.confmodification — removed from Linux optionsTest plan
cargo-check-linuxjob passes (first run)build-desktop-linuxjob passes (produces .deb + AppImage)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Chores