Quick start
Two machines are involved: the server, which holds the master,
and each client, which holds a copy. Install App::FileSync on both —
filesync-admin and filesync-server run on the server,
filesync on the clients.
On this page
On the server
Create the repo as the account the clients log in to. A session over ssh
carries only the repo, the client id and the role, so the client never tells
the server which root to use: name the same --root on every
server command.
# on the server, once
filesync-admin --root ~/filesync repo create documents
filesync-admin --root ~/filesync client allow laptop --repo documents
repo create makes the master directory and the metadata beside
it, printing both paths. client allow is optional, because a repo
with no clients admits every client; it takes the client's id, which is the
host name unless --id was given.
The example keeps the master in the server account's home directory, so
nothing has to run as root. /srv/filesync is the built-in default
root; see a root of /srv/filesync instead if you prefer it.
The server-command wrapper
The server also needs --root when filesync-server
starts, and the client cannot pass one, so wrap it:
# on the server, once
cat > ~/bin/filesync-server-home <<'EOF'
#!/bin/sh
exec /usr/local/bin/filesync-server --root "$HOME/filesync" "$@"
EOF
chmod +x ~/bin/filesync-server-home
Use the path filesync-server was actually installed to, which
ssh srv 'command -v filesync-server' shows: a non-interactive ssh
command can see a shorter PATH than an interactive login
shell.
On each client
Register the directory and name the wrapper as the server program:
# on the client, once
filesync init --repo documents --root /home/foo/Documents \
--server foo@srv \
--server-command /home/foo/bin/filesync-server-home
Nothing else needs naming: the client reports its host name, which is the id
client allow lists and the name the server keeps this client's
base, its journal entries and its conflict copies under. Pass --id
only when the host name is not the name you want, and allow that name on the
server instead. An id is not a login — the ssh key and the account do the
authenticating — and it does not divide the repo: every client of a repo
syncs the whole tree.
ssh runs with BatchMode=yes, so the client's key must already
be in the server account's authorized_keys, or loaded in
ssh-agent: cron cannot answer a password prompt.
The first sync
Look before you leap. --dry-run prints the actions the session
would take without changing either side.
filesync sync --repo documents --dry-run
filesync sync --repo documents
Each run reports what moved and how much of it crossed the wire:
documents: pulled 0, pushed 3, deleted 0, conflicts 0
transfer: sent 148.2 KiB, received 0 B, 1 file by delta
From cron
# crontab -e, on each client
* * * * * ionice -c3 /usr/local/bin/filesync sync --repo documents --quiet >/dev/null
The summary line is dropped so cron does not mail on every successful run;
diagnostics still go to standard error, so a failure is mailed. Every attempt,
successful or not, is also appended to
~/.filesync/logs/documents.jsonl.
Adjust /usr/local/bin/filesync to where the script was
installed, because cron's default PATH rarely includes a per-user
Perl bin directory.
Checking what happened
filesync status --repo documents
filesync log --repo documents -n 5
filesync log --repo documents --exit 1
filesync verify --repo documents --deep
status shows when each repo last synced, how big it is and what
the last transfer cost. log filters the run log by time, exit code
or warning. verify compares the local tree with the base the last
successful sync recorded; it works offline.
Exit codes tell cron what happened: 0 ok, 1 fatal, 2 another run holds the lock, 3 completed with warnings, 4 verification found drift.
On Windows
Windows clients use a scheduled task rather than cron. With the script in
C:\filesync and Strawberry Perl installed, the same job is set up
from a command prompt:
REM on the client, once
C:\Strawberry\perl\bin\perl.exe C:\filesync\bin\filesync init ^
--repo documents --root C:\Users\foo\Documents --server foo@srv ^
--server-command /home/foo/bin/filesync-server-home
REM then create the task; it runs every minute as the current user
schtasks /Create /SC MINUTE /MO 1 /TN FileSync /F ^
/TR "C:\Strawberry\perl\bin\perl.exe C:\filesync\bin\filesync sync --repo documents --quiet"
The task runs only while that user is logged on, which suits a Documents
folder; its run log is still written under
%USERPROFILE%\.filesync\logs, so
filesync log --repo documents --warnings works there too.
A root of /srv/filesync instead
To keep the master outside a home directory, create the default root once
for the account the clients log in to. Then no --root is needed
on the server commands, and no --server-command on the client:
# on the server, as an administrator
sudo mkdir -p /srv/filesync
sudo chown -R foo /srv/filesync
filesync-admin repo create documents then works as it is, and
the master is /srv/filesync/master/documents.
Backup and restore
The master directory is an ordinary directory tree, so it can be mirrored to a backup directory from the server account's crontab. Make the backup directory once — rsync creates the last component only — then let cron keep it in step:
# on the server, once
mkdir -p /home/foo/backups/documents
# crontab -e, on the server
* * * * * ionice -c3 rsync -a --delete --exclude '.filesync-tmp-*' \
/home/foo/filesync/master/documents/ /home/foo/backups/documents/
Filesync writes each file to a temporary name and renames it into place, so
the mirror never catches a half-written upload; the exclusion keeps the
temporary files themselves out of the backup. --delete makes the
copy a mirror: a deletion that reaches the master also removes the backed-up
file.
To restore the master from that backup, stop the client jobs (comment out their cron lines, or disable the scheduled task) and the server's backup job, so the backup job cannot copy a half-restored tree back into the backup. Let any running sync finish, then mirror the backup back:
rsync -a --delete --checksum \
/home/foo/backups/documents/ /home/foo/filesync/master/documents/
--checksum compares the contents, so a restored file is not
skipped just because its size and modification time happen to match the newer
copy. This is a rollback rather than a merge: files created after the backup
was taken disappear from the master, and the next sync propagates that to the
clients, so copy anything you need to keep out of the tree first.
The server would rehash the restored files on the next session anyway, but
filesync-admin reindex rebuilds the index immediately, before any
client reconnects; clients then pull the restored versions, merging edits they
have not synced as usual.
Before re-enabling the jobs, check that the copy is exact with a read-only comparison against the backup:
rsync -a --delete --checksum --dry-run --itemize-changes \
/home/foo/backups/documents/ /home/foo/filesync/master/documents/
No output and exit 0 mean the master matches the backup. Any line beginning
>f names a path that still differs, and *deleting
names one the rollback should have removed. The index still describes the
pre-rollback tree, so filesync-admin verify may report drift at
this point; that is expected. Rebuild it and check it:
filesync-admin --root ~/filesync reindex documents
filesync-admin --root ~/filesync verify documents --deep
reindex hashes every file in the master and replaces the index
atomically; it refuses to run while a sync session holds the repo lock, and it
names any paths it could not index. clean and exit 0 from
verify confirm that the master and its index agree. Re-enable the
jobs once that check passes.
To restore just one file or directory, copy that path back instead of mirroring the whole tree, with the jobs paused as above so nothing changes the tree while the copy runs:
rsync -a --checksum /home/foo/backups/documents/notes/plan.txt \
/home/foo/filesync/master/documents/notes/
rsync -a --checksum /home/foo/backups/documents/photos/ \
/home/foo/filesync/master/documents/photos/
The trailing slash on the second source copies the directory's contents;
without it rsync would put the directory itself inside the destination. Add
--delete only when that directory should match the backup
exactly, since it removes files created after the backup was taken. Refresh
the index with reindex as above, or let the next session do it,
and clients pull the restored versions.