A command-line file sync client for izerop. Syncs files between your local machine and your izerop server with real-time file watching, conflict detection, and background daemon support.
git clone https://github.com/patricksimpson/izerop-cli.git
cd izerop-cli
make installDownload the latest binary for your platform:
| Platform | CLI | Desktop |
|---|---|---|
| Linux x64 | izerop-linux-amd64 | izerop-desktop-linux-amd64 |
| Linux ARM64 | izerop-linux-arm64 | — |
| macOS Intel | izerop-darwin-amd64 | izerop-desktop-macos-amd64.zip |
| macOS Apple Silicon | izerop-darwin-arm64 | izerop-desktop-macos-arm64.zip |
| Windows x64 | izerop-windows-amd64.exe | izerop-desktop-windows-amd64.exe |
Then for the CLI:
chmod +x izerop-linux-amd64
mv izerop-linux-amd64 ~/.local/bin/izeropizerop update# 1. Log in
izerop login
# 2. Check connection
izerop status
# 3. List your files
izerop ls
# 4. Start syncing a folder
izerop watch ~/izeropAuthenticate with an izerop server. Prompts for server URL and API token.
izerop loginConfig is saved to ~/.config/izerop/profiles/<name>/config.json.
Show connection info, file/directory counts, and storage usage.
izerop statusList remote directories and files with names, sizes, timestamps, and IDs.
# List everything
izerop ls
# List files in a specific directory
izerop ls <directory-id>Run a one-shot bidirectional sync between a local directory and the server.
# Sync current directory
izerop sync
# Sync a specific directory
izerop sync ~/izerop
# Pull only (no uploads)
izerop sync --pull-only
# Push only (no downloads)
izerop sync --push-only
# Verbose output
izerop sync -vWatch a directory and sync continuously. Combines fsnotify for instant local change detection with periodic server polling for remote changes.
# Watch current directory (foreground)
izerop watch
# Watch a specific directory
izerop watch ~/izerop
# Custom poll interval (default: 30s)
izerop watch --interval 10
# Verbose — log every poll tick
izerop watch -vRun the watcher in the background:
# Start as daemon
izerop watch ~/izerop --daemon
# Stop the daemon
izerop watch --stop
# Stop all profile watchers
izerop watch --stop --all
# Custom log file location
izerop watch ~/izerop --daemon --log /path/to/watch.logDefault log location: ~/.config/izerop/profiles/<name>/watch.log
View the watch daemon's log output.
# Last 50 lines (default)
izerop logs
# Last 100 lines
izerop logs --tail 100
# Follow live (like tail -f)
izerop logs --followUpload a file to the server.
# Upload to a directory
izerop push photo.jpg --dir <directory-id>
# Upload with a custom name
izerop push IMG_001.jpg --dir <directory-id> --name vacation.jpg
# Upload as public
izerop push report.html --publicSet a file's public/private visibility.
# Make a file public (accepts filename or file ID)
izerop visibility report.html --public
# Make a file private
izerop visibility abc12345 --privateDownload a file by ID.
# Download (auto-names from server)
izerop pull <file-id>
# Download to a specific path
izerop pull <file-id> --out photo.jpgCreate a remote directory.
# Create a top-level directory
izerop mkdir photos
# Create a subdirectory
izerop mkdir thumbnails --parent <directory-id>Delete a file or directory (soft-delete on server).
# Delete a file
izerop rm <file-id>
# Delete a directory
izerop rm <directory-id> --dirMove or rename a file.
# Rename a file
izerop mv <file-id> --name new-name.txt
# Move to a different directory
izerop mv <file-id> --dir <directory-id>
# Both at once
izerop mv <file-id> --name new-name.txt --dir <directory-id>Self-update to the latest GitHub release. Downloads the correct binary for your OS and architecture, then replaces the current executable.
izerop updateizerop versionProfiles let you manage multiple izerop accounts or servers. Each profile has its own server URL, API token, sync directory, state file, and watcher process.
# List all profiles (active marked with ★)
izerop profile list
# Create a new profile
izerop profile add ranger
# Authenticate a profile
izerop --profile ranger login
# Set the active (default) profile
izerop profile use ranger
# Delete a profile
izerop profile remove rangerThe active profile is used when no --profile flag is given:
# Set ranger as default
izerop profile use ranger
# These all use ranger now
izerop sync
izerop ls
izerop watch --daemon
# Explicitly use a different profile
izerop --profile default syncEach profile runs its own independent watcher. You can run them simultaneously:
# Start watchers for two profiles
izerop --profile default watch --daemon
izerop --profile ranger watch --daemon
# Check status — shows both watchers
izerop status
# Stop one
izerop --profile ranger watch --stop
# Stop all
izerop watch --stop --allEach watcher has its own PID file, log file, and sync state stored under ~/.config/izerop/profiles/<name>/.
Each profile's config is stored at ~/.config/izerop/profiles/<name>/config.json:
{
"server_url": "https://izerop.com",
"token": "your-jwt-token",
"sync_dir": "~/izerop"
}| Variable | Description |
|---|---|
IZEROP_SERVER_URL |
Override server URL |
IZEROP_TOKEN |
Override API token |
IZEROP_SYNC_DIR |
Override default sync directory |
# Via flag (works with any command)
izerop --server http://localhost:3000 ls
# Via env
export IZEROP_SERVER_URL=http://localhost:3000
izerop lsPrecedence: --server flag → env vars → config file → https://izerop.com
The sync engine uses a three-tree model inspired by Dropbox's Nucleus:
- Local tree — what's on disk (computed via scan or fsnotify)
- Remote tree — what the server has (fetched via manifest/changes API)
- Synced tree — the last agreed-upon state between local and remote
Each sync cycle compares all three trees:
| Local vs Synced | Remote vs Synced | Action |
|---|---|---|
| Same | Same | Skip |
| Changed | Same | Push |
| Same | Changed | Pull |
| Changed | Changed (same content) | Skip (converged) |
| Changed | Changed (different) | Conflict |
This eliminates false conflicts that plague two-way sync.
Files are only synced when at rest. The stability tracker monitors fsnotify events and waits for a cooldown period (default 5s) with no changes before a file is eligible for sync. This prevents syncing half-written files.
When both local and remote versions of a file change between syncs, the conflict is queued (not written to disk as .conflict files):
# View conflicts
izerop conflicts
# Resolve a specific file — keep your version
izerop conflicts --resolve path/to/file.txt --keep local
# Resolve a specific file — keep server version
izerop conflicts --resolve path/to/file.txt --keep remote
# Resolve all — keep local versions
izerop conflicts --resolve-all --keep localFile updates use If-Match / ETag headers. If another client updates a file between your last sync and push, the server returns 409 Conflict instead of silently overwriting.
Sync state is stored at ~/.config/izerop/profiles/<name>/sync-state-v2.json. This tracks:
- Server cursor (for incremental pull)
- Per-file local and remote content hashes at last sync
- Remote file IDs
Don't delete this file unless you want a full re-sync.
Note: The v2 engine auto-migrates v1 state on first run. Use
--legacyto fall back to the v1 engine if needed.
- All files in the sync directory (recursively)
- Directories are mirrored on the server under a
rootdirectory - Hidden files/dirs (starting with
.) are skipped - Temp files (
.swp,~suffix) are skipped
Point at a local Rails server:
export IZEROP_SERVER_URL=http://localhost:3000
export IZEROP_TOKEN=your-local-jwt
izerop statusOr use the --server flag:
izerop --server http://localhost:3000 lscmd/izerop/ CLI entrypoint
pkg/api/ API client (reusable library)
pkg/sync/ Sync engine v1 (legacy)
pkg/sync2/ Sync engine v2 (three-tree model, active)
pkg/config/ Configuration management
pkg/watcher/ fsnotify + polling watcher (v1)
pkg/updater/ Self-update from GitHub releases
internal/auth/ Authentication flow
The pkg/ packages are designed as reusable libraries — a GUI wrapper (e.g., Wails) can import pkg/api, pkg/sync, and pkg/watcher directly without depending on CLI code.
# Run all tests
make test
# Run with verbose output
go test ./... -v -count=1
# Run only sync engine tests
go test ./pkg/sync2/ -v
# Run a specific test
go test ./pkg/sync2/ -run TestSync_BothChanged_DifferentContent_Conflict -vTests use httptest mock servers — no external dependencies needed.
make build # Build for current platform → bin/izerop
make install # Build and install to ~/.local/bin
make release # Cross-compile for all platforms → dist/
make test # Run tests
make clean # Remove build artifacts| OS | Architecture |
|---|---|
| Linux | amd64, arm64 |
| macOS | amd64 (Intel), arm64 (Apple Silicon) |
| Windows | amd64 |
Releases are automated via GitHub Actions — push a v* tag to create a release with pre-built binaries.
A native desktop app is available for all platforms. Download the appropriate file from Releases.
- Download
izerop-desktop-macos-arm64.zip(Apple Silicon) orizerop-desktop-macos-amd64.zip(Intel) - Unzip and drag
izerop.appto your Applications folder - First launch: macOS will block the app because it's unsigned. To bypass:
- Right-click (or Control-click)
izerop.app→ Open - Click Open in the dialog that appears
- Alternatively: System Settings → Privacy & Security → scroll down and click Open Anyway
- You only need to do this once
- Right-click (or Control-click)
- Download
izerop-desktop-windows-amd64.exe - First launch: Windows SmartScreen may block the app:
- Click More info
- Click Run anyway
- Optionally move the
.exeto a permanent location likeC:\Program Files\izerop\
- Download
izerop-desktop-linux-amd64 - Make it executable and run:
chmod +x izerop-desktop-linux-amd64 ./izerop-desktop-linux-amd64
- Dependencies: The desktop app requires GTK3 and WebKit2GTK:
- Ubuntu/Debian:
sudo apt install libgtk-3-0 libwebkit2gtk-4.0-37 - Fedora:
sudo dnf install gtk3 webkit2gtk4.0 - Arch:
sudo pacman -S gtk3 webkit2gtk(provides webkit2gtk-4.0 compat)
- Ubuntu/Debian:
# Requires Go 1.25+ and Wails CLI
go install github.com/wailsapp/wails/v2/cmd/wails@latest
# Build
make desktop
# Or build + run in dev mode
make desktop-dev- Authentication and config management
- File upload/download (
push,pull) - Directory listing and creation (
ls,mkdir) - Delete and move/rename (
rm,mv) - File visibility control (
visibility,push --public) - Bidirectional sync with cursor-based changes
- Conflict detection
- File watching with fsnotify
- Background daemon mode with logging
- Self-updater from GitHub releases
- Cross-platform release builds
- Selective sync (.izeropignore patterns)
- Desktop GUI app (Wails)
- Cross-platform desktop builds (macOS, Windows, Linux)
- System tray integration
MIT