Automatic directory synchronization via Git.
Commits, pulls, and pushes your changes on a configurable schedule — no manual git commands needed. Runs as a background service managed by your OS.
Primary use case: Keep your Obsidian vault in sync across multiple devices.
- Quick Start for Obsidian Users
- Installation
- Commands
- How It Works
- Configuration
- Logging
- Development
- Future Ideas
- License
Not a developer? This section is for you. Brand new to Git and the terminal? Follow our step-by-step Wiki tutorial instead. If you're comfortable with the terminal, skip to Installation.
What syncthis does: It runs in the background and automatically commits and syncs your Obsidian vault to a private Git repository (e.g. on GitHub). This keeps your notes in sync across all your devices — without any manual steps.
Prerequisites:
- Git installed — check with
git --versionin your terminal. If missing, download it here. - Node.js 20+ installed — check with
node --version. If missing, download it here. - A private GitHub repository created for your vault (e.g.
github.com/yourname/my-vault). See Creating a repository — make sure to select Private. - SSH access to GitHub configured — follow GitHub's SSH guide if you haven't done this yet.
Setup (one-time, takes ~2 minutes). Open a terminal (macOS: Terminal.app via Spotlight; Linux: Ctrl+Alt+T) and run:
# 1. Install syncthis
npm install -g syncthis
# 2. Go to your vault folder
cd /path/to/your/obsidian-vault
# 3. Initialize — links your vault to your GitHub repo
syncthis init --remote git@github.com:yourname/my-vault.git
# 4. Start syncing in the background (every 5 minutes by default)
syncthis startThat's it. You can close the terminal — syncthis runs as a background service managed by your OS. On your other devices, repeat steps 2–4 using --clone instead of --remote:
# On your second device: clone and start syncing
syncthis init --clone git@github.com:yourname/my-vault.git --path /path/to/vault
syncthis startCheck the status anytime:
syncthis statusStop syncing:
syncthis stopnpm install -g syncthisOr run without installing:
npx syncthis init --remote git@github.com:yourname/vault.gitRequirements: Node.js ≥ 20.0.0, Git installed and accessible in PATH.
Supported platforms: macOS (launchd), Linux (systemd).
Initializes a directory for syncing. Two modes:
Mode A — Initialize an existing directory:
syncthis init --remote git@github.com:user/vault.git
syncthis init --remote git@github.com:user/vault.git --path /home/user/my-vault- Runs
git initif the directory is not already a Git repo. - Adds the remote as
origin. - Creates
.syncthis.jsonwith default configuration. - Creates a
.gitignorewith Obsidian-specific defaults (only if none exists). - Makes an initial commit if there are untracked files.
Mode B — Clone a remote repository:
syncthis init --clone git@github.com:user/vault.git
syncthis init --clone git@github.com:user/vault.git --path ./my-vault- Clones the repository into the target directory.
- Creates
.syncthis.json.
Flags:
| Flag | Type | Description |
|---|---|---|
--remote |
string | Remote URL (Mode A) |
--clone |
string | Repository URL to clone (Mode B) |
--path |
string | Target directory. Default: current directory |
--branch |
string | Branch name. Default: main |
--remote and --clone are mutually exclusive.
Installs (if needed) and starts the background sync service. This is the primary way to run syncthis — the OS handles starting, stopping, and restarting the process for you.
syncthis start
syncthis start --path ~/vault
syncthis start --label my-vault
syncthis start --enable-autostart- Creates an OS service (launchd on macOS, systemd on Linux).
- Starts syncing immediately in the background.
- If a service already exists and is running: does nothing (idempotent).
- The service auto-restarts if it crashes unexpectedly.
Flags:
| Flag | Type | Description |
|---|---|---|
--path |
string | Directory to sync. Default: current directory |
--label |
string | Custom service name. Default: derived from directory path |
--enable-autostart |
boolean | Start automatically on login. Default: false |
--cron |
string | Cron expression. Persisted in the service definition. |
--interval |
number | Interval in seconds. Persisted in the service definition. |
--on-conflict |
string | Conflict strategy: auto-both, auto-newest, stop, ask. Default: auto-both |
--log-level |
string | debug, info, warn, error. Default: info |
--foreground |
boolean | Run in foreground instead of as a service (see below). |
--cron and --interval are mutually exclusive. CLI flags take priority over .syncthis.json.
Use syncthis start --foreground to run the sync loop attached to the terminal. The process stops when the terminal is closed.
syncthis start --foreground
syncthis start --foreground --path /home/user/my-vault
syncthis start --foreground --cron "*/5 * * * *"
syncthis start --foreground --interval 300Use foreground mode when you want to see live output for debugging, or in environments without a service layer (e.g. Docker containers).
Stops the background sync service. The service stays installed and can be restarted with syncthis start.
syncthis stop
syncthis stop --path ~/vaultShows the current sync status of a directory, including config, Git info, and service state.
syncthis status
syncthis status --path /home/user/my-vaultOutput includes:
- Whether
.syncthis.jsonexists and is valid. - Whether a sync process is currently running (with PID).
- Git info: branch, remote URL, number of uncommitted changes, last commit.
- Service status: running/stopped/not installed, label, autostart.
Works even without .syncthis.json (shows "Not initialized").
Lists all registered syncthis services on the system.
syncthis listExample output:
Label Status PID Schedule Autostart Path
vault-notes running 1234 */5 * * * * off /home/user/vault-notes
work-notes stopped - */5 * * * * on /home/user/work/notes
Shows the sync log output.
syncthis logs # Last 50 lines
syncthis logs --follow # Live output (Ctrl+C to stop)
syncthis logs --lines 100 # Last 100 linesStops and completely removes the service from the OS.
syncthis uninstall
syncthis uninstall --path ~/vaultYour files, .syncthis.json, and logs are not deleted — only the OS service registration is removed.
Interactively resolves a paused rebase conflict left by the ask strategy when running in a non-TTY environment (e.g. background service).
syncthis resolve
syncthis resolve --path ~/vault- Shows a word-level diff for each conflicting file.
- Prompts you to choose
local,remote,both, orabortper file. - Continues the rebase after each resolution step.
- Pushes to the remote once all conflicts are resolved.
Flags:
| Flag | Type | Description |
|---|---|---|
--path |
string | Directory to resolve. Default: current directory |
Every sync cycle follows these steps:
Scheduled trigger
│
▼
┌───────────────────────┐ ┌────────────────────────┐
│ Rebase in progress? ├── Yes ──►│ Sync skipped; │
└───────────┬───────────┘ │ run `syncthis resolve` │
Nope └────────────────────────┘
│
▼
┌───────────────────┐
│ git status │
└───┬───────────┬───┘
│ │
Changes No changes
│ │
│ ▼
│ ┌───────────────────┐ ┌──────────────────┐
│ │ git pull --rebase ├─────►│ Sync paused / │
│ └─────────┬─────────┘ Err │ retry next cycle │
│ OK └──────────────────┘
│ │
│ ▼
│ ┌───────────────────┐
│ │ HEAD changed? │
│ └────┬─────────┬────┘
│ Yes Nope
│ │ │
│ ▼ ▼
│ ┌────────┐ ┌───────┐
│ │ Pulled │ │ No-op │
│ └────────┘ └───────┘
▼
┌───────────────┐
│ git add -A │
└───────┬───────┘
│
▼
┌───────────────────┐
│ git commit │
└───────┬───────────┘
│
▼
┌───────────────────┐ ┌──────────────────┐
│ git pull --rebase ├──── Err ─────►│ Sync paused / │
└───────┬───────────┘ │ retry next cycle │
OK └──────────────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐
│ git push ├─ Net error ──►│ Log warning, │
└───────┬──────────┘ │ retry next cycle │
OK └──────────────────┘
│
▼
┌──────────┐
│ Done │
└──────────┘
Conflict handling: When a rebase conflict occurs during git pull --rebase, syncthis handles it according to the onConflict setting (see Conflict Strategies below).
Offline support: If the network is unavailable, the local commit succeeds. The pull and push failures are logged as warnings, and the loop continues. Everything syncs on the next successful cycle.
Single instance: A .syncthis.lock file prevents multiple instances from running against the same directory. Stale locks (left by a crash) are detected automatically by checking the recorded PID.
When using syncthis start, the OS manages the sync process:
- macOS: Registered as a launchd LaunchAgent (
~/Library/LaunchAgents/). The service runssyncthis start --foregroundinternally — launchd handles daemonization. - Linux: Registered as a systemd user unit (
~/.config/systemd/user/). Usessystemctl --userfor management.
The OS auto-restarts the service on unexpected exits (crash, rebase conflict after manual resolution). Graceful stops via syncthis stop or SIGTERM are not restarted.
Linux note: For the service to keep running after logout, user lingering must be enabled:
loginctl enable-linger $USER(may require sudo). syncthis warns you if this isn't configured.
Configure how syncthis handles merge conflicts with onConflict in .syncthis.json or --on-conflict on the command line.
Keeps both versions — no data is lost:
- The original file retains your local version.
- The remote version is saved alongside it as a conflict copy.
Conflict copy filename pattern: <name>.conflict-YYYY-MM-DDTHH-MM-SS.<ext>
Examples:
note.md→note.conflict-2025-03-04T14-30-00.mdarchive.tar.gz→archive.tar.conflict-2025-03-04T14-30-00.gz
Both files are committed and pushed, so the conflict copy appears on all devices. Review and delete conflict copies manually when you're done.
Automatically keeps the version with the newer Git commit timestamp. The older version is discarded.
- If timestamps are equal, falls back to
auto-both(creates a conflict copy). - No user action required.
Stops the sync loop immediately and exits with code 1. Resolve the conflict manually:
cd /path/to/vault
git status # see conflicting files
# edit files, then:
git add -A
git rebase --continue
syncthis startPauses the sync and prompts you interactively to resolve each conflict:
- In foreground / TTY mode: Shows a word-level diff and prompts you inline to choose
local/remote/both/abortper file. - In background service mode (non-TTY): The rebase is left open. Run
syncthis resolvein the same directory to complete resolution interactively.
syncthis init creates a .syncthis.json in the synced directory:
{
"remote": "git@github.com:user/vault.git",
"branch": "main",
"cron": "*/5 * * * *",
"interval": null,
"onConflict": "auto-both"
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
remote |
string | Yes | — | Remote repository URL |
branch |
string | No | "main" |
Branch to sync |
cron |
string | null | No | "*/5 * * * *" |
Cron expression |
interval |
number | null | No | null |
Interval in seconds (≥ 10) |
onConflict |
string | No | "auto-both" |
Conflict strategy: auto-both, auto-newest, stop, ask |
Exactly one of cron or interval must be set. CLI flags always override the config file.
Common cron expressions:
| Expression | Meaning |
|---|---|
*/5 * * * * |
Every 5 minutes (default) |
*/1 * * * * |
Every minute |
0 * * * * |
Every hour |
Or use --interval for a simple seconds-based schedule:
syncthis start --interval 60 # every 60 secondsLogs are written to both stdout and .syncthis/logs/syncthis.log in the synced directory.
Format:
[2025-02-20T14:30:00.000Z] [INFO] Sync started. Schedule: */5 * * * *. Watching: /home/user/vault
[2025-02-20T14:35:00.000Z] [INFO] Sync cycle: 3 files changed, committed, pushed.
[2025-02-20T14:40:00.000Z] [WARN] Push failed: Network unreachable. Will retry next cycle.
[2025-02-20T14:45:00.000Z] [ERROR] Rebase conflict detected. Sync paused. Resolve conflicts manually.
Control log verbosity with --log-level:
syncthis start --foreground --log-level debug # verbose output
syncthis start --foreground --log-level warn # warnings and errors onlyService mode logging: In addition to the app log file, stdout/stderr are captured by the OS service layer. On macOS, these are stored in .syncthis/logs/launchd-stdout.log and .syncthis/logs/launchd-stderr.log. On Linux, they go to the systemd journal and can be viewed with journalctl --user -u syncthis-<label>. Use syncthis logs as a shortcut.
git clone git@github.com:mischah/syncthis.git
cd syncthis
npm install| Command | Description |
|---|---|
npm run dev -w packages/cli -- -- --help |
Run CLI in dev mode |
npm test |
Run all tests |
npm run build |
Build dist/cli.js |
npm run lint |
Lint and check formatting |
npm run lint:fix |
Auto-fix lint and formatting issues |
npm run typecheck -w packages/cli |
Type-check without building |
syncthis/
├── packages/
│ └── cli/
│ ├── src/
│ │ ├── cli.ts # Entry point, command routing
│ │ ├── commands/
│ │ │ ├── init.ts
│ │ │ ├── resolve.ts # Interactive conflict resolution
│ │ │ ├── start.ts # Dual-mode: service (default) + foreground
│ │ │ ├── status.ts
│ │ │ └── daemon.ts # Service management functions
│ │ ├── conflict/
│ │ │ ├── resolver.ts # Conflict detection & strategy dispatch
│ │ │ ├── interactive.ts # Interactive prompts & resolution logic
│ │ │ ├── diff-renderer.ts # Word-level diff rendering
│ │ │ ├── conflict-filename.ts # Conflict copy filename generation
│ │ │ └── notify.ts # Conflict notification hooks
│ │ ├── daemon/
│ │ │ ├── platform.ts # DaemonPlatform interface + factory
│ │ │ ├── launchd.ts # macOS launchd implementation
│ │ │ ├── systemd.ts # Linux systemd implementation
│ │ │ ├── service-name.ts # Service naming + slugify
│ │ │ └── templates.ts # Plist / unit file generation
│ │ ├── config.ts # Config loading & validation
│ │ ├── sync.ts # Git sync cycle
│ │ ├── scheduler.ts # Cron / interval scheduler
│ │ ├── lock.ts # Process lock management
│ │ └── logger.ts # stdout + file logging
│ └── tests/
│ ├── unit/
│ └── integration/
├── biome.json # Linting & formatting
└── tsconfig.base.json
| Component | Technology |
|---|---|
| Runtime | Node.js ≥ 20 |
| Language | TypeScript 5 (ESM) |
| CLI framework | meow |
| Git operations | simple-git |
| Scheduler | croner |
| Bundler | tsdown |
| Tests | Vitest + execa |
| Linting | Biome |
These features are intentionally out of scope for now but may be explored later:
- GUI — A desktop app (
packages/gui) that wraps the CLI as a subprocess (Electron / Tauri / web-based). - File watcher — Trigger a sync immediately on file changes via
fs.watch, instead of waiting for the next scheduled cycle. - Log rotation — Automatically rotate or clean up log files by size or age.
- Multi-directory — A single process that syncs multiple directories at once.
- Desktop notifications — Notification hooks are in place (log-only in v1). A transport layer (node-notifier, native OS APIs) will be added separately.
- Conflict cleanup — A
syncthis cleanupcommand to remove.conflict-*files from the directory (conflict copies are intentionally committed and synced to all devices so you can review them anywhere). - Conflict history — Persistent log of which conflicts occurred, when, and how they were resolved, stored in
.syncthis/conflict-log.json. - Dry-run mode —
syncthis start --dry-runto preview what would happen without making any changes. - Custom commit messages — A template system for auto-commit message formatting.
- Config migration — Automatically update
.syncthis.jsonon schema changes. - Standalone distribution — Ship without requiring Node.js:
- Stage 1: Homebrew formula with Node as a dependency (
brew install syncthis). - Stage 2: Self-contained binaries via
bun build --compileor Node SEA, built by GitHub Actions for macOS (arm64 + x64), Linux (x64), and Windows (x64).
- Stage 1: Homebrew formula with Node as a dependency (
- Windows service support — Service mode currently supports macOS (launchd) and Linux (systemd). Windows support could be added via Windows Service Manager or NSSM (Non-Sucking Service Manager).
- Service updates — When syncthis is updated, existing service definitions may still point to the old binary path. A
syncthis updatecommand or automatic detection insyncthis statuscould handle this. - Batch management —
syncthis start --all/syncthis stop --allto manage all registered services at once. - Health checks — Periodic verification that the service is actually syncing (not just that the process is alive). Could detect stuck processes or persistent errors.
- Automated releases — Conventional Commits +
commit-and-tag-version(orrelease-it) for SemVer tagging, auto-generatedCHANGELOG.md, and a GitHub Actions workflow that publishes to npm on tag push (feat:→ minor,fix:→ patch,feat!:→ major).