@peerbit/shared-fs-cli
v0.13.16
Published
Experimental native mount CLI for `@peerbit/shared-fs`.
Readme
Peerbit Shared FS CLI
Experimental native mount CLI for @peerbit/shared-fs.
Mounted unlink and rename operations use the library's exact node-guarded namespace capability when available. This prevents an in-flight mount request from deleting a replacement node, preserves replaced/unlinked open descriptor buffers without recreating their names, and binds open descendants across a directory rename. It remains a visible-replica CRDT fence rather than a linearizable cross-peer transaction; concurrent unseen naming events can still surface as normal shared-filesystem conflicts.
peerbit-fs create
peerbit-fs create --no-auth
peerbit-fs whoami
peerbit-fs trust <address> <public-key>
peerbit-fs install-adapter
peerbit-fs trust-legacy-replica <address> --assume-local-replica-complete
peerbit-fs mount <address> <mountpoint>
peerbit-fs mount <address> <mountpoint> --native-adapter peerbit-shared-fs-native
peerbit-fs status [address]
peerbit-fs conflicts <address>
peerbit-fs naming-conflicts <address>
peerbit-fs resolve-conflict <address> <path> <version-id>
peerbit-fs resolve-naming-conflict <address> <node-id> <keep|restore|delete|move|merge-directory>
peerbit-fs benchmark [address]
peerbit-fs unmount <mountpoint>
peerbit-fs prepare-disposal <address>benchmark writes and reads one large file plus a configurable many-small-files
workload. It is a baseline for tracking regressions, not a claim that v0 is
optimized for code workspaces. It generates a fresh byte corpus by default and
prints its seed; use --seed <seed> to reproduce those exact bytes. This avoids
silently measuring content-addressed deduplication on repeated runs. JSON output
also includes the seed. Establish a fresh baseline when adopting these I/O-only
timings: older results included corpus generation or verification work and are
not directly comparable.
status prints the current native mount adapter, whether its prerequisites are
available on the host, and any missing pieces before optionally opening an
address. Address status also reports write readiness, its durable source, and
whether the local directory is eligible for one-time legacy promotion. Add
--json for one JSON document containing nativeMount and either a
filesystem object or null. nativeMount.metadata reports the synthetic
fixed file/directory modes, non-persisted creation mode, synthetic ownership,
existence-only OS access checks, logical timestamps, and unsupported
chmod/chown/utimens mutations. Routine status does not scan retained conflict
metadata; add --include-conflicts to include the current local content and
naming records plus separate contentCount and namingCount fields. Those
whole-store scans are deliberately opt-in because they can dominate status
latency and memory on a large/history-heavy workspace. During bootstrap the
diagnostic records may be partial; check their partial flag and the adjacent
bootstrap phase before treating them as an operator fence. partial: false
requires a stable, write-ready full replica whose accepted snapshot coverage
was verified for the entire scan. Observer, off/plain-join, fetching,
overlay-active, unverified, and changing bootstrap views remain partial. The
records also report their local-replica scope and before/after phases; even a
verified local scan is not a global network frontier.
create creates an access-controlled filesystem rooted at the local Peerbit
identity. Use create --no-auth only for explicitly unauthenticated test/demo
filesystems. Another machine can run peerbit-fs whoami to print its writer
key; the owner can then run peerbit-fs trust <address> <public-key> to
authorize that writer.
Inspect and resolve conflicts
Content conflicts preserve every concurrently written head. Inspect all of them, or one file/path prefix, before explicitly choosing the version whose bytes should become visible:
peerbit-fs conflicts "$ADDRESS" --json
peerbit-fs conflicts "$ADDRESS" --path /docs/report.md
peerbit-fs resolve-conflict \
"$ADDRESS" /docs/report.md <version-id> --jsonThe JSON document includes address, path filter, view metadata, and a
conflicts array. Each record contains the file's nodeId,
visibleVersionId, and every head in the converged local view: immutable
version id, content hash, parent ids, author, machine, size, and creation time.
Size and time are decimal strings so the output is safe to parse in JavaScript.
snapshotCoverageVerified means only that every id in the accepted signed
snapshot was covered before overlay retirement; it is not a global/log-frontier
claim and remains false for bootstrap-off/plain-join views. Inspection still
permits --no-replicate for an existing local store, but its
fullReplica: false view does not prove completeness. Resolution writes a new
version that references the selected bytes and causally supersedes the heads
visible when it executes; the other immutable versions remain readable until
normal retention/GC policy eventually permits reclaiming them.
Namespace conflicts are inspected and acted on separately:
peerbit-fs naming-conflicts "$ADDRESS" --json
peerbit-fs resolve-naming-conflict \
"$ADDRESS" <node-id> keep
peerbit-fs resolve-naming-conflict \
"$ADDRESS" <node-id> move --to /recovered/report.md
peerbit-fs resolve-naming-conflict \
"$ADDRESS" <shadowed-directory-node-id> merge-directory --jsonThe optional naming --path is an output filter; naming conflict discovery
still rebuilds the local namespace state before filtering.
Use the conflict type and listed ids deliberately:
multi-head:keepnormally asserts the deterministic visible placement.duplicate-name: move or delete one of the listedshadowedNodeIds(keeping the visible winner alone normally leaves the collision unresolved). When both claimants are directories,merge-directorymoves every observed live direct child of one shadowed claimant into the visible directory without changing node ids; nested subtrees follow their directory node, while child name collisions remain explicit duplicate-name conflicts. Resolve any source, target, or direct-child node with multiple naming heads first; the merge fails before publishing a partial repair.delete-vs-edit:restorerecovers surviving edited content;deleteacknowledges the currently visible content heads.unreachable: move the node below a reachable directory, or delete it.
move can itself target an occupied slot and create another duplicate-name
conflict, so inspect again after every action. The resolution JSON returns the
conflicts observed before the action and any still involving that node
afterwards. A successful directory merge additionally reports its source and
target node ids, moved direct-child ids, and naming-event ids.
Resolution commands reject --no-replicate, wait for write readiness, require
the local identity to be trusted on authenticated filesystems, and reject ids
that are not part of the currently visible conflict. Naming actions pass the
complete listed conflict records into the library. The library rechecks their
exact local topology—including other duplicate-name claimants and recoverable
delete-vs-edit versions—and fails retryably if it changed before execution.
Delete/restore also publish only against content heads snapshotted before that
final check, so later content stays recoverable or concurrent. This is a local
observed-view fence, not a global transaction: an event arriving after that read
can reintroduce a conflict later. Directory merge is likewise bounded and
observed-view-only: its child moves and source tombstone are submitted in one
local batch with the tombstone last, but replicas may ingest those independent
events in another order; there is no cross-node causal edge or remote
atomicity. Reinspect before retrying after a reported publication error. A
child first seen later beneath the deleted source is surfaced as an
unreachable conflict and can be moved into the merged tree. The event format
is unchanged, so older replicas converge during a rolling upgrade even though
only upgraded libraries/CLIs expose this action.
Content resolution reports both the heads
observed during CLI preflight and the heads actually superseded so automation
can detect its corresponding race. Neither command waits for persisted remote
acknowledgements; run prepare-disposal separately before retiring the
resolving machine.
Install
Install the CLI, then make sure the native adapter is installed:
npm install -g --omit=peer @peerbit/shared-fs-cli
peerbit-fs install-adapter
peerbit-fs status--omit=peer keeps npm from auto-installing optional browser and React Native
peer packages that are not needed by the Node.js CLI.
peerbit-fs install-adapter downloads a prebuilt
peerbit-shared-fs-native binary into ~/.peerbit/shared-fs/bin. The global
package install also tries this automatically, but the explicit command is safe
to rerun and is the easiest way to repair a missing adapter. mount and
status auto-detect that managed adapter, a peerbit-shared-fs-native command
on PATH, or PEERBIT_SHARED_FS_NATIVE_ADAPTER.
Native runtime prerequisites are platform-specific:
- Linux: FUSE/libfuse. On Debian or Ubuntu, install
fuse3andlibfuse3-dev. - macOS: macFUSE. With Homebrew, run
brew install --cask macfuse, then approve macFUSE in System Settings and reboot if macOS requires it. - Windows: WinFsp runtime must be installed before mounting.
Native metadata is intentionally limited while the shared model persists only
names and file content. Linux/macOS stat reports synthetic 0755 directories
and 0644 files; Windows normalizes them to 0777 and 0666. Creation modes
are not persisted, ownership is adapter-synthetic, and atime mirrors the
logical/synthetic mtime. chmod, chown, and explicit timestamp updates are
unsupported and fail instead of claiming success. These fields are not an
authorization boundary. The external adapter checks only path existence in its
OS access callback, so access(2) and test -w are advisory. Use the Shared FS
trusted-writer model for write authorization.
Create and mount an authenticated shared filesystem:
peerbit-fs status
ADDRESS=$(peerbit-fs create)
mkdir -p "$HOME/PeerbitShared"
peerbit-fs mount "$ADDRESS" "$HOME/PeerbitShared"On Windows PowerShell:
peerbit-fs status
$address = peerbit-fs create
New-Item -ItemType Directory -Force "$env:USERPROFILE\PeerbitShared"
peerbit-fs mount $address "$env:USERPROFILE\PeerbitShared"Authentication is on by default. Use peerbit-fs create --no-auth only for
explicitly unauthenticated tests or demos.
Share With Another Machine
On the joining machine, print the local Peerbit writer key:
peerbit-fs whoamiOn a machine that already owns or can write the filesystem, authorize that key:
peerbit-fs trust "$ADDRESS" <public-key>The joining machine can then mount the same address:
mkdir -p "$HOME/PeerbitShared"
peerbit-fs mount "$ADDRESS" "$HOME/PeerbitShared"mount opens a full replica and waits until the initial namespace has settled
before exposing a writable filesystem. During that window library mutations and
writable backend opens return retryable EAGAIN; reads remain available.
mount --no-replicate is rejected because an observer cannot establish the
complete namespace required for safe mounted writes. For now readiness is a
settled-view heuristic rather than a protocol log-frontier proof, so deployments
requiring a strict frontier should keep writers stopped until Peerbit exposes
that upstream barrier.
Mounted flush publishes one frozen buffer generation. fsync and close drain
every mutation accepted before their fence, while mutations racing a closing
handle fail instead of disappearing. These calls do not wait for a remote
persisted quorum; use the quiesced prepare-disposal workflow for machine
retirement. CI verifies the default disk-backed store after forced process
termination, not arbitrary custom targets or a host/controller power failure.
For access-controlled filesystems, write readiness is not a global revocation proof. The trusted-writer graph converges separately and entries do not carry an authorization epoch, so quiesce and isolate a revoked machine/key, wait for every serving replica to report it untrusted, and keep an already-converged durable replica online. A signed trust frontier and entry-bound authorization epoch are still needed upstream for protocol-grade revocation.
create requires a full replica and publishes a signed empty snapshot, so a
newly created empty filesystem can be mounted locally and later prove its empty
starting view to a connected joiner. create --no-replicate is rejected.
mount waits up to 120 seconds by default; tune this with
--write-ready-timeout-ms. A timeout is not permission to write: keep a
complete replicator for this filesystem connected and retry. An unrelated
connected Peerbit peer does not count. A fresh no-snapshot join can count
namespace rows materialized during the initial store open only when they are
paired with the lower log's successful network-commit phase. Local replay has
no such phase, so a populated store with a missing sidecar cannot certify
itself; when it is already identical to its donor, one later donor mutation or
a verified snapshot is still required.
Pre-marker stores have no persisted readiness proof. Connection alone is
insufficient when local and remote states are already identical, because no new
metadata event may arrive. The normal path is to open the legacy handle, keep a
complete replicator connected, and make one normal namespace mutation on that
replicator. If peerbit-fs status "$ADDRESS" reports
legacy promotion eligible: yes, and only after independently verifying that
this exact directory was a cleanly shut down, complete full replica that was
never copied mid-bootstrap, run:
peerbit-fs trust-legacy-replica "$ADDRESS" \
--assume-local-replica-completeThis is an operator assertion, not a network proof. It persists a marker for
this directory/address before enabling writes and is safe to repeat after
success; never copy the marker to another machine. The --allow-partial-writes
mount escape hatch is instead a session-only recovery bypass. It can manufacture
duplicate paths or overwrite from stale state, does not persist proof, and keeps
snapshot, GC, ACL, and disposal operations blocked.
Run peerbit-fs status "$ADDRESS" when diagnosing a host. It checks the native
adapter, platform prerequisites, local Peerbit state, and whether the address can
be opened.
Safely dispose of a machine
Stop mounted writes, wait for persisted delivery, and only then dispose of the machine or delete its local Peerbit state:
# In the original mount terminal, send Ctrl-C/SIGTERM and wait for
# `peerbit-fs mount` to finish its clean shutdown.
peerbit-fs prepare-disposal "$ADDRESS" --min-acks 1
# Only after both commands exit successfully, dispose of the machine or its state.peerbit-fs unmount "$MOUNTPOINT" only detaches the OS mountpoint; it does not
stop a separately running peerbit-fs mount process. If manual unmounting is
needed, still terminate that original process and wait for it to exit before
opening the same Peerbit state directory for the barrier.
--min-acks <number> defaults to 1, --timeout-ms <number> bounds the
barrier after the filesystem has opened, and --json emits a machine-readable
result. Network connection, filesystem open, and shutdown are outside that
flag's deadline. A timeout, abort, error, or nonzero exit never indicates safe
disposal. Keep the source machine and its state available, correct connectivity
or storage capacity, and retry the barrier. Run it against the existing full
local replica; --no-replicate is rejected. A pending bootstrap decision is
awaited; a bootstrapping or unverified view is rejected. Any filesystem-content
or trusted-writer-graph arrival during the fence also fails the attempt. A
deferred resurrection-guard decision for removed live metadata fails closed
until it settles, so let replication and guard recovery settle before retrying.
Avoid tight retry loops after a timeout: already-started local index/log reads
cannot be cancelled and finish in the background.
The captured recoverable head closure contains every current naming head (including tombstones and conflicts), every current file-version head, and every distinct chunk those versions reference, plus every surviving trusted-writer-log entry (live grants and revocation tombstones). It does not preserve already superseded filesystem history or control manifests.
The reported guarantee is persisted per entry: every exact entry in that closure independently reached the requested number of capable remote leaders backed by supported durable storage. It does not prove that one common custodian, or the same group of custodians, holds all entries. The command does not raise the replication degree, and older, incapable, in-memory, and local peers do not count.
Receipts describe the instant they were issued and assume cooperative remotes; they are not permanent-custody guarantees or Byzantine proofs. A successful empty result is vacuous: it acknowledges no data and provides no evidence that a remote peer was present. The command does not revoke the local writer key; perform any intended revocation first. If a cold receipt target cannot validate a revocation tombstone because it never admitted the original grant, the barrier fails closed instead of claiming disposal safety.
The portable process-crash campaign kills all three authenticated replica
instances without graceful shutdown after minAcks: 2, deletes the source, and
reopens each custodian alone. That verifies the application-crash path on the
tested disk backend. A literal power-cut claim still requires a VM or hardware
fault campaign with explicit filesystem and controller-cache semantics.
macOS from this repo
For repository development, the macOS installer builds the TypeScript CLI and
the external Go/macFUSE adapter, then installs wrappers into ~/.local/bin:
pnpm shared-fs:install:macos
export PATH="$HOME/.local/bin:$PATH"
peerbit-fs status
ADDRESS=$(peerbit-fs create)
mkdir -p "$HOME/PeerbitShared"
peerbit-fs mount "$ADDRESS" "$HOME/PeerbitShared"macFUSE is required. The installer tries brew install --cask macfuse when
Homebrew is available, but macOS may still require one-time approval in System
Settings > Privacy & Security and a reboot.
The external packages/shared-fs/native adapter uses cgofuse for Linux FUSE,
macFUSE, and WinFsp. The repo includes a manual Shared FS Native Smoke GitHub
workflow for Linux FUSE. Portable CI still runs the backend and cross-OS
shared-store checks on Linux, macOS, and Windows.