various tools I have been using throughout the years
README.md

dam (diff-and-merge) #

Syncs line-oriented text files between a local directory and a remote host over rsync, with a plan/apply workflow: --plan (the default) previews the changes, --pull applies them locally, --push applies them to the remote.

Merge strategy: when a file exists on both sides, the result is the de-duplicated union of its local and remote lines: the local lines in their original order, with the lines only the remote has appended. When a file exists on only one side it is propagated verbatim. The merged result is the same in every mode; the mode only chooses which side gets written.

Order is never changed — pushing a reordered file (say, a playlist) propagates the local order rather than fighting it. dam is intended for list-like data (allow-lists, hosts files, playlists, word lists). Blank lines and duplicate lines are not preserved by the merge.

--local and --remote may be directories or single files. When --local is an existing directory, dam syncs the whole tree (recursing into subdirectories); otherwise it treats both paths as a single file. To mirror a directory into a new local location, create that directory first.

dam never deletes. Because the merge is a union, a line removed on one side reappears from the other. Removing data is a manual edit on both sides (or a one-shot rsync), not something dam does.

Local files are written atomically (staged in a temp file and renamed), so an interrupted run can't leave a half-written file. rsync runs with ssh -o BatchMode=yes and an I/O --timeout so it fails fast in automation rather than blocking on a prompt.

Building #

cargo build --release

Usage #

dam --host <host> --local <dir> --remote <dir> [--plan | --pull | --push] [--sudo | --doas] [--exit-code] [--timeout <secs>]

Modes #

--plan, --pull, and --push are mutually exclusive; --plan is the default.

Flag Description
--plan Show the changes each direction would make. Writes nothing. (default)
--pull Apply the merge to the local directory (remote → local)
--push Apply the merge to the remote host (local → remote)

Options #

Flag Description
--host SSH host alias (from ~/.ssh/config) to sync with
--local Local directory containing the text files
--remote Remote directory to sync from/to
--sudo Elevate the remote rsync with sudo (via --rsync-path). Mutually exclusive with --doas
--doas Elevate the remote rsync with doas (via --rsync-path). Mutually exclusive with --sudo
--exit-code Exit 1 if --plan finds pending changes. Useful in CI
--timeout rsync I/O timeout in seconds (default: 30; 0 disables)

Run dam --help for full details.

--sudo/--doas elevate only the rsync process on the remote host (e.g. when the remote path is root-owned) by passing --rsync-path="sudo rsync". The local rsync always runs unprivileged, so your own SSH config and keys are used.

Exit status #

Code Meaning
0 Success; nothing pending (or changes were applied with --pull/--push)
1 --exit-code was set with --plan and pending changes were found
2 One or more files failed to process

Progress and the summary go to stderr; the plan diff goes to stdout, so dam --plan … > changes.diff captures only the differences.

Examples #

# See what would change on either side
dam --host myserver --local ~/lists --remote /srv/lists

# Bring the local directory up to date
dam --host myserver --local ~/lists --remote /srv/lists --pull

# Bring the remote up to date
dam --host myserver --local ~/lists --remote /srv/lists --push

# Sync a single file
dam --host myserver --local ~/hosts.allow --remote /etc/hosts.allow --pull

# Fail if the two sides have drifted
dam --host myserver --local ~/lists --remote /srv/lists --plan --exit-code