offsync is a minimal SSH-only file sync CLI for two-node setup and controller-to-target delivery.
Install offsync on the controller and on each target. Run init on the controller to create a dedicated key. Run link on the target, paste the controller key, and the target installs a restricted SSH entry that only allows rsync file transfer into the configured path.
offsync is not a daemon. It does not open ports, watch files, use a cloud service, or define a custom protocol. Each sync run is explicit:
plan -> apply -> verify
- SSH-only transport using
rsyncover SSH - Dedicated controller keypair generated by
offsync init - Target-side
offsync linkthat accepts the controller public key - Restricted target key using SSH forced-command behavior
- No shell access for the installed offsync key
- No hidden deletes
- No peer-to-peer sync between targets
- Deterministic planning output sorted by target and path
- Operation log with timestamp, host, and files changed
- System dependency installer with explicit permission
Controller node:
- Python 3.9+
sshssh-keygenssh-keyscanrsync
Target node:
- Python 3.9+
- SSH server
rsyncrrsync
offsync install-deps can install the common system packages for the controller, target, or both roles using the detected package manager.
Install offsync on both the controller node and every target node.
Recommended for a CLI install:
brew install pipx
pipx install git+https://github.com/offband/offsync.gitIf you prefer a project-local virtual environment:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install git+https://github.com/offband/offsync.gitUse python -m pip, not python3 -m pip, after activating the virtual environment. On Homebrew Python, python3 -m pip install ... can still target the externally managed system interpreter.
Or install from a source checkout.
After cloning the repository, install from the project directory:
cd offsync
python3 -m pip install .For development, use editable mode:
python -m pip install -e .Verify the CLI is available:
offsync --helpInstall system packages on each node:
# controller node
offsync install-deps --role controller
# target node
offsync install-deps --role targetinit and link also check required system commands and ask before attempting package installation. Use --yes only when you want package installation to proceed without an interactive confirmation:
offsync install-deps --role controller --yesOn the controller:
offsync init --source /srv/app-dataCopy the public key printed by init.
On the target:
offsync link --path /srv/app-dataPaste the controller public key when prompted. link prints a controller command similar to:
offsync target add deploy@node02.example.com --path /srv/app-dataRun that command on the controller, then sync:
offsync plan
offsync applytarget add verifies the target host key and performs a restricted rsync dry-run before saving the target.
usage: offsync [-h] {init,link,target,install-deps,plan,apply} ...
SSH-only deterministic file sync
positional arguments:
{init,link,target,install-deps,plan,apply}
init create keypair and config
link install a controller key on this target
target manage controller target config
install-deps install required system packages
plan print deterministic transfer plan
apply apply and verify the transfer plan
options:
-h, --help show this help message and exit
Run on the controller. Creates controller state under ~/.offsync:
id_ed25519id_ed25519.pubconfig.yaml
It prints the public key that must be pasted into offsync link on each target.
usage: offsync init [-h] [--source SOURCE]
Create the controller config and dedicated offsync SSH keypair.
options:
-h, --help show this help message and exit
--source SOURCE source directory to sync (default: current directory)
Examples:
offsync init
offsync init --source /srv/app-dataRunning init again is safe. If --source is provided, it updates the configured source path.
Run on a target. Installs the controller public key into the local user's ~/.ssh/authorized_keys with strict restrictions.
usage: offsync link [-h] [--path PATH] [--rrsync RRSYNC] [--key KEY]
[--key-file KEY_FILE]
Run on a target node to install the controller public key with strict file-
transfer restrictions.
options:
-h, --help show this help message and exit
--path PATH directory this target allows the controller to sync
into (default: ~/.offsync/sync)
--rrsync RRSYNC absolute path to rrsync on this target (default:
/usr/bin/rrsync)
--key KEY controller public key text
--key-file KEY_FILE file containing the controller public key
Examples:
offsync link --path /srv/app-data
offsync link --path /srv/app-data --key-file ./offsync-controller.pub
offsync link --path /srv/app-data --rrsync /usr/lib/rsync/rrsyncRun on the controller after target-side offsync link has installed the key. This verifies host-key trust, verifies restricted rsync access, then stores the target in the controller config so plan and apply know where to sync.
usage: offsync target add [-h] --path PATH [--rrsync RRSYNC] login
Verify host trust and restricted rsync access, then add a linked target to
controller config.
positional arguments:
login remote target in user@host form
options:
-h, --help show this help message and exit
--path PATH absolute target directory reported by offsync link
--rrsync RRSYNC absolute path to rrsync on the target (default:
/usr/bin/rrsync)
Example:
offsync target add deploy@node02.example.com --path /srv/app-dataIf the target host key is not already trusted, target add fetches the key fingerprint and requires an explicit yes before writing it to known_hosts.
Installs required system packages using the detected package manager. It asks before installing unless --yes is provided.
usage: offsync install-deps [-h] [--role {controller,target,both}] [-y]
Install ssh/rsync system dependencies using the detected package manager,
after confirmation.
options:
-h, --help show this help message and exit
--role {controller,target,both}
dependencies to install (default: both)
-y, --yes do not prompt before installing packages
Examples:
offsync install-deps --role controller
offsync install-deps --role target
offsync install-deps --role both --yesSupported package managers:
apt-getdnfyumpacmanzypperapkbrew
Run on the controller. Scans the configured source directory and each target, then prints a deterministic diff.
usage: offsync plan [-h]
Scan the configured source and targets, then print added, modified, and
target-only files.
options:
-h, --help show this help message and exit
Plan categories:
added: file exists locally and not on the targetmodified: file exists on both sides but differsmissing: target-only file that would require an explicit delete policy
plan does not make changes.
Run on the controller. Transfers planned file changes with rsync over SSH, then re-scans the target to verify expected state.
usage: offsync apply [-h]
Transfer planned file changes with rsync over SSH, then verify the target
state.
options:
-h, --help show this help message and exit
apply refuses to continue if the plan contains target-only files. Deletes are never hidden or implicit.
Controller state lives in ~/.offsync by default:
~/.offsync/
config.yaml
id_ed25519
id_ed25519.pub
operations.log
Use OFFSYNC_HOME to place controller state somewhere else:
OFFSYNC_HOME=/etc/offsync offsync init --source /srv/app-dataDefault config values:
delete: false
overwrite: true
verify: "hash"Example controller config:
version: 1
source: "/srv/app-data"
delete: false
overwrite: true
verify: "hash"
targets:
- login: "deploy@node02.example.com"
user: "deploy"
host: "node02.example.com"
remote_path: "/srv/app-data"
rrsync: "/usr/bin/rrsync"offsync link installs an authorized_keys entry that restricts the dedicated controller key:
command="/usr/bin/rrsync -wo /allowed/path",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 ...
SSH forced-command behavior replaces any requested command with rrsync. The installed key is limited to file-transfer operations inside the configured path. It cannot be used for an interactive shell, arbitrary command execution, port forwarding, agent forwarding, X11 forwarding, or PTY allocation.
Controller-side SSH uses the dedicated key with password authentication disabled and strict host-key checking enabled. offsync target add must trust the target host key before plan or apply can connect.
Each apply run appends a JSON line to:
~/.offsync/operations.log
Each log record includes:
- UTC timestamp
- target host
- operation name
- sorted list of files changed
- The configured source must be a local directory.
- Target paths must be explicit and scoped.
- Symlinks that escape the source scope are refused.
- Deletes are disabled by default.
- Target-only files cause
applyto fail instead of deleting them. - The offsync SSH key is used with password authentication disabled.
Run tests:
python3 -m unittest discover -s tests -qBuild release artifacts:
python3 -m pip install build twine
python3 -m build
python3 -m twine check dist/*Run from a checkout without installing:
bin/offsync --help