jjfs

Version, currently 0.1.02 versions

github.com/jtippett/jjfs

No description declared in shard.yml.

0 stars
0 dependents
License: MIT

Nothing has been indexed for 0.1.0 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  jjfs:
    github: jtippett/jjfs
    version: ~> 0.1.0

Then run:

shards install

shard.yml

No shard.yml has been indexed for 0.1.0. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

jjfs - Eventually Consistent Multi-Mount Filesystem

NFS-based filesystem that mounts multiple directories as live, eventually-consistent views of the same Jujutsu repository.

Features

  • Multi-mount: Mount the same repo in unlimited locations
  • Eventually consistent: Changes propagate automatically (<2s)
  • Zero maintenance: Auto-syncs and auto-starts on login
  • Remote backup: Push and pull to GitHub or GitLab
  • Cross-platform: Runs on macOS and Linux
  • Git-aware: Detects git repos and offers to update .gitignore
  • No external dependencies: Built-in NFS server, no FUSE or kernel extensions required
  • Easy repo management: Import existing git repos or create new jj repos with simple commands

Status

Version: 0.2.0 Status: Production ready (with known NFS unmount caveat on macOS)

jjfs provides a stable foundation for multi-location file synchronization using Jujutsu's workspace feature. The sync engine handles multiple concurrent mounts and resolves conflicts gracefully.

Installation

via Homebrew (macOS/Linux)

brew install jtippett/jjfs/jjfs

This installs both jjfs and jjfsd. Note: On macOS, you'll need to install bindfs separately (see caveats after installation).

Alternatively, tap first then install:

brew tap jtippett/jjfs
brew install jjfs

Build from Source

Requirements

  • Crystal 1.10+
  • Rust toolchain (for building jjfs-nfs)
  • Jujutsu (jj) - Install guide
  • macOS: fswatch (brew install fswatch)
  • Linux: inotify (built-in)

Build Steps

# Clone the repository
git clone https://github.com/jtippett/jjfs.git
cd jjfs

# Install dependencies (if any)
shards install

# Build Rust NFS server
cd jjfs-nfs
cargo build --release
cp target/release/jjfs-nfs ../bin/
cd ..

# Build Crystal binaries
crystal build src/jjfs.cr -o bin/jjfs --release
crystal build src/jjfsd.cr -o bin/jjfsd --release

# Install to system (optional)
sudo cp bin/jjfs /usr/local/bin/
sudo cp bin/jjfsd /usr/local/bin/
sudo cp bin/jjfs-nfs /usr/local/bin/

Setup

# Install system service (starts daemon automatically)
jjfs install

# Or start daemon manually
jjfsd &

# Initialize default repo
jjfs init

Quick Start

# Create a new repo
jjfs new my-notes

# Or import an existing git repo
jjfs import https://github.com/user/notes.git

# View available repos
jjfs repos

# Open the repo in multiple locations (requires sudo password for NFS mount)
jjfs open my-notes ~/Documents/notes
jjfs open my-notes ~/Desktop/quick-notes

# Work in either location - changes sync automatically!
echo "Hello world" > ~/Documents/notes/hello.txt
# Wait ~2 seconds
cat ~/Desktop/quick-notes/hello.txt
# Output: Hello world

# View all mounts with status
jjfs list

# Close a mount when done
jjfs close ~/Desktop/quick-notes

Usage

Commands

Repo Management:

jjfs new <name>                    # Create new jj repo
jjfs import <git-url> [name]       # Import existing git repo
jjfs repos                         # List all available repos
jjfs init [name]                   # Initialize a repo (default: "default")

Mount Management:

jjfs open <repo> [path]            # Open repo at path (default: ./<repo>)
jjfs close <path>                  # Close mount at path
jjfs list                          # List all mounts with status

Daemon:

jjfs status                        # Show daemon status
jjfs start                         # Start daemon
jjfs stop                          # Stop daemon

