pushme: Synchronize multiple filesets across machines using rsync

[ bsd3, program, system ] [ Propose Tags ] [ Report a vulnerability ]

pushme is a wrapper around rsync allowing declarative filesets to be transferred between machines. The screenshot above shows pushme in action (where push is a script I use to call pushme with appropriate arguments based on which machine I'm running it from).


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.2.0, 1.0.0, 1.0.1, 1.1.0, 1.4.0, 1.5.0, 2.0.1, 2.0.2, 2.1.1, 2.1.3, 3.0.0
Dependencies aeson (>=1.0 && <2.3), base (>=4.7 && <4.21), bytestring (>=0.10 && <0.13), containers (>=0.6 && <0.8), directory (>=1.2 && <1.4), filepath (>=1.4 && <1.6), foldl (>=1.4 && <1.5), lens (>=4.9 && <5.4), logging (>=3.0.6 && <3.1), monad-logger (>=0.3 && <0.4), old-locale (>=1.0 && <1.1), optparse-applicative (>=0.10 && <0.19), parallel-io (>=0.3 && <0.4), pretty-show (>=1.10 && <1.11), process (>=1.6 && <1.7), regex-posix (>=0.95 && <0.97), system-fileio (>=0.3 && <0.4), system-filepath (>=0.4 && <0.5), temporary (>=1.2 && <1.4), text (>=1.2 && <2.2), time (>=1.4 && <1.15), transformers (>=0.3 && <0.7), unix (>=2.6 && <2.9), unordered-containers (>=0.2 && <0.3), yaml (>=0.11 && <0.12) [details]
License BSD-3-Clause
Author John Wiegley
Maintainer johnw@newartisans.com
Category System
Home page https://github.com/jwiegley/pushme#readme
Bug tracker https://github.com/jwiegley/pushme/issues
Source repo head: git clone https://github.com/jwiegley/pushme
Uploaded by JohnWiegley at 2025-10-10T23:23:05Z
Distributions
Reverse Dependencies 1 direct, 0 indirect [details]
Executables pushme
Downloads 7098 total (18 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs not available [build log]
Last success reported on 2025-10-10 [all 1 reports]

Readme for pushme-3.0.0

[back to package description]

pushme v3.0.0

Image of pushme in operation

pushme is a wrapper around rsync allowing declarative filesets to be transferred between machines. The screenshot above shows pushme in action (where push is a script I use to call pushme with appropriate arguments based on which machine I'm running it from).

As each destination completes, pushme displays a color-coded completion message (e.g., athena done in green) so you can track progress across multiple parallel transfers.

Configuration

Filesets are declared, one per file, in the directory ~/.config/pushme/filesets. An exhaustive set of options are given in the following src.yaml example:

# Filesets can be specifically named using -f name1,name2,…
Name:     'src'
# Filesets are transmitted in priority order (lower number = higher priority)
# Default priority is 1000 if not specified
Priority: 50
# Classes allow transferring subsets using -c class1,class2,…
Classes:
  - 'small'

# Define on which machines the fileset is located, where, and if custom
# rsync options apply
Stores:
  hera:
    # The remote pathname on `hera` where this fileset resides
    Path: /Users/johnw/src

    # Filters passed to rsync whenever transferring TO `hera`
    # Uses rsync filter syntax (see Filter Syntax section below)
    Filters: |
      - *~
    # If NoBasicOptions is set, then `-a` is not passed to `rsync`
    NoBasicOptions: false
    # If NoDelete is set, then `--delete` is not passed to `rsync`
    NoDelete: true
    # If PreserveAttrs is set, then `-AXUNHE` is passed to `rsync`. This is
    # not the default.
    PreserveAttrs: true
    # If ProtectTopLevel is set, top-level items are protected from deletion
    # (implemented as --filter "P /*" passed to rsync). This prevents --delete
    # from removing any files/directories at the root level of the destination.
    ProtectTopLevel: false
    # Additional `rsync` options added, in addition to any others.
    Options:
      - "--delete-after"
    # Only transfer here if we are transferring FROM the given machines.
    ReceiveFrom:
      - clio
    # Is this fileset active on this machine? If not, never transfer here.
    # Default is true if not specified.
    Active: true

  clio:
    Path: /Users/johnw/src
    ReceiveFrom:
      - hera

  athena:
    Path: /Users/johnw/src
    ReceiveFrom:
      - hera
      - clio

  # `tank` is the same machine as `athena`, it just uses a different set of
  # platter-based directories for long-term, ZFS archival storage. By using a
  # separate ssh hostname to refer to `athena` this way, I gain additional
  # flexibility as to which filesets get transferred and how much parallelism
  # is used when receiving files (to reduce fragmentation)
  tank:
    Path: /tank/src
    ReceiveFrom:
      - hera
      - clio

# In addition to specifying specific options for given targets above, you can
# also specify options here that apply to all targets. Note that each setting
# here may be overridden by target specific settings or command-line options.
Common:
  Filters: |
    # Exclude common build artifacts and temporary files
    - *.agdai
    - *.d
    - *.glob
    - *.hi
    - *.o
    - *~
    - dist/
    - dist-newstyle/
    - node_modules/

Filter Syntax

Filters use standard rsync filter rule syntax. Despite being passed via --include-from, patterns can be both includes and excludes. Key points:

  • - pattern excludes files matching pattern
  • + pattern includes files (when combined with excludes)
  • * matches any characters except /
  • ** matches any characters including /
  • Patterns starting with / are anchored to the source directory root
  • First matching rule wins
  • See man rsync FILTER RULES section for complete details

Simple exclude examples:

Filters: |
  - *~              # Exclude backup files
  - *.o             # Exclude object files anywhere
  - /dist/          # Exclude dist/ in root directory only
  - /.cache/        # Exclude .cache/ in root only
  - **/.git/        # Exclude .git/ directories anywhere

Advanced include/exclude example:

Filters: |
  # Include foo directory, but only some of its children
  + /foo/
  + /foo/bar
  - /foo/*          # Exclude all other files in /foo
  - /foo/*/         # Exclude all other directories in /foo
  - /foo/.*         # Exclude hidden files in /foo
  - /foo/.*/        # Exclude hidden directories in /foo

Usage

Once you have defined a group of filesets, you may transfer all filesets that apply from one machine to any others using the basic command:

pushme SOURCE TARGETS…

For example, I have a desktop hera, a laptop clio, a server athena and a ZFS drive on athena that I reference using the ssh hostname tank. Thus I might use any of the following commands:

pushme hera clio                # update the laptop
pushme hera clio athena         # update the laptop and server
pushme hera clio athena tank    # update both and archival store

Parallelism

If a machine has multiple cores, you can take advantage of parallelism by specifying how many simultaneous transfer jobs you'd like to support either from or to a particular machine:

pushme hera@24 clio@14 athena@10 tank@1

Filesets are always transferred in priority order (lower numbers first), independent of how many times a particular fileset may be "in flight" at a given moment when transferring to multiple machines. This should ensure that the most important data is always completed first, before transferring other filesets.

As transfers to each destination complete, pushme displays a completion message (e.g., athena done in green by default).

Global Configuration

In addition to setting rsync options per-target or common to all targets within a fileset, you may also define global options using ~/.config/pushme/config.yaml:

# If DryRun is true, no changes will be made to any target fileset
DryRun: false
# If Filesets is defined here, only these filesets are eligible for transfer
Filesets:
  - 'foo'
# If Classes is defined here, only matching filesets are eligible for transfer
Classes:
  - 'foo'
# If SIUnits is true, report in gigabytes, for example, instead of gibibytes
SIUnits: true
# If Verbose is true, present a great amount of detail
Verbose: false
# If NoColor is true, disable ANSI color codes in output
NoColor: false
# GlobalOptions are like having a Common block in every fileset
GlobalOptions:
  PreserveAttrs: true
  Options:
    - "--include-from=/Users/johnw/.config/ignore.lst"

Command-Line Options

Options may also be specified using the command-line:

Short Long Description
--config DIR Config directory (default: ~/.config/pushme)
-n --dry-run Do not take any actions, just report what would happen
-f --filesets LIST Comma-separated list of filesets to transfer
-c --classes LIST Comma-separated list of classes to transfer
-s --si-units Use 1000 instead of 1024 as divisor (GB vs GiB)
-v --verbose Report progress verbosely (show full rsync output)
--no-color Disable ANSI color codes in output
--rsync-filters TEXT rsync filter rules to apply globally
--rsync-no-basic-options Do not pass -a to rsync
--rsync-no-delete Do not pass --delete to rsync
--rsync-preserve-attrs Pass -AXUNHE to rsync (preserve all attributes)
--rsync-protect-top-level Protect top-level items from deletion
--rsync-options LIST Space-separated list of options to pass to rsync

Examples:

pushme --dry-run hera athena
pushme -f src,Documents -v hera clio
pushme --no-color hera athena
pushme --rsync-options "--delete-after --partial" hera clio

Configuration Precedence

When the same option is specified in multiple places, pushme uses the following precedence order (highest to lowest):

  1. Target-specific options (in fileset's Stores.<hostname> section)
  2. Command-line options (merged with fileset Common)
  3. Fileset Common (in fileset's Common section)
  4. GlobalOptions (in config.yaml)
  5. Built-in defaults

Merging Behavior

Options are merged using the following rules:

  • For Maybe fields (Filters, Options, ReceiveFrom): Right side wins (later definition completely overrides earlier one)
  • For Boolean flags (NoDelete, PreserveAttrs, etc.): true wins if set anywhere (OR logic)
  • For Active flag: Both must be true (AND logic)

Example: If you specify Options in both GlobalOptions and a target- specific store, only the target-specific Options will be used. They are not concatenated.

Output Format

In non-verbose mode (default), pushme displays summary statistics for each transfer:

Sending src → athena: 42.3M in 127 (1.2G in 1,234) [8s]

This shows:

  • 42.3M - actual data transferred (after compression/rsync delta)
  • 127 - number of files transferred
  • 1.2G - total size of all files examined
  • 1,234 - total number of files examined
  • [8s] - time taken in seconds

With --verbose, full rsync output is displayed instead.

With --no-color, ANSI color codes are disabled (useful for logging or terminals that don't support colors).

Troubleshooting

See what would be transferred without making changes

Use --dry-run to see what would be transferred without making any changes:

pushme --dry-run hera athena

Debug transfer issues

Use --verbose to see detailed rsync output including which files are being transferred:

pushme --verbose hera clio

With verbose mode, filter rules are also logged before each transfer.

Color output appears as garbage

If ANSI color codes appear as garbage characters in your terminal or logs, use --no-color:

pushme --no-color hera athena

Or set NoColor: true in ~/.config/pushme/config.yaml.

Filter rules not working as expected

  1. Use --verbose to see the filter rules being passed to rsync
  2. Test filter rules with rsync directly: rsync -avn --include-from=filters.txt src/ dest/
  3. Remember that first matching rule wins
  4. Check if patterns need to be anchored with / prefix

No filesets found error

Ensure filesets are defined in ~/.config/pushme/filesets/*.yaml (or your custom config directory). Each fileset should have a .yaml extension.

Building from Source

Using Nix (recommended):

nix develop    # Enter development shell
nix build      # Build the project

Using Cabal:

cabal build
cabal run pushme -- hera athena

License

Copyright (C) 2025 John Wiegley BSD3 License - see LICENSE file for details