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):- macOS FUSE Setup, macFUSE + kernel extension + Apple Silicon recovery mode steps.
- Linux FUSE Setup,
fuse3install and/etc/fuse.conf. - Windows FUSE Setup, WinFsp install; experimental.
Install Mirage with the FUSE Extra
Verify
/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 onebackend
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
/Volumesentry appears when the filesystem goes live and is removed when it unmounts./Volumesis root-owned, so nothing here needs (or could get) elevated permissions. - Every mounted resource must be able to size its files. See below.
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.
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, sostat 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).Symlinks and Permissions
Symlinks live in Mirage’s namespace, not in any backend. Links created withln -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.