Skip to main content

Module live_mounts_guide

Module live_mounts_guide 

Source
Expand description

Guide for live mount/unmount on a running Bash instance.

This guide covers:

  • Attaching/detaching filesystems post-build
  • State preservation across mount operations
  • Hot-swapping mounted filesystems
  • Layered filesystem architecture

Related: Bash::mount, Bash::unmount, MountableFs, BashBuilder::mount_text

§Live Mount/Unmount Guide

Bashkit supports attaching and detaching filesystems on a running interpreter without rebuilding it. Shell state — environment variables, working directory, history, aliases — is fully preserved across mount operations.

§Motivation

Before live mounts, the only way to add a filesystem after build() was to accumulate mount configs and call reset(), which rebuilds the entire interpreter and loses all in-flight state. Live mounts solve this by exposing the internal MountableFs layer that wraps every Bash instance.

Common use cases:

  • Agent workflows: attach a host directory mid-session when a tool needs it
  • Plugin systems: mount/unmount plugin filesystems without restarting
  • Hot-swap deployments: replace a mounted app filesystem with a new version
  • Testing: inject mock data at specific paths during a test

§Quick Start

use bashkit::{Bash, FileSystem, InMemoryFs};
use std::path::Path;
use std::sync::Arc;

let mut bash = Bash::new();

// Create and populate a filesystem
let data_fs = Arc::new(InMemoryFs::new());
data_fs.write_file(Path::new("/users.json"), br#"["alice"]"#).await?;

// Mount it live — no rebuild needed
bash.mount("/mnt/data", data_fs)?;

let result = bash.exec("cat /mnt/data/users.json").await?;
assert!(result.stdout.contains("alice"));

// Unmount when done
bash.unmount("/mnt/data")?;

§API

§Bash::mount(vfs_path, fs)

Mounts fs at vfs_path. The mount takes effect immediately — subsequent exec() calls see files from the mounted filesystem. If a mount already exists at vfs_path, it is replaced (hot-swap).

let bash = Bash::new();
let fs = Arc::new(InMemoryFs::new());
bash.mount("/mnt/data", fs)?;

Errors: Returns Err if vfs_path is not absolute (after normalization).

§Bash::unmount(vfs_path)

Removes the mount at vfs_path. Paths that previously resolved to the mounted filesystem fall back to the root filesystem or the next shorter mount prefix.

bash.unmount("/mnt/data")?;

Errors: Returns Err if nothing is mounted at vfs_path.

§How It Works

Every Bash instance wraps its filesystem stack in a MountableFs as the outermost layer. This layer uses longest-prefix matching to route path operations to the correct mounted filesystem:

┌──────────────────────────────┐
│  MountableFs (live mounts)   │  ← Bash::mount() / unmount()
├──────────────────────────────┤
│  OverlayFs (text mounts)     │  ← BashBuilder::mount_text()
├──────────────────────────────┤
│  MountableFs (real mounts)   │  ← BashBuilder::mount_real_*_at()
├──────────────────────────────┤
│  Base filesystem             │  ← InMemoryFs or custom
└──────────────────────────────┘

Because the interpreter holds an Arc<dyn FileSystem> pointing to the outermost MountableFs, any mount/unmount operation is visible to the interpreter immediately — no rebuild or state transfer required.

§Builder Mounts vs Live Mounts

Builder mountsLive mounts
WhenBefore build()After build()
MethodBashBuilder::mount_text(), mount_real_*()Bash::mount()
StateN/A (no interpreter yet)Fully preserved
Use caseInitial configurationDynamic attachment

Both approaches can be combined: configure initial mounts with the builder, then add/remove mounts at runtime.

§Examples

§Multiple Mounts

use bashkit::{Bash, FileSystem, InMemoryFs};
use std::path::Path;
use std::sync::Arc;

let mut bash = Bash::new();

let logs = Arc::new(InMemoryFs::new());
logs.write_file(Path::new("/app.log"), b"started\n").await?;

let config = Arc::new(InMemoryFs::new());
config.write_file(Path::new("/app.toml"), b"port = 8080\n").await?;

bash.mount("/var/log", logs)?;
bash.mount("/etc/app", config)?;

let result = bash.exec("cat /var/log/app.log").await?;
assert_eq!(result.stdout, "started\n");

let result = bash.exec("cat /etc/app/app.toml").await?;
assert_eq!(result.stdout, "port = 8080\n");

§Hot-Swap

Re-mounting at the same path replaces the filesystem atomically:

use bashkit::{Bash, FileSystem, InMemoryFs};
use std::path::Path;
use std::sync::Arc;

let mut bash = Bash::new();

let v1 = Arc::new(InMemoryFs::new());
v1.write_file(Path::new("/version"), b"1.0").await?;
bash.mount("/app", v1)?;

let result = bash.exec("cat /app/version").await?;
assert_eq!(result.stdout, "1.0");

// Hot-swap to v2
let v2 = Arc::new(InMemoryFs::new());
v2.write_file(Path::new("/version"), b"2.0").await?;
bash.mount("/app", v2)?;

let result = bash.exec("cat /app/version").await?;
assert_eq!(result.stdout, "2.0");

§See Also