Skip to content

Add localhost NFS mounts for macOS #3

Description

@brynary

Motivation

Quarry currently exposes Library contents as ordinary files through Linux FUSE only. That works for Linux users, but FUSE is a poor default on macOS because it requires extra kernel/filesystem software and has a higher support burden. The user-facing goal is to let macOS users mount a Quarry Library with built-in OS tooling while preserving Quarry's existing guarantees: committed-state projection, stable identities, read-only mode, auto-commit writable mode, and Markdown reconciliation.

Desired UX:

mkdir -p /Volumes/quarry-notes
quarry mount notes /Volumes/quarry-notes --protocol nfs

or, if we decide to make protocol selection automatic:

quarry mount notes /Volumes/quarry-notes

Recommended direction from research:

  • Add localhost NFSv3 support first.
  • Keep existing Linux FUSE support.
  • Defer SMB until Windows support or Finder-native sharing becomes a primary product goal.

Why NFS first:

  • macOS has a built-in NFS client, so users do not need macFUSE or another kernel extension.
  • Quarry already has stable numeric identities through the storage inodes table, which maps well to NFS file handles.
  • The existing FuseProjection is already close to a protocol-neutral store-backed filesystem projection.
  • nfsserve is a Rust NFSv3 server built for this exact class of problem: a localhost user-mode filesystem mount that avoids FUSE driver friction. Its README includes macOS mount examples and calls out FUSE pain on Mac/Windows as motivation: https://github.com/huggingface/nfsserve

Why not SMB first:

Important semantic risk:

The current FUSE writable path stages writes per open file handle and publishes on flush/release. NFSv3 is more stateless: writes arrive as RPCs, with COMMIT but no true close. The first implementation should be read-only, then add writable behavior only after the NFS write/commit semantics are proven against Quarry's Markdown reconciliation model.

Implementation Plan
# Add localhost NFS mounts for macOS

## Current Baseline

- `crates/quarry-fuse` owns the current mount implementation.
- `FuseProjection` is the reusable store-backed projection layer:
  - path normalization
  - directory inference and persisted empty directories
  - stable inode lookup through `QuarryStore`
  - open handle buffers
  - read-only enforcement
  - Markdown whole-file reconciliation through `write_block_markdown`
  - raw byte writes through `put_document`
  - storage event invalidation generation
- The Linux-only FUSE adapter lives inside `crates/quarry-fuse/src/lib.rs` under `linux_mount` and implements `fuse3::raw::Filesystem`.
- `quarry mount` currently opens the store directly and runs `mount_library_with_shutdown`; with `--serve-addr`, it runs REST and FUSE in the same process.
- The older server-managed mounts plan exists in `docs/superpowers/plans/2026-05-28-cli-api-daemon-and-server-mounts.md`, but the implementation is not present. Do not require server-managed mounts for this issue.

## Requirements

- R1. Support a macOS-friendly localhost mount path using NFSv3.
- R2. Preserve the existing Linux FUSE behavior and CLI compatibility.
- R3. Use loopback-only serving by default. The NFS listener must not expose Quarry Libraries on non-loopback interfaces unless explicitly requested in a future issue.
- R4. Support read-only NFS mounts before writable NFS mounts.
- R5. Reuse Quarry's existing stable inode/path identity model rather than inventing a second identity system.
- R6. Keep NFS as a projection over committed Quarry state, not a separate source of truth.
- R7. Preserve Markdown semantics: Markdown writes must reconcile through the existing whole-file writer and must not silently degrade block documents to raw bytes.
- R8. Preserve raw document semantics: non-Markdown bytes round-trip exactly.
- R9. Document macOS prerequisites, mount command behavior, unmount command, and known limitations.
- R10. Defer SMB support. Record it as a future option, not part of this implementation.

## Proposed User-Facing Shape

Add an explicit protocol selector:

    quarry mount <library> <mountpoint> --protocol fuse
    quarry mount <library> <mountpoint> --protocol nfs

Recommended defaults:

- Linux: default to `fuse` to preserve current behavior.
- macOS: default to `nfs` once the NFS adapter is stable.
- Other platforms: return a clear unsupported error unless NFS support is verified there.

Read-only mode should work consistently:

    quarry mount notes /Volumes/quarry-notes --protocol nfs --read-only

Manual NFS mount command shape for macOS spike:

    mount_nfs -o nolocks,vers=3,tcp,rsize=131072,actimeo=0,port=<port>,mountport=<port> 127.0.0.1:/notes /Volumes/quarry-notes

Exact options should be validated during the spike. `actimeo=0` or similarly short caching may be needed for Quarry's committed-state visibility model. `nolocks` is likely acceptable for phase one because Quarry does not expose POSIX-perfect locking today.

## Technical Approach

1. Spike `nfsserve` against a minimal in-memory filesystem.

   Goals:

   - Build on current Rust stable.
   - Serve on localhost with a non-privileged port.
   - Mount successfully on macOS with `mount_nfs`.
   - Exercise `ls`, `cat`, `find`, `rg`, `cp`, editor write, rename, and unmount.
   - Confirm the crate can represent stable file handles using Quarry's inode table.
   - Confirm read/write/commit call ordering from macOS for common workflows.

   Exit criteria:

   - If `nfsserve` cannot support macOS mounting or needed write semantics, stop and document the blocker before building Quarry integration.
   - If it works for read-only but write semantics are ambiguous, ship/readiness-plan read-only NFS first.

2. Introduce a protocol-neutral projection boundary if needed.

   Preferred low-risk path:

   - Keep `FuseProjection` behavior intact.
   - Extract or rename shared logic only when the NFS adapter needs it.
   - Avoid large refactors before the NFS spike proves viability.

   Possible target shape:

       crates/quarry-mount/
         shared projection model and tests
       crates/quarry-fuse/
         Linux FUSE adapter
       crates/quarry-nfs/
         localhost NFSv3 adapter

   Do not force this crate split if a smaller first PR can reuse code safely.

3. Implement read-only NFS projection.

   Cover:

   - root export
   - file handle to path resolution
   - getattr/stat
   - lookup
   - readdir/readdirplus equivalent
   - read
   - stable file and directory identities
   - directory inference from document paths
   - persisted empty directories if exposed in read-only listings

   Tests:

   - Unit tests for handle/path mapping without kernel mount.
   - Existing projection tests should continue passing.
   - Add a manual macOS smoke script or documented checklist.

4. Add CLI integration for NFS read-only.

   Behavior:

   - `quarry mount notes /Volumes/quarry-notes --protocol nfs --read-only` starts a local NFS server.
   - The CLI runs the macOS `mount_nfs` command when on macOS.
   - Foreground command waits for Ctrl-C and unmounts where possible.
   - If mounting fails, print the exact manual mount command and server port for debugging.

   Keep existing FUSE command behavior unchanged on Linux.

5. Add writable NFS only after read-only is stable.

   Key design problem:

   - FUSE currently publishes on `flush`/`release` from a per-open handle buffer.
   - NFSv3 write flows do not provide the same close event.

   Candidate strategies to evaluate during the spike:

   - Buffer writes by NFS file handle and publish on `COMMIT`.
   - Publish after a short debounce window following the last write.
   - For temp-file editor save patterns, rely on create/write/commit/rename and route rename-over-target through the existing Markdown reconciler.
   - Keep writable NFS behind an explicit opt-in until editor behavior is verified.

   Required tests before considering writable complete:

   - Create/write/read raw file.
   - Create/write/read Markdown file.
   - Truncate existing Markdown file and write new content.
   - Atomic-save rename over existing Markdown preserves target document id, block ids, and review anchors.
   - Concurrent canonical edit plus NFS Markdown write converges with conflict review items, matching FUSE expectations.
   - Invalid UTF-8 into Markdown returns an error and does not replace the canonical document.
   - Raw binary bytes round-trip exactly.

6. Wire storage invalidation/caching carefully.

   - Keep store event subscription behavior.
   - Choose conservative NFS attribute caching for phase one.
   - Document any visibility delay if caching cannot be fully disabled.
   - Ensure REST/Git/FUSE/NFS writes are visible across surfaces under the same process model.

7. Update docs.

   - `README.md`: add NFS/macOS mount summary and verification command.
   - `docs/operations/fuse.md`: clarify FUSE remains Linux-only.
   - Add `docs/operations/nfs.md` or rename to a broader `docs/operations/mounts.md`.
   - Document macOS mount command, unmount command, troubleshooting, and limitations.

8. Verification matrix.

   Automated where possible:

   - `cargo test -p quarry-fuse`
   - new NFS adapter unit tests
   - projection/shared tests
   - `cargo test --workspace`
   - `cargo check --workspace --all-targets`

   Manual macOS smoke:

   - initialize a Quarry root
   - put Markdown and raw files
   - mount read-only over NFS
   - `ls`, `cat`, `find`, `rg`
   - unmount cleanly
   - remount and confirm stable identities/contents

   Manual writable smoke before writable release:

   - `cp` a Markdown file into the mount
   - edit with `vim` or another temp-rename editor
   - truncate and rewrite a Markdown file
   - copy a binary file
   - verify REST reads see NFS writes
   - verify NFS reads see REST writes after invalidation/caching window

## Non-Goals

- SMB support.
- Windows mount support.
- NFSv4 support.
- Network exposure beyond localhost.
- POSIX-perfect filesystem semantics.
- Hard links, symlinks, device files, or executable build-artifact workflows.
- Server-managed mount lifecycle unless that separate plan is revived.

## Research Notes

- `nfsserve` is the best current Rust lead for an embedded NFSv3 server:
  - https://github.com/huggingface/nfsserve
  - It was built as a FUSE alternative for user-mode mounts.
  - It has macOS mount examples.
  - It is described by its maintainers as incomplete but functional, so start with a spike.
- macOS `mount_nfs` supports explicit NFS version, TCP, port/mountport, lock, and cache controls:
  - https://leopard-adc.pepas.com/documentation/Darwin/Reference/ManPages/man8/mount_nfs.8.html
- SMB should be deferred because a robust embedded SMB server is a much larger project. Samba VFS integration is possible but introduces a system dependency and a broad C module surface:
  - https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
- Samba's macOS compatibility guidance uses `fruit` and `streams_xattr`, which is useful later if SMB becomes a product priority:
  - https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/deploying_different_types_of_servers/assembly_using-samba-as-a-server

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions