Note that Synchrotron is in Alpha Testing currently. Use at your own risk and expect issues.
Local, platform-agnostic file synchronisation.
Synchrotron is for syncing files and folders across different paths on the same machine.
I built Synchrotron to help me replicate common sets of non version controlled utility files in multiple Git projects, but is essentially general purpose local syncing.
Synchrotron runs in the background, syncing files and folders according to a .yaml configuration file in your home directory. The main motivation for the .yaml based configuration is that the sync config can be version controlled. My personal workflow also includes version controlled copy of each of the sync sets, which acts as a backup for accidental deletes being synced across folders, and monitoring of the sync process.
- Sync sets — group directories or individual files that should be kept in sync
- Directory sets — sync full directory trees across multiple locations
- File sets — sync individual files by content, regardless of filename differences
- YAML configuration — human-readable
.synchrotron.ymlconfig file - Conflict resolution — configurable per sync set (
keep-bothorlast-write-wins) - File watching —
fs.watchwith automatic polling fallback for network drives - Background daemon — runs continuously with status checking
- Startup service — install as a system service (systemd, launchd, Task Scheduler)
- Cross-platform — works on Linux, macOS, and Windows
- Robust — handles permission errors, locked files, symlinks, and large files
npm install -g @gbro3n/synchrotron# Initialise config in ~/.synchrotron/.synchrotron.yml
synchrotron init
# Edit ~/.synchrotron/.synchrotron.yml to add your sync sets (see Configuration below)
# Start the sync daemon
synchrotron start
# Check status
synchrotron status
# Stop the daemon
synchrotron stopCreate a .synchrotron.yml configuration file in ~/.synchrotron (config home). Edit it to add your sync sets.
| Flag | Description |
|---|---|
--config <dir> |
Write config to a different directory instead of ~/.synchrotron |
Start the sync daemon in the background. Reads config from ~/.synchrotron/.synchrotron.yml by default. Performs an initial sync of all sets, then watches for changes.
If a daemon is already running, it is automatically stopped before starting the new one. This includes orphaned daemon processes that the PID file doesn't know about (see Single Instance Enforcement below).
| Flag | Description |
|---|---|
--foreground |
Run in the foreground instead of as a background daemon |
--config <dir> |
Read config from a different directory instead of ~/.synchrotron |
Stop the running sync daemon. Sends SIGTERM for graceful shutdown, then SIGKILL after 5 seconds if needed. Also scans for orphaned daemon processes and kills those too.
Show the current status of the daemon, config, and all sync sets including last sync times and file counts per directory.
| Flag | Description |
|---|---|
--config <dir> |
Read config from a different directory instead of ~/.synchrotron |
Install synchrotron as a startup service for the current platform:
- Linux — creates a systemd user service (
~/.config/systemd/user/synchrotron.service) - macOS — creates a launchd agent (
~/Library/LaunchAgents/com.synchrotron.daemon.plist) - Windows — creates a Task Scheduler entry that runs at logon
Remove the startup service for the current platform.
Configuration is stored in .synchrotron.yml:
pollInterval: 5000
conflictResolution: keep-both
syncSets:
# Directory set — syncs a full directory tree across multiple locations
- name: photos
type: directory
paths:
- /home/user/photos
- /mnt/backup/photos
ignore:
- "*.tmp"
- ".DS_Store"
# Directory set with per-set overrides
- name: documents
type: directory
paths:
- /home/user/documents
- /mnt/backup/documents
pollInterval: 10000
conflictResolution: last-write-wins
watchMode: poll
# File set — syncs individual files by content (position-based, not name-based)
- name: hosts
type: file
paths:
- /etc/hosts
- /mnt/backup/hosts| Option | Level | Applies To | Default | Description |
|---|---|---|---|---|
name |
Per-set | All sets | (optional) | Label for log readability and status display |
type |
Per-set | All sets | (required) | directory or file |
pollInterval |
Global / Per-set | All sets | 5000 |
Milliseconds between sync cycles |
conflictResolution |
Global / Per-set | All sets | keep-both |
keep-both or last-write-wins |
watchMode |
Per-set | Directory sets | auto |
auto, watch, or poll |
ignore |
Per-set | Directory sets | [] |
Glob patterns to ignore |
maxLogSizeMB |
Global | — | 10 |
Max log file size in MB before rotation |
maxLogFiles |
Global | — | 5 |
Max number of rotated log files to keep |
keep-both(default) — renames the destination file with a.conflict-<timestamp>suffix and copies the source file in. Both versions are preserved.last-write-wins— the file with the most recent modification time overwrites the other.
auto(default) — usesfs.watchfor real-time detection; falls back to polling iffs.watcherrors (e.g. on network drives).watch— usesfs.watchonly. Will error if the filesystem doesn't support it.poll— polling only. Works everywhere but uses more CPU. Use this for network drives.
The daemon writes to ~/.synchrotron/logs/synchrotron.log with size-based rotation:
- When the log reaches
maxLogSizeMB(default 10 MB), it is rotated tosynchrotron.1.log,synchrotron.2.log, etc. - At most
maxLogFiles(default 5) rotated files are kept; older ones are deleted.
Both values can be set in .synchrotron.yml:
maxLogSizeMB: 20
maxLogFiles: 3Log lines include per-file action detail:
[2026-02-24T14:30:00.000Z] [INFO] Syncing "photos" (directory)...
[2026-02-24T14:30:00.050Z] [INFO] + /home/user/photos/new.jpg → /mnt/backup/photos/new.jpg (added)
[2026-02-24T14:30:00.120Z] [INFO] ~ /home/user/photos/edit.jpg → /mnt/backup/photos/edit.jpg (modified)
[2026-02-24T14:30:00.200Z] [INFO] - /mnt/backup/photos/old.jpg (deleted)
[2026-02-24T14:30:00.250Z] [INFO] Sync "photos" complete: +1 ~1 -1 conflicts:0 errors:0
- Each directory sync set defines 2+ directories to keep in sync.
- A
.syncmetadata file in each directory tracks the file manifest (path, size, mtime, SHA-256 hash) at the last sync. - On each sync cycle, the engine builds a current manifest, diffs it against the previous one, and propagates additions, modifications, and deletions across all directories in the set.
- Empty directories (with no metadata file) are treated as fresh peers — they receive all files and no deletions are propagated to/from them.
- Symlinks are skipped. Files with permission errors or locks are skipped with warnings.
- Files larger than 10 MB are copied using Node.js streams for memory efficiency.
- Each file sync set defines 2+ individual files to keep in sync (e.g.
/etc/hostsand a backup copy). - Sync is positional —
paths[0]is treated as the reference; if it changes, the change propagates to all other paths. - A
<filename>.syncsidecar file next to each path records the hash, mtime, and size at last sync. - Peers with no sidecar are treated as fresh peers — they receive the content with no deletions involved.
- File sets support the same conflict resolution strategies as directory sets.
All runtime files live under ~/.synchrotron/:
~/.synchrotron/
├── .synchrotron.yml # Configuration file
├── daemon.pid # PID of the running daemon process
└── logs/
├── synchrotron.log # Current log file
├── synchrotron.1.log # Most recent rotated log
├── synchrotron.2.log # Older rotated logs...
└── ...
Each synced directory contains a hidden .sync file (JSON, pretty-printed) that records the state of the directory at the last completed sync. The engine uses this to compute diffs between sync cycles.
{
"lastSyncTime": 1740412200000,
"manifest": {
"notes.md": {
"relativePath": "notes.md",
"size": 2048,
"mtimeMs": 1740412100000,
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"subfolder/data.csv": {
"relativePath": "subfolder/data.csv",
"size": 51200,
"mtimeMs": 1740411000000,
"hash": "a1b2c3d4e5f6..."
}
}
}| Field | Type | Description |
|---|---|---|
lastSyncTime |
number |
Milliseconds since epoch when the sync completed |
manifest |
Record<string, FileEntry> |
Map of relative file paths to their entry |
manifest[*].relativePath |
string |
Path relative to the sync directory root |
manifest[*].size |
number |
File size in bytes |
manifest[*].mtimeMs |
number |
Last modified time (ms since epoch) |
manifest[*].hash |
string |
SHA-256 hex digest of the file contents |
A directory with no .sync file is treated as a fresh peer — it receives all files from other peers and never contributes deletions.
For file-type sync sets, each peer file has a sidecar metadata file at <filepath>.sync (e.g. /etc/hosts.sync). It records the state of that individual file at last sync.
{
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"mtimeMs": 1740412100000,
"size": 2048,
"lastSyncTime": 1740412200000
}| Field | Type | Description |
|---|---|---|
hash |
string |
SHA-256 hex digest of the file contents at last sync |
mtimeMs |
number |
Last modified time (ms since epoch) at last sync |
size |
number |
File size in bytes at last sync |
lastSyncTime |
number |
Milliseconds since epoch when the sync completed |
A file with no sidecar is treated as a fresh peer and receives content from peers that do have metadata.
Located at ~/.synchrotron/daemon.pid. Contains the process ID (as a plain integer string) of the currently running daemon.
- Written by the daemon on startup (
entry.ts) - Read by
synchrotron stopandsynchrotron statusto locate the running process - Removed by the daemon on graceful shutdown (SIGTERM/SIGINT handler)
If the daemon crashes or is killed without cleanup, a stale PID file may remain. synchrotron status checks whether the recorded PID is actually alive. synchrotron start will refuse to start if the PID file exists and the process is still running.
Located at ~/.synchrotron/logs/synchrotron.log. Each line is a timestamped, level-tagged message:
[<ISO-8601 timestamp>] [<LEVEL>] <message>
Levels are INFO, WARN, and ERROR. Example output from a sync cycle:
[2026-02-24T14:30:00.000Z] [INFO] Syncing "photos" (directory)...
[2026-02-24T14:30:00.050Z] [INFO] + /home/user/photos/new.jpg → /mnt/backup/photos/new.jpg (added)
[2026-02-24T14:30:00.120Z] [INFO] ~ /home/user/photos/edit.jpg → /mnt/backup/photos/edit.jpg (modified)
[2026-02-24T14:30:00.200Z] [INFO] - /mnt/backup/photos/old.jpg (deleted)
[2026-02-24T14:30:00.250Z] [INFO] Sync "photos" complete: +1 ~1 -1 conflicts:0 errors:0
Rotation: when the log reaches maxLogSizeMB (default 10 MB), it is rotated:
synchrotron.log→synchrotron.1.logsynchrotron.1.log→synchrotron.2.log- ... up to
maxLogFiles(default 5); older files are deleted.
Synchrotron enforces that only one daemon process runs at a time. Multiple running daemons cause metadata conflicts and cascading file storms (see TROUBLESHOOTING.md for details).
Protection is layered:
synchrotron startkills existing daemons — reads the PID file and kills that process, then performs a platform-specific OS process scan to find and kill any orphaned daemon processes the PID file doesn't know about.- Daemon self-check — on startup, the daemon itself checks the PID file and kills any existing daemon before claiming it. This catches cases where the daemon entry point is invoked directly.
- npm script hooks —
npm run buildandnpm startboth automatically runsynchrotron stopbefore proceeding, preventing the common development mistake of rebuilding without stopping. synchrotron stopscans for orphans — after stopping the PID-file daemon, it scans for any remaining daemon processes.
The process scanner uses:
- Windows:
wmic processto find allnode.exeprocesses with synchrotron's daemon entry in the command line - Linux / macOS:
ps -eo pid,argsto find matching processes
If process scanning fails (e.g. restricted permissions), the PID-file-based approach is used as a fallback.
# Install dependencies
npm install
# Run tests
npm test
# Type-check
npm run build
# Watch mode
npm run test:watchBuild and pack a tarball containing exactly what would be published:
npm run build
npm pack
# Produces gbro3n-synchrotron-<version>.tgzInstall globally from the tarball and smoke-test:
npm install -g gbro3n-synchrotron-<version>.tgz
synchrotron --help
synchrotron initUninstall when done:
npm uninstall -g @gbro3n/synchrotronUSE AT YOUR OWN RISK.
Synchrotron is a file synchronisation tool. By its nature, it reads, writes, overwrites, and deletes files across the directories you configure. Incorrect configuration, unexpected filesystem behaviour, bugs, or hardware failures could result in permanent data loss or corruption.
The authors and contributors of Synchrotron provide this software "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability — including loss of data — arising from the use of or inability to use this software.
Always maintain independent backups of any data you intend to sync. Do not rely on Synchrotron as your sole means of data protection.
MIT