Other:

jjfs sync [repo]                   # Force sync (default: all repos)
jjfs remote add <url> [--repo=name]
jjfs install                       # Install system service

How It Works

jjfs builds on Jujutsu's workspace feature to synchronize files across mounts. Each mount represents a jj workspace. When you change files, fswatch or inotify detects the changes and commits them. The system then runs jj workspace update-stale to propagate changes across workspaces. Every 5 minutes (configurable), the system pushes and pulls to remote repositories. Jujutsu preserves conflicts by inserting conflict markers into files. When you mount inside a git repo, jjfs detects this and offers to add the mount to .gitignore.

Configuration

Config file: ~/.jjfs/config.json

{
  "repos": {
    "my-notes": {
      "path": "/Users/you/.jjfs/repos/my-notes",
      "remote": "git@github.com:user/my-notes.git",
      "sync_interval": 2,
      "push_interval": 300
    }
  },
  "mounts": [
    {
      "id": "workspace-id",
      "repo": "my-notes",
      "path": "/Users/you/Documents/notes",
      "workspace": "/Users/you/.jjfs/repos/my-notes/workspaces/workspace-id"
    }
  ]
}

Use Cases

Sync Notes Across Devices

Keep your notes synchronized on multiple devices with automatic backup to GitHub:

# On device 1
jjfs init notes
jjfs remote add git@github.com:user/notes.git
jjfs open notes ~/Documents/notes

# On device 2 (after cloning the git repo to ~/.jjfs/repos/notes)
jjfs open notes ~/Documents/notes

Multiple Views of Same Project

Work on the same codebase from different directories:

jjfs init project
jjfs open project ~/work/project-stable
jjfs open project ~/work/project-experimental

# Changes in either location sync immediately

Desktop + Mobile Workflow

Keep a quick-access folder in sync with your main workspace:

jjfs init todos
jjfs open todos ~/Documents/todos
jjfs open todos ~/Desktop/quick-todos

Troubleshooting

Daemon won't start

# Check if already running
jjfs status

# Check lock file
cat ~/.jjfs/daemon.lock

# Remove stale lock if needed
rm ~/.jjfs/daemon.lock

Mount appears empty

# Check if bindfs is installed
which bindfs

# Check mount status
mount | grep bindfs

# Try remounting
jjfs close /path/to/mount
jjfs open repo-name /path/to/mount

Changes fail to sync

# Check daemon is running
jjfs status

# Check daemon logs
tail -f ~/.jjfs/sync.log

# Force manual sync
jjfs sync repo-name

Conflicts

When you edit the same file in multiple mounts simultaneously, jj creates conflict markers:

<<<<<<< Conflict 1 of 1
%%%%%%% Changes from abc123
Content from mount A
+++++++ Contents of def456
Content from mount B
>>>>>>> Conflict 1 of 1 ends

Resolve conflicts by editing the file to keep the desired content, then save.

Architecture

jjfs consists of six main components:

  • CLI (jjfs): Handles user-facing commands
  • Daemon (jjfsd): Runs as a long-running process managing mounts and sync
  • RPC: Uses JSON-RPC over Unix socket for CLI-daemon communication
  • Mount Manager: Creates and destroys jj workspaces and bindfs mounts
  • Sync Coordinator: Watches for changes, commits them, and propagates to other workspaces
  • Remote Syncer: Pushes and pulls to git remotes periodically

Development

# Run tests
crystal spec

# Run integration tests (requires bindfs)
crystal spec spec/integration_spec.cr

# Build in debug mode
crystal build src/jjfs.cr -o bin/jjfs
crystal build src/jjfsd.cr -o bin/jjfsd

# Run daemon in foreground for debugging
./bin/jjfsd

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

Documentation

Credits

Built with:

  • Jujutsu - Version control system
  • Crystal - Programming language
  • bindfs - FUSE filesystem for pass-through mounting