> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# FUSE

> Set up FUSE support for MIRAGE with the TypeScript SDK.

## Prerequisites

* Node.js 20+
* [pnpm](https://pnpm.io/) (recommended, `npm` and `yarn` also work, but the FUSE binding's install script needs extra configuration with pnpm).

## System FUSE

Install the OS-level FUSE kernel support 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`.

<Note>
  **No Windows support in TypeScript.** `@zkochan/fuse-native` targets macOS
  and Linux only; its legacy Windows path builds against the unmaintained
  Dokany-based `fuse-shared-library-win32`, not WinFsp. Python's FUSE mounts
  run on Windows experimentally, see [Windows FUSE Setup](/home/setup/windows).
</Note>

## Install the Node Binding

The Node package (`@struktoai/mirage-node`) ships FUSE support via an **optional peer dependency** on [`@zkochan/fuse-native`](https://www.npmjs.com/package/@zkochan/fuse-native), the pnpm author's actively-maintained fork that works on Node 20+ and supports both macOS and Linux (thanks to [@zkochan](https://github.com/fuse-friends/fuse-native/issues/36#issuecomment-3089754579)).

```bash theme={null}
pnpm add @struktoai/mirage-node @zkochan/fuse-native
```

<Tip>
  Non-FUSE users don't need `@zkochan/fuse-native`, the base `@struktoai/mirage-node` package works without it. Only install the binding when you want a real mountpoint.
</Tip>

### Allow pnpm to run the install script

`@zkochan/fuse-native` compiles native code on install. pnpm blocks install scripts by default, allow this one explicitly in `pnpm-workspace.yaml`:

```yaml theme={null}
onlyBuiltDependencies:
  - '@zkochan/fuse-native'
```

Or, if you're not using a pnpm workspace, in `package.json`:

```json theme={null}
{
  "pnpm": {
    "onlyBuiltDependencies": ["@zkochan/fuse-native"]
  }
}
```

## macOS 4+ Symlink Workaround

<Tabs>
  <Tab title="macOS">
    macFUSE 4 ships `libfuse.2.dylib` instead of the legacy `libosxfuse.2.dylib` that `@zkochan/fuse-native` was built against. Create a one-time symlink:

    ```bash theme={null}
    sudo ln -sf /usr/local/lib/libfuse.2.dylib /usr/local/lib/libosxfuse.2.dylib
    pnpm rebuild @zkochan/fuse-native
    ```
  </Tab>

  <Tab title="Linux">
    No workaround needed on Linux, `@zkochan/fuse-native` links against `libfuse3` directly.
  </Tab>
</Tabs>

## Verify

```ts theme={null}
import { Mount, MountMode, RAMResource, Workspace } from '@struktoai/mirage-node'

const ws = new Workspace({
  '/data': new Mount(new RAMResource(), { mode: MountMode.WRITE, fuse: true }),
})
await ws.fuseReady()

await ws.execute('echo hello | tee /data/x.txt')
console.log('mountpoints:', ws.fuseMountpoints)

// Any tool (ls, cat, external subprocess) can now see x.txt under the mountpoint.
await ws.close() // auto-unmounts
```

If `ws.fuseMountpoints` prints a `/tmp/mirage-fuse-XXXXXX` path and no error is thrown, the binding is wired up correctly.

Mounts are async, so `await ws.fuseReady()` once after constructing the workspace; it resolves when every `fuse` mount is live (and rejects if one fails to mount). Until then `ws.fuseMountpoints` may be empty. (In Python, where mounts are synchronous, the constructor blocks until ready 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 theme={null}
mode: WRITE
mounts:
  /data:
    resource: ram
    fuse: /tmp/data-repo   # explicit path
  /s3:
    resource: s3
    fuse: true             # temp directory
```

Programmatically, set `fuse` per mount: `{ '/data': new Mount(dataResource, { fuse: '/tmp/data-repo' }), '/s3': new Mount(s3Resource, { fuse: true }) }`.
`ws.fuseMountpoints` 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 over the FUSE mount those files behave like
Linux `/proc` files: they stat as **0 bytes until first open** and become
fully readable the moment anything opens them. See
[Limitations → Size-unknown API files](/typescript/limitations#3-size-unknown-api-files-stat-as-0-bytes-until-first-open)
for the per-tool table and the mechanics (`direct_io` + `attr_timeout=0`).

<Note>
  FUSE on Node has two runtime constraints worth knowing: `fs-monkey` can't patch ESM `node:fs` imports, and synchronous access to your own mount from the mounting process deadlocks the event loop that serves it. See [TypeScript Limitations](/typescript/limitations) for details and workarounds.
</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. Multiple per-mount FUSE mounts work on Linux; on macOS, enable
  `fuse` on a single mount per workspace (or run extra 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.
