SSH Sync

unlisted

by Peter Mukhortov

Sync selected vaults with a remote server via SSH using rsync.

6 starsUpdated 19d agoMIT
View on GitHub

Obsidian SSH Sync

CI

Sync your Obsidian vaults with remote servers via SSH.

Features

  • Bidirectional sync — Changes on both local and remote are synchronized
  • Automatic sync on save — Local changes are pushed immediately
  • Periodic polling — Remote changes are detected on a configurable interval
  • Conflict resolution — Timestamp-based resolution with automatic backups
  • File rename detection — Detects renames via content hashing
  • Uses existing SSH config — No key management, relies on ~/.ssh/config
  • Status bar indicator — Shows sync state (idle, syncing, error) in the Obsidian status bar

Prerequisites

  • Obsidian desktop (macOS or Linux)
  • rsync installed (included by default on macOS, available via package manager on Linux)
  • SSH access to remote server configured in ~/.ssh/config

Installation

From release

  1. Download ssh-sync-v*.zip from the latest release
  2. Unzip into your vault's plugins directory:
    <vault>/.obsidian/plugins/
    
    This creates <vault>/.obsidian/plugins/ssh-sync/ with main.js and manifest.json inside.
  3. Enable "SSH Sync" in Obsidian Settings > Community Plugins

Manual install (local build)

  1. Clone this repository and build:
    git clone https://github.com/mukhortov/obsidian-ssh-sync.git ssh-sync
    cd ssh-sync
    npm install && npm run build
    
  2. Copy main.js and manifest.json into your vault's plugins directory:
    <vault>/.obsidian/plugins/ssh-sync/
    
  3. Enable "SSH Sync" in Obsidian Settings > Community Plugins

Configuration

SettingDescriptionDefault
Enable syncTurn sync on/offfalse
SSH hostSSH connection string (user@hostname)""
Remote vault pathAbsolute path on remote server""
Poll intervalSeconds between remote change checks60
Sync on savePush changes immediately on file modifytrue
Conflict policyHow to resolve conflicts: remote-wins, local-wins, newest-winsremote-wins
Exclude patternsGlob patterns to skip.git/**, .DS_Store, plugin manifest

How It Works

  1. Local > Remote: File changes trigger an rsync push over SSH
  2. Remote > Local: Periodic rsync dry-run detects remote changes, then pulls
  3. Conflicts: When both sides changed, newer wins, older is backed up next to the original
  4. Renames: Content hash matching detects file renames and cleans up old paths

Testing

Unit tests

npm test

Runs all unit, scenario, and E2E tests via Vitest. E2E tests are skipped automatically unless SSH connectivity is available. Tests are organized by category:

DirectoryTestsDescription
tests/scenarios/Local changes, remote changes, conflicts, sync triggers, edge cases, lifecycle, setup, multi-deviceEnd-to-end logic with mocked SSH
tests/sync/Coordinator, engine, manifest, conflict resolver, watcher, pollerCore sync component unit tests
tests/ssh/Command building, path escaping, output parsingSSH/rsync command unit tests
tests/utils/File hashingUtility unit tests
tests/e2e/Real rsync over SSHIntegration tests (require SSH)

Watch mode

npm run test:watch

E2E tests

E2E tests perform real rsync operations over SSH. They are skipped by default unless SSH connectivity is available.

# Run against localhost (requires passwordless SSH to localhost)
npm test

# Run against a specific host and remote path
E2E_SSH_HOST=myserver.local E2E_REMOTE_PATH=/tmp/test-vault npm test
VariableDescriptionDefault
E2E_SSH_HOSTSSH host to connect tolocalhost
E2E_REMOTE_PATHBase directory for remote test vaultsauto-created temp dir

Note: Remote file verification uses local filesystem calls, so the remote path must be accessible from the test runner's filesystem (same machine or mounted path).

Build

npm run build

Type-checks with tsc then bundles with esbuild.

Commands

  • SSH Sync: Sync now — Run a full manual sync
  • SSH Sync: Sync current file — Sync the currently opened file
  • SSH Sync: Toggle sync — Enable/disable sync

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.