Skip to content

offband/offsync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

offsync

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

Features

  • SSH-only transport using rsync over SSH
  • Dedicated controller keypair generated by offsync init
  • Target-side offsync link that 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

Requirements

Controller node:

  • Python 3.9+
  • ssh
  • ssh-keygen
  • ssh-keyscan
  • rsync

Target node:

  • Python 3.9+
  • SSH server
  • rsync
  • rrsync

offsync install-deps can install the common system packages for the controller, target, or both roles using the detected package manager.

Install

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.git

If 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.git

Use 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 --help

Install system packages on each node:

# controller node
offsync install-deps --role controller

# target node
offsync install-deps --role target

init 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 --yes

Quick Start

On the controller:

offsync init --source /srv/app-data

Copy the public key printed by init.

On the target:

offsync link --path /srv/app-data

Paste the controller public key when prompted. link prints a controller command similar to:

offsync target add deploy@node02.example.com --path /srv/app-data

Run that command on the controller, then sync:

offsync plan
offsync apply

target add verifies the target host key and performs a restricted rsync dry-run before saving the target.

Commands

offsync --help

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

offsync init

Run on the controller. Creates controller state under ~/.offsync:

  • id_ed25519
  • id_ed25519.pub
  • config.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-data

Running init again is safe. If --source is provided, it updates the configured source path.

offsync link

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/rrsync

offsync target add

Run 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-data

If 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.

offsync install-deps

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 --yes

Supported package managers:

  • apt-get
  • dnf
  • yum
  • pacman
  • zypper
  • apk
  • brew

offsync plan

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 target
  • modified: file exists on both sides but differs
  • missing: target-only file that would require an explicit delete policy

plan does not make changes.

offsync apply

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.

Configuration

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-data

Default 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"

Security Model

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.

Logging

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

Safety Rules

  • 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 apply to fail instead of deleting them.
  • The offsync SSH key is used with password authentication disabled.

Development

Run tests:

python3 -m unittest discover -s tests -q

Build 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

About

Offsync is a minimal SSH-only file sync CLI using rsync and a dedicated key to push a source directory to targets. No daemons, no cloud, no peer sync. Explicit plan → apply → verify workflow with restricted target-side SSH access and no hidden deletes.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages