mountx logomountx

Quick Start

From install to a real folder in about a minute.

#Install

npm i mountx

Warning

Pre-1.0 API. Expect breaking changes, and note that a mounted driver is reachable by every program on the machine.

#Step 0 — see one without writing one

The package ships a CLI: a seeded in-memory filesystem, mounted for real, with every request the kernel makes printed as it happens. No root on either platform.

npx mountx                       # mounts ~/mountx
npx mountx /tmp/scratch -t nfs   # somewhere else, over a named transport
npx mountx --help

Then poke at it from another terminalls -l ~/mountx, cat ~/mountx/README.md — and watch the requests scroll past. Full flags in The mountx CLI.

#Step 1 — no mounting at all

The fastest way to see mountx work is without touching the operating system. Wrap a driver in createLoopback() and use it like fs/promises, right inside your program:

import { createLoopback } from "mountx";
import { createMemoryDriver } from "mountx/drivers/memory";

const fs = createLoopback(createMemoryDriver());

await fs.mkdir("/notes");
await fs.writeFile("/notes/hello.txt", "hi");

const entries = await fs.readdir("/notes", { withFileTypes: true });
console.log(entries.map((entry) => entry.name)); // [ "hello.txt" ]

const bytes = await fs.readFile("/notes/hello.txt"); // Uint8Array
console.log(new TextDecoder().decode(bytes)); // "hi"

createLoopback does the same tidying-up a real mount does: it normalizes paths, answers ENOSYS for methods your driver does not have, and resolves what your driver supports. Nothing is mounted, no root is needed, and it works on every platform — including Windows. This is where drivers get developed and tested.

#Step 2 — mount it for real

Same driver, now as a folder anyone on the machine can open:

serve.ts
import { mount } from "mountx/auto";
import { createMemoryDriver } from "mountx/drivers/memory";

await using mounted = await mount(createMemoryDriver(), "/mnt/point");

mounted.transport; // "fuse" | "nfs" — which one this host got

// /mnt/point is a real folder now. Keep the process alive.
await new Promise(() => {});

Run it:

node serve.ts

Then, from another terminal (this matters — see below):

echo hi > /mnt/point/hello.txt
cat /mnt/point/hello.txt
ls -l /mnt/point

Press Ctrl-C in the first terminal and the folder disappears. await using unmounts when the block ends; you can also call await mounted.unmount() yourself.

mount() from mountx/auto picks the transport this host can use — FUSE on Linux, NFSv3 on macOS — so that is the same code everywhere.

#Step 3 — serve a real folder

The second bundled driver hands requests to a real directory, resolving every path component itself so nothing outside the root can be reached:

import { mount } from "mountx/auto";
import { createNodeFsDriver } from "mountx/drivers/node-fs";

await using mounted = await mount(createNodeFsDriver("/home/me/data"), "/mnt/point");

Useful on its own, and the easiest thing to build on: wrap it and you can add caching, logging, filtering or transformation to an ordinary directory.

#Do I need root?

Serving never needs privileges, on either transport. Only attaching the mount to a directory does, and how much depends on the host:

hosttransportroot?
Linux with fusermount3FUSEno
Linux without itFUSEyes
macOSNFSv3no, but you must own the mountpoint
Linux (NFS pinned explicitly)NFSv3yes

On Linux, mounting is a syscall Node cannot make, so mountx asks fusermount3 — the setuid helper that ships with FUSE — to make it and hand the connection back. It is already installed on most desktop Linux; otherwise apt install fuse3 / dnf install fuse3. As root, mountx skips the helper and uses mount(8) directly.

On macOS you get NFSv3, and /sbin/mount_nfs is not setuid — macOS is a BSD, and a BSD lets an ordinary user mount onto a directory that user owns. So no sudo, as long as the mountpoint is yours.

Two things an unprivileged FUSE mount cannot do: allowOther additionally needs user_allow_other in /etc/fuse.conf, and forcing down a mount whose driver has stopped answering is weaker, because there is no umount -f without root.

#Don't use it from the same process

A synchronous fs call against your own mountpoint, from the process serving it, deadlocks. Enough concurrent async ones wedge it too. Put the client in another process — a shell, a test child process, anything. Full explanation in Troubleshooting.

#Where to go next

  • Writing a driver — a complete filesystem in about 60 lines.
  • Mounting — the mount object, lifecycle, and unmounting properly.
  • Tuning — the cache settings that are worth 8–15×.
  • Troubleshooting — stale mounts, wedges, and how to recover.
mountx logo

mountx  Write a filesystem in JavaScript, mount it as a real folder.