Skip to content

feat: mount authorized msg resources as a read-only FUSE filesystem - #233

Merged
LIghtJUNction merged 7 commits into
mainfrom
work/fuse-mount-20261001
Oct 2, 2026
Merged

LIghtJUNction merged 7 commits into
mainfrom
work/fuse-mount-20261001

Conversation

@LIghtJUNction

Copy link
Copy Markdown
Contributor

User request

Introduce FUSE support so users can mount the msg website's resource tree in a local folder.

Implementation

  • msg mount ~/msg, with --path for a subtree and existing --server, account, key, certificate and OAuth identity selection.
  • Reuse authorized file.stat, cursor-paginated discovery.read_query and signed, revision-pinned HTTP byte ranges. file.stat now returns the authoritative resource-type container flag.
  • Original Markdown/binary content; authorized JSON for revisionless leaves. This is the resource namespace, not a crawler, server disk export, Git worktree or rendered page mirror.
  • Read-only mount, current OS user only, nodev,nosuid,noexec; no automatic ACK, follow, reaction, tool execution or payment. No persistent content cache; raw reads use direct I/O and reauthorize each content request.
  • Explicit request, directory and open-handle limits; sanitize remote failures into filesystem errno; no cross-origin redirects, unsafe child paths or silent truncated listings.
  • Optional msgctl[fuse] dependency and locked mfusepy 3.1.1; normal CLI use does not import native FUSE.
  • Foreground mount with manual unmount and cancellation cleanup. Includes fixes for libfuse3 max-read negotiation and mountpoint checks that must run off the SDK event loop.

Verification

  • Python 3.15.0rc2: 35 client contract tests passed in the initial cloud verification run; uv locked installation and pinned Ruff checks passed.
  • The initial real kernel smoke exposed a read-size negotiation mismatch. It is fixed in the current head along with an event-loop deadlock risk; the FUSE workflow reruns actual Linux mount/list/stat/read/seek/write rejection/unmount/cancellation.
  • Added lifecycle tests and real PostgreSQL + HTTP service integration tests covering raw binary/empty files, revision pinning and revoked permissions. Current-head results are tracked by CI; not claimed passed before execution.
  • Local Python 3.13 compatibility-only copy: 37 unit/lifecycle tests passed. Project source and target remain Python 3.15.

Rollout / boundaries

Both client and server need this change because the client requires file.stat.container; older servers fail explicitly rather than guessing. The feature is not yet published to PyPI or deployed. Linux gets real kernel CI; macOS/macFUSE is implemented but not hardware-validated. Native Windows is unsupported. Writes remain explicit CLI operations.

Usage and limits: docs/fuse.md.

The one-off integration workflow was removed from the branch after applying the reviewed patch; it is not part of the final PR diff.

Copilot AI balanced review requested due to automatic review settings October 1, 2026 22:00
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copy link
Copy Markdown
Contributor Author

One lifecycle issue in the current head (4233fcef): cancellation can wait indefinitely when unmount fails.

In serve_mount, fusermount3 -u / umount runs with stderr discarded, its return code is ignored, and the next step awaits the native FUSE task without a deadline. If another process keeps an open file or its working directory inside the mount, ordinary unmount can fail because the filesystem is busy (umount documentation). The mount and worker then remain active, so the cancelled coroutine does not finish until someone successfully unmounts it separately.

Please handle a failed or stalled unmount explicitly and define a bounded cleanup path. Add a regression where the unmount helper returns nonzero while the native worker remains alive, plus a kernel case with an open handle or CWD during cancellation. The current lifecycle mock always returns success, and the kernel smoke closes its file handles before cancelling.

This finding is from source inspection; I have not run a local reproduction.

Copy link
Copy Markdown
Contributor Author

FUSE acceptance completed on f3c4927c5a488fb216dce0568505d4bd76b7f3fe

Dedicated workflow FUSE mount #9 succeeded: https://github.com/TokenNotIncluded/msg/actions/runs/36933006973

  • Python 3.15.0rc2; locked optional-dependency installation passed.
  • Pinned Ruff lint and format checks passed.
  • Actual Linux /dev/fuse mount passed: directory listing, stat, full binary reads, random seek, rejection of writes/deletes/mkdir, explicit unmount and task-cancellation cleanup.
  • 39 tests passed, 0 failed, 0 errors, 0 skipped (JUnit artifact fuse-acceptance, 24.932 s). These include real PostgreSQL + HTTP service tests for pinned revisions, raw binary and empty files, private subtree listing, and denial after permissions are revoked. Normal mounted reads do not create reactions/follows/watches.

The initially discovered libfuse3 max-read negotiation mismatch and the off-loop mountpoint-check deadlock risk are fixed and covered. This replaces the earlier pending native/service verification status in the PR description. Repository-wide CI is separate; this comment does not claim every unrelated workflow has completed.

No merge, PyPI release, or deployment was performed. Both client and server require this feature version (file.stat.container). First release remains explicitly read-only; macOS still lacks hardware acceptance.

@LIghtJUNction
LIghtJUNction merged commit 0bb596c into main Oct 2, 2026
9 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants