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 mounts | Live mounts | |
|---|---|---|
| When | Before build() | After build() |
| Method | BashBuilder::mount_text(), mount_real_*() | Bash::mount() |
| State | N/A (no interpreter yet) | Fully preserved |
| Use case | Initial configuration | Dynamic 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
MountableFs— the underlying mount infrastructureBashBuilder::mount_text— pre-build text file mountsBashBuilder::fs— custom filesystem injectionBash::fs— direct filesystem access- VFS specification