---
title: FUSE
icon: hard-drive
description: Set up FUSE support for MIRAGE in Python.
---

## Prerequisites

- Python 3.11+
- [uv](https://docs.astral.sh/uv/) package manager (optional)

## System FUSE

Install the OS-level FUSE driver first (see the
[support matrix](/home/setup/fuse) for the full OS overview):

- [macOS FUSE Setup](/home/setup/macos), macFUSE + kernel extension + Apple Silicon recovery mode steps.
- [Linux FUSE Setup](/home/setup/linux), `fuse3` install and `/etc/fuse.conf`.
- [Windows FUSE Setup](/home/setup/windows), WinFsp install; experimental.

## Install Mirage with the FUSE Extra

```bash
pip install "mirage-ai[fuse]"
```

Or with uv:

```bash
uv add "mirage-ai[fuse]"
```

## Verify

```python
from mirage import Mount, MountMode, Workspace
from mirage.resource.ram import RAMResource

with Workspace(
    {"/data": Mount(RAMResource(), mode=MountMode.WRITE, fuse=True)}) as ws:
    print("mountpoints:", ws.fuse_mountpoints)
```

If the printed path exists under `/tmp/mirage-*`, FUSE is wired up correctly.

The `Workspace` constructor blocks until every `fuse` 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 FUSE

FUSE is configured **per mount**. Each mount whose `fuse` is set is exposed at
its own mountpoint, showing only that mount's subtree. The value is either
`true` (mount at a fresh temp directory) or a path string (mount there,
creating the directory if missing):

```yaml
mode: WRITE
mounts:
  /data:
    resource: ram
    fuse: /tmp/data-repo   # explicit path
  /s3:
    resource: s3
    fuse: true             # temp directory
  /logs:
    resource: disk
    # no fuse key, not FUSE-exposed
```

`ws.fuse_mountpoints` returns a `{prefix: path}` map of the live mountpoints.

## 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:

| Tools | Behavior |
| --- | --- |
| `cat`, `grep`, `head`, `cp`, `md5sum`, `sed`, `sort` | correct content, always |
| `wc -c`, `tail -c` | correct (they fstat after open, which serves the real size) |
| `ls -l`, `du`, `find -size`, `test -s` | report 0 until the file has been opened recently |
| `tar`, `rsync`, `scp` | see 0 at stat time and copy empty content, exactly like `tar` over `/proc`; read the file first or use `cat`/`cp` based flows |

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`.

<Note>
  **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](/home/setup/windows) for the other Windows-specific
  behaviors (unmount at process exit, mount-level ownership).
</Note>

<Warning>
  **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, enable `fuse` on a single
  mount per workspace (or run additional mounts in separate processes).
</Warning>

## Symlinks and Permissions

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.
