Skip to main content

Prerequisites

  • Python 3.11+
  • uv package manager (optional)

System FUSE

Install the OS-level FUSE driver first (see the support matrix for the full OS overview):

Install Mirage with the FUSE Extra

Or with uv:

Verify

If the printed path exists under /tmp/mirage-*, FUSE is wired up correctly. The Workspace constructor blocks until every kernel mount is live, so the mountpoint is ready to read as soon as the with block is entered, no sleep needed. (In TypeScript, where mounts are async, you await ws.fuseReady() instead.)

Per-mount backends

How a mount is exposed is configured per mount, through one backend field: A kernel-backed mount shows only that mount’s subtree. mountpoint pins where it lands; omit it for a fresh temp directory:
ws.fuse_mountpoints returns a {prefix: path} map of the live mountpoints.

Mounting without the kernel extension (FSKit)

Apple has deprecated third-party kernel extensions: on Apple Silicon the macFUSE kext already needs a reduced-security boot plus admin approval, and future macOS releases are expected to stop loading it entirely. FSKit (macOS 15.4+) is Apple’s supported userspace replacement, and macFUSE 5.x serves the same libfuse API through it. backend=fskit is mirage’s path to keep real mounts working on Macs where the kext is blocked. Only the kernel-to-userspace hop changes: Set backend on the mount:
kextstat stays empty and the mount table tags the volume fskit. Two rules are enforced at mount time rather than discovered at run time:
  • The mountpoint must be under /Volumes. FSKit refuses anything else. Mirage names one automatically when you do not pass a path, and rejects an explicit path outside it.
  • Mirage does not create the mountpoint, macOS does. An FSKit mount is a volume: the /Volumes entry appears when the filesystem goes live and is removed when it unmounts. /Volumes is root-owned, so nothing here needs (or could get) elevated permissions.
  • Every mounted resource must be able to size its files. See below.
There is deliberately no auto value: auto-selecting FSKit would silently break every API-backed mount, and an option whose safe value is always the default is a trap. MountBackend.FSKIT is macOS-only and raises elsewhere. The metadata write surface works: create, mkdir, rename, unlink and in-place overwrites, pinned by integ/fuse/truth_fskit.json. This depends on mirage/fuse/darwin.py, which declares macFUSE’s Darwin-only callbacks (setattr_x, renamex) that the FSKit shim uses to finalize new items and route renames; stock mfusepy leaves those slots NULL, which fails every create with ENOSYS after the file already landed.
Treat fskit mounts as read-mostly. The macFUSE FSKit shim flushes pages a file did not already have (a new file, cp, > truncation) as NUL bytes; appends are hit-or-miss. The writer sees no error, and the kernel page cache serves the written data back, so the corruption only shows in the backing store. Mirage warns at mount time on writable fskit mounts (check_writes); use MountBackend.FUSE for writes. Pinned in integ/fuse/truth_fskit.json so a shim fix flips the test.
Two more upstream FSKit-shim caveats (macFUSE 5.3.3): #1181, running a binary off an fskit mount fails until the file has been read once; and #1165, new entries in the volume root may not appear in a live mount because the root readdir cache cannot be invalidated.
Size-unknown files read as empty. There is no direct_io, so reads stop at the size stat reported; a file that stats 0 pre-open reads as empty with exit code 0. Mirage warns at mount time, naming the degraded mounts (SIZES_ALWAYS_KNOWN). Byte stores (ram, disk, redis, s3, gridfs) and Linear (size push-down) qualify; for the rest, use MountBackend.FUSE or scope the fskit mount to a sized subtree.
examples/python/fuse/fskit.py runs all of this end to end: the size warning, exact reads on a live mount, and each write op with its result. TypeScript supports the same backend with the same guards (details); examples/typescript/fuse/fskit.ts is its live end-to-end check.

Size semantics for API-backed files

Some resources (Linear, Trello, Slack, …) cannot report a file’s size without fetching its content, so stat returns an unknown size. Over the FUSE mount these files behave like Linux /proc files: they stat as 0 bytes until first open, and become fully readable the moment anything opens them. Mirage mounts with direct_io (the kernel reads to EOF regardless of the reported size) and attr_timeout=0 (post-open fstat returns the real size of the now-fetched content, kept warm in a 30-second cache). What that means per tool: Mirage never reports a fake size and never fetches content during stat: returning real sizes eagerly would fire one API call per file on every ls -l.
Windows differs: it cannot query attributes without opening a handle, so a per-file stat of a size-unknown file fetches the content and shows the real size immediately. Directory listings stay cheap. See Windows FUSE Setup for the other Windows-specific behaviors (unmount at process exit, mount-level ownership).
macOS allows only one in-process FUSE mount. macFUSE registers a process-global signal source, so a second simultaneous mount in the same process fails with fuse: cannot register signal source. Multiple per-mount FUSE mounts work on Linux and Windows; on macOS, use a kernel backend on a single mount per workspace (or run additional mounts in separate processes).
Symlinks live in Mirage’s namespace, not in any backend. Links created with ln -s in the Mirage shell (or with ln -s inside the mountpoint) appear as real symlinks over FUSE: ls -l shows lrwxrwxrwx, readlink prints the target, and reads follow the link. Absolute targets are displayed relative to the link’s directory so they always resolve inside the mountpoint. Permission bits shown over FUSE come from Mirage’s metadata (chmod, chown, touch results), and they are display only: access control is enforced by Mirage mount modes, not by the kernel. For that reason, never mount with the default_permissions FUSE option. It would make the kernel enforce the displayed bits, and a chmod 000 could lock Mirage out of its own files.