Skip to main content

rangler

rangler is a macOS command-line tool that backs up a single user's home directory to an rclone remote. It mirrors an explicit list of configured paths (under files/) and auto-discovered project .local directories (under locals/), namespaces everything by hostname, keeps prior versions of changed and deleted files, and can run unattended on a schedule.

Backups are one-way (local to remote). rangler never writes to your local filesystem except its own configuration, logs, runtime state, and its LaunchAgent. It shells out to the rclone binary you have installed; it does not install or configure rclone, and it adds no encryption of its own (use an rclone crypt remote if you want that).

Requirements

  • macOS, single user.
  • rclone installed and at least one remote configured.
  • Python 3.13+ and uv for installation from source.

Installation

brew install rclone
rclone config          # create a remote, e.g. a `local` remote named `backup`
uv tool install --editable .   # or run ad hoc with: uv run rangler ...

Configuration

rangler reads a single TOML file at ~/.config/rangler/config.toml. Scaffold a starter file with rangler init, then edit it. Every path must live under your home directory.

Key Type Default Notes
remote_base string (required) rclone remote in name:path form, e.g. backup:rangler.
paths array of string [] Explicit paths under $HOME to back up. ~ is expanded.
scan_roots array of string ["~/Code"] Roots scanned for .local directories.
max_depth integer 4 Maximum descent depth under each scan root.
excludes array of string see below rclone filter patterns applied to every target.
time string or time "12:00" Daily local time backups run, as "HH:MM" (24-hour).
notify string "failure" Desktop notifications: failure, always, or never.

Default excludes: node_modules/**, .git/**, .DS_Store, *.log, .cache/**, __pycache__/**. At least one of paths or scan_roots must be non-empty. Unknown keys are reported as warnings (typo protection) rather than aborting.

Usage

rangler init

Scaffold a starter config.toml. Does nothing if one already exists unless you pass --force to overwrite it.

rangler init

rangler check

Validate the configuration, confirm rclone is present and the remote is reachable, and print the full resolved target list (each source and its computed destination) without transferring anything. Exits 0 when valid, 2 when the configuration is invalid, and 3 when rclone is missing or the remote is unreachable.

rangler check

rangler run

Back up every resolved target. Each explicit path is mirrored to <remote_base>/<hostname>/files/<path-relative-to-home>. Symlinks are followed so symlinked dotfiles are stored as content. Prior copies of changed or deleted files are moved into <remote_base>/<hostname>/_versions/<run-timestamp>/... rather than being hard-deleted. Each run prints per-target progress and a final summary, and writes a timestamped log to ~/Library/Logs/rangler/.

rangler run            # perform the backup
rangler run --dry-run  # report intended changes without writing to the remote

A configured path that does not exist on this machine is reported as skipped and does not fail the run (paths legitimately differ across machines). A path that exists but errors during transfer is recorded as a failed target; the run continues with the others and exits non-zero. Overlapping runs are prevented by a lock: a second concurrent run exits cleanly with skipped: already running.

Auto-discovery of .local directories

Beyond the explicit paths list, rangler scans each entry in scan_roots (default ~/Code) for project .local directories down to max_depth (default 4). Each discovered <root>/.../project/.local is mirrored to <remote_base>/<hostname>/locals/<project-path-relative-to-home> - the .local segment itself is dropped. For example, ~/Code/pathling/.local is backed up to locals/Code/pathling.

Discovery deliberately skips:

  • the XDG ~/.local directory (it shares the name but is not a project scratch directory);
  • .Trash and any directory named by an exclude pattern (such as node_modules or .git), which are pruned without being descended into.

If the same directory is both discovered and listed explicitly in paths, it is backed up once, with the explicit files/ mapping taking precedence. Run rangler check to preview the full resolved target list - explicit and discovered alike - before backing up.

Scheduling (schedule / unschedule / status)

rangler can run unattended via a macOS LaunchAgent that fires once a day at a fixed local time and survives reboot.

rangler schedule --at 02:00    # install and load the agent (defaults to config time)
rangler status                 # report whether scheduling is active and the last run
rangler unschedule             # unload and remove the agent

schedule writes a LaunchAgent plist to ~/Library/LaunchAgents/au.id.grimes.john.rangler.plist and loads it with launchctl bootstrap; re-running it replaces any existing agent. --at accepts a 24-hour HH:MM local time and defaults to the time from the config (12:00 if unset). Because launchd starts agents with a minimal PATH that omits the Homebrew directories, the plist sets an EnvironmentVariables PATH covering both Homebrew prefixes (/opt/homebrew/bin and /usr/local/bin) so the scheduled run can locate rclone. The agent uses launchd StartCalendarInterval, so the schedule is anchored to a wall-clock time rather than a relative countdown: reinstalling the agent never delays the next run, and if the machine is asleep or off at the scheduled time launchd runs the missed backup on the next wake. unschedule is idempotent: it succeeds even if nothing was installed. status reports the scheduled daily time and the time and result of the last run (read from ~/.local/state/rangler/last-run.json).

Breaking change: earlier versions scheduled on a repeating interval via an interval config key and a rangler schedule --every <duration> option. Both have been removed. A stale interval key is now reported as an unknown key (warning) and ignored, and the daily time default applies. An agent installed by an older version keeps running on its old interval until you next run rangler schedule, which migrates it to the daily-time agent; status flags such a legacy agent and points you at the reschedule.

You can confirm the loaded job directly with launchctl:

launchctl print gui/$(id -u)/au.id.grimes.john.rangler

Notifications

For unattended runs, rangler can raise a macOS desktop notification at run completion. The notify config key controls when:

  • failure (default): notify only when one or more targets failed.
  • always: notify on every completed run, success or failure.
  • never: stay silent.

Notifications fire for real runs (manual or scheduled); a --dry-run preview is always silent.

Remote layout

Given remote_base = "backup:rangler" and hostname mymac:

backup:rangler/
└── mymac/                         # Hostname segment.
    ├── files/                     # Explicit configured paths.
    │   ├── .ssh/...               # from ~/.ssh
    │   └── Documents/notes/...    # from ~/Documents/notes
    ├── locals/                    # Auto-discovered .local directories.
    │   └── Code/pathling/...      # from ~/Code/pathling/.local
    └── _versions/                 # Versioned overwrites/deletes.
        └── 2026-06-19T03-21-55Z/  # One sub-tree per run that changed something.
            ├── files/...
            └── locals/...

This layout is a stable contract that manual restore relies on. rangler only ever writes under <remote_base>/<hostname>/, so other machines' trees are never touched.

Restore (manual)

Restore is performed manually with rclone against the layout above. For example:

rclone copy backup:rangler/mymac/files/.ssh ~/.ssh
rclone copy backup:rangler/mymac/locals/Code/pathling ~/Code/pathling/.local

Earlier versions of a file are found under backup:rangler/mymac/_versions/<timestamp>/....

Troubleshooting

  • "rclone is not installed or not on your PATH" - install rclone (brew install rclone) and confirm rclone version works in your shell.
  • "the remote ... is not reachable" - the name before the : in remote_base must match a remote from rclone listremotes. Create or rename it with rclone config.
  • A configured path is reported as skipped - the path does not exist on this machine. This is expected when paths differ across machines; it does not fail the run.
  • A target is reported as failed - check the per-run log under ~/Library/Logs/rangler/ for the rclone error (for example a permission problem). The run still backs up every other target and exits non-zero.
  • "skipped: already running" - another rangler run holds the lock at ~/.local/state/rangler/rangler.lock. Wait for it to finish; the lock is released automatically even if that process is killed.
  • Scheduled runs are not happening - confirm the agent is loaded with launchctl print gui/$(id -u)/au.id.grimes.john.rangler and inspect ~/Library/Logs/rangler/launchd.err.log.

Licence

rangler is licensed under the Apache License, Version 2.0. See the NOTICE file for attribution.

Copyright © 2026 John Grimes.

Metadata

Release files for rangler 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rangler 0.2.0
File Size Uploaded Provenance Actions
rangler-0.2.0.tar.gz 56.2 kB Without provenance Details for rangler-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rangler 0.2.0
File Interpreter ABI Platform Actions
rangler-0.2.0-py3-none-any.whl Python 3 none any Details for rangler-0.2.0-py3-none-any.whl

Total release size: 93.2 kB

Release files / rangler-0.2.0.tar.gz

Download URL rangler-0.2.0.tar.gz
Size 56.2 kB
Tags Source
SHA-256 checksum
How to use checksums
85c01bd17db975372f912e0dfe85bac72f58c7b1dba18ff8e0af1b459c7b7cf7
BLAKE2b-256 checksum
How to use checksums
92f1cd33f763c60f91a688e48ca222afb69a1a1c6da0f131fe5d196012c1895d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / rangler-0.2.0-py3-none-any.whl

Download URL rangler-0.2.0-py3-none-any.whl
Size 37.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fb66e3025483cb63d8b49000f69f1af15801eaa85b54a746a195741ac2ef3d4b
BLAKE2b-256 checksum
How to use checksums
787b5d0f8d2b5b742b226bd99dec9d59e73f920a7f5d5fce572806e7d025d75b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for rangler 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page