Documentation · How-To

Moraine docs

A practical guide to the command line first — install, config, backends, scheduling, retention, restore and troubleshooting — plus a short tour of the desktop app.

Install

Moraine ships a command-line client (moraine) and a GTK desktop app (moraine-gui, Linux). The CLI is what this guide focuses on.

PlatformInstall
Debian / Ubuntu / Mintsudo apt install moraine — Moraine is in official Debian (testing and unstable; it reaches Ubuntu/Mint as they sync). On Debian stable, or to get new releases the day they ship, add the thern.io APT repo first
Fedora / RHELAdd the DNF repo, then sudo dnf install moraine — or from Copr: sudo dnf copr enable jonaz/moraine
openSUSEsudo zypper ar https://download.opensuse.org/repositories/home:/TheJonaz/openSUSE_Tumbleweed/home:TheJonaz.repo, then sudo zypper in moraine
Arch Linuxparu -S moraine — from the AUR, or git clone https://aur.archlinux.org/moraine.git && makepkg -si
Arch Linux (prebuilt)Add the pacman repo, then sudo pacman -S moraine — a binary package, no compile step
Gentoosudo eselect repository add moraine git https://github.com/TheJonaz/moraine-overlay.git, then sudo emaint sync -r moraine and sudo emerge -av app-backup/moraine — USE="-gui" builds the CLI without pulling in GTK
Flatpak (any distro)flatpak remote-add --if-not-exists moraine https://cdn.thern.io/moraine.flatpakrepo, then flatpak install moraine io.thern.moraine
AppImage (any distro)One portable file, GTK bundled: x86-64 or arm64. Make it executable and run it — chmod +x moraine-x86_64.AppImage && ./moraine-x86_64.AppImage. Needs glibc ≥ 2.39 (Ubuntu 24.04+, Debian 13+, Fedora 40+); older systems: use the Flatpak
Snap (any distro)sudo snap install moraine, then sudo snap connect moraine:ssh-keys — CLI only, strictly confined. Covers your home folder, external drives and all remote backends; top-level dotfiles (~/.ssh, ~/.config) need a store permission still under review, so use the .deb/Flatpak/AppImage if you back those up. Scheduling: a host cron/systemd timer running snap run moraine backup <target>
Raspberry Pi (CLI)Statically linked, no dependencies — unpack and run. uname -m tells you which: aarch64 → arm64 tarball, armv7l → armv7 tarball
macOS (CLI)brew install TheJonaz/moraine/moraine — Homebrew also pulls in a modern rsync, which macOS itself does not ship. Or the universal .pkg (Apple Silicon + Intel, installs into /usr/local): it is unsigned, so macOS blocks the first run — Control-click the file and choose Open, or allow it under System Settings → Privacy & Security
Windows (CLI)winget install TheJonaz.Moraine — or with Scoop: scoop bucket add moraine https://github.com/TheJonaz/scoop-moraine, then scoop install moraine
From sourcecargo build --release — add --no-default-features for the CLI only (skips GTK)

The thern.io CDN hosts GPG-signed APT, DNF and pacman repos, so installs auto-update; the exact repo-setup one-liners (GPG key + source line) are on the download page. Every build is also on GitHub Releases. Runtime needs rsync + ssh for the SSH backend; rclone for the cloud/FTP backends.

Quick start

Four steps from nothing to a working snapshot:

# 1. Write a starter config (moraine.toml, owner-only)
$ moraine init

# 2. Edit it — set host, user, dest and your sources
$ $EDITOR moraine.toml

# 3. See exactly what would transfer, without touching anything
$ moraine run --dry-run

# 4. Take the first snapshot
$ moraine run

Each run creates <dest>/<name>/<timestamp>/ on the target and updates a latest pointer. Unchanged files are hard-linked against the previous snapshot, so history is nearly free.

By default Moraine reads moraine.toml in the current directory. Point at another file with -c/--config, e.g. moraine -c ~/.config/moraine/moraine.toml run.

What should I back up?

If you are not sure what belongs in a backup, let Moraine look. moraine recommend scans this machine for a curated, OS-aware set of locations that usually matter — documents, pictures and media, application settings, developer keys, mail — and prints what it actually found, grouped by category:

$ moraine recommend

Paths that carry credentials (SSH keys, GnuPG, cloud tokens) are marked [sensitive], with a reminder to turn on crypt_password if the destination is somewhere you do not control. Nothing is written unless you ask: the command ends with a ready-to-paste [[target]] block, complete with sensible excludes.

$ moraine recommend --write              # append it to moraine.toml
$ moraine recommend --name laptop        # call the target something else

--write preserves the comments already in your config, keeps the file owner-only, and refuses to add a target whose name is taken. The suggestion is a starting point, not a verdict — edit it, and set host, user and dest yourself.

The desktop app has the same thing behind the ✨ Suggest sources button in Quick Backup — tick the locations you want and it builds the target for you.

The config file

The config is TOML: one [[target]] block per destination, each with an optional [target.retention]. Here is an annotated SSH target:

[[target]]
name    = "nas"          # folder on the target & --target name
host    = "192.168.1.50" # IP or hostname
user    = "backup"       # SSH user
port    = 22             # optional (default 22)
key     = "~/.ssh/id_ed25519"  # optional; omit to use ssh-agent
dest    = "/volume1/backups"   # snapshots go under <dest>/<name>/
sources = ["/home/me/documents", "/home/me/pictures"]
exclude = ["*.tmp", "node_modules", ".cache"]  # optional
# strict_host_key = true   # require the host key already in known_hosts
# vpn = "home-vpn"         # NetworkManager connection to raise for this run
# bwlimit = "2M"           # cap the transfer rate (rsync/rclone --bwlimit)
# healthcheck = "https://hc-ping.com/<uuid>"   # dead man's switch, pinged after each run

[target.retention]       # optional — omit to keep everything
keep_last    = 7          # keep the 7 most recent
keep_daily   = 14         # + newest per day, 14 days
keep_weekly  = 8
keep_monthly = 12
FieldMeaning
nameUnique, no / — becomes a folder under dest.
backendssh (default), rclone or ftp.
host, user, portSSH connection (or the rclone remote name in host for the rclone backend).
keyPath to a private SSH key. Omit to use ssh-agent.
passwordOnly for the FTP backend, or an SSH key passphrase / login password (stored plaintext — the file is written mode 0600).
strict_host_keytrue = require the key in known_hosts (protects the first connect). Default trusts on first use.
destRoot directory on the target. Snapshots land in <dest>/<name>/<timestamp>/.
sourcesFiles/folders on this machine to back up. Two sources can't share a base name.
excludersync exclude patterns.
vpnA NetworkManager connection brought up before the run and down after.
bwlimitTransfer rate cap, e.g. "2M" or "500K" (rsync/rclone --bwlimit). Applies to backup and restore; empty = unlimited.
healthcheckA "dead man's switch" URL, pinged after every run of this target — the URL on success, <url>/fail on failure (the healthchecks.io convention). An uptime monitor then alerts you if a scheduled backup silently stops running.
crypt_password, crypt_saltrclone/FTP backends only: encrypt the destination at rest with rclone's crypt.

One key lives outside the target blocks, at the top of the file: notify = false silences the desktop notification shown when a backup finishes (default: on). The desktop app's Schedule tab also stores its schedules here, as [[schedule]] blocks.

Command reference

Every command takes the global -c/--config <file>. Without --target, commands act on all targets.

CommandWhat it does
moraine init [--force]Write an example moraine.toml to start from.
moraine verify [-t NAME]Check sources exist, the SSH connection, and that the destination is writable.
moraine run [-t NAME] [--dry-run]Take a snapshot. --dry-run shows what would transfer without writing.
moraine list -t NAMEList the snapshots on a target.
moraine check [-t NAME] [--snapshot TS]Verify a snapshot's contents against the current sources, by checksum. Without --snapshot it checks the newest complete one.
moraine prune [-t NAME] [--dry-run]Delete old snapshots per the retention policy.
moraine recommend [--write] [--name N]Suggest what to back up on this machine and emit it as a ready [[target]]. See above.
moraine secrets checkReport whether every configured secret can be read right now, and where each one comes from — without printing any of them.
moraine secrets set -t NAME [--field F]Store one secret in the OS keyring and point the config at it.
moraine secrets migrate [--dry-run]Move the plaintext secrets already in the config into the keyring.
$ moraine verify -t nas          # test connection + sources + dest
$ moraine run -t nas             # snapshot one target
$ moraine run --dry-run          # preview all targets
$ moraine list -t nas            # show timestamps
$ moraine prune -t nas --dry-run # show what pruning would delete

A successful moraine run auto-prunes the target afterwards if it has a [target.retention] policy — so a single scheduled run both backs up and cleans up.

Ad-hoc backups (no config file)

moraine run can define a whole target from flags, which is handy for one-off jobs and scripts — nothing is written to moraine.toml:

$ moraine run --host nas --user me --key ~/.ssh/id \
      --dest /backups --source ~/docs --source ~/pictures

It works for every backend (--backend ssh|rclone|ftp) and takes repeated --source/--exclude, plus --port, --name (the snapshot folder under --dest, default: the host), --bwlimit (e.g. 2M) and --strict-host-key.

Encrypting the destination

For the rclone and FTP backends, --crypt-password encrypts the destination at rest (rclone's crypt), with an optional --crypt-salt. Useful when the storage is somewhere you don't control.

Pass secrets through the environment, not flags. A value given as --password or --crypt-password is visible to every other local user in ps and /proc/<pid>/cmdline. Use MORAINE_PASSWORD and MORAINE_CRYPT_PASSWORD instead:

$ MORAINE_PASSWORD=… moraine run --host ftp.example.com --backend ftp \
      --user me --dest backups --source ~/docs

How snapshots work

Moraine uses rsync's --link-dest. Each run writes a full-looking tree, but files that didn't change become hard links to the previous snapshot instead of new copies:

/volume1/backups/nas/
  2026-07-01T02-00-00/     # first run — full copy
  2026-07-02T02-00-00/     # unchanged files are hard links → ~no extra disk
  latest -> 2026-07-02T02-00-00

Because every snapshot is a plain directory tree, you can browse or copy from it with ordinary tools — no special format, no unpacking. Deleting a snapshot only frees the blocks nothing else links to.

An interrupted run never becomes a snapshot

A snapshot is built in a hidden work area and only made visible once every file has transferred. The SSH backend writes to .incomplete-<timestamp>/ and atomically renames it on success; the rclone/FTP backends create a <timestamp>.incomplete marker first and delete it last.

list, check, restore and prune all ignore unfinished snapshots. A crashed or aborted run therefore can't become "the latest snapshot", can't be restored by mistake, and can't trick prune into deleting your last complete backup. Leftovers are cleaned up automatically — on the next successful backup (SSH), or the next successful backup or prune (rclone/FTP).

Only one run per target at a time. A cross-process lock stops a scheduled run and a manual one — or the CLI and the desktop app — from touching the same target at once; the second fails immediately with target is busy. The lock releases itself if a run crashes.

Backends

SSH / rsync (default)

The default. Moraine runs rsync over ssh to user@host. Auth options, easiest first:

The remote needs rsync installed. On the first connect an unknown host key is trusted and pinned (set strict_host_key = true to require it up front instead).

rclone & FTP

Set backend = "rclone" and put an rclone remote name in host (run rclone config first). Works with SFTP, SMB, WebDAV, S3, Drive, B2, and more — no rsync/ssh needed, which makes it the portable choice on Windows.

[[target]]
name    = "cloud"
backend = "rclone"
host    = "myremote"   # an rclone remote (empty = a local path)
dest    = "backups"
sources = ["/home/me/documents"]

For plain FTP without an rclone config, use backend = "ftp" with host/user/password/port. Credentials are passed to rclone through the environment, not the process list.

Retention & pruning

Retention is grandfather-father-son (GFS). Each tier is a count; the newest snapshot is always kept, and unparseable/odd entries are kept too (never accidentally deleted).

KeyKeeps
keep_lastthe N most recent snapshots, regardless of age
keep_dailythe newest snapshot per day, for N days
keep_weeklythe newest per ISO week, for N weeks
keep_monthlythe newest per month, for N months

The kept sets are a union. Run moraine prune -t nas --dry-run to see exactly what a policy would delete before it does.

Scheduling

Automate moraine run with cron on Linux and macOS. Because a successful run auto-prunes, one line does both:

# crontab -e — nightly backup of every target at 02:00
0 2 * * *  moraine -c /home/me/moraine.toml run >/dev/null 2>&1

# or one target, hourly
0 * * * *  moraine -c /home/me/moraine.toml run -t nas >/dev/null 2>&1

Use an absolute path to both moraine and the config (cron has a minimal environment). The desktop app's Schedule tab writes these lines for you.

Windows

On Windows the same Schedule tab installs jobs into the Task Scheduler instead: each schedule becomes a task under the \Moraine\ folder, driven by a small .cmd wrapper in %APPDATA%\Moraine\tasks\. From the CLI, point a Task Scheduler action at moraine.exe with the same -c and run arguments.

Restoring files

A snapshot is just a directory tree, so restoring from the CLI is a normal copy from the target:

# the whole snapshot
$ rsync -aAX backup@192.168.1.50:/volume1/backups/nas/latest/ ./restore/

# a single file or folder from a specific snapshot
$ rsync -aAX backup@192.168.1.50:/volume1/backups/nas/2026-07-01T02-00-00/documents/report.odt  ./

For a point-and-click restore — browse the file tree, tick files, restore to the original location or elsewhere — use the desktop app's Restore tab (see below). Restore never deletes at the destination; it only adds/overwrites.

Restores skip symlinks that point outside the restored tree (rsync --safe-links), because the file list comes from the destination and a compromised target could otherwise plant a link like x → ~/.ssh and write through it. If a legitimate absolute symlink is skipped, rsync says so — recreate it by hand.

Per-target VPN

If a target is only reachable over a VPN, set vpn to a NetworkManager connection name. Moraine brings it up before the run and down after (and leaves it up if you'd already connected it):

vpn = "home-vpn"   # nmcli connection up/down around the run

List your connections with nmcli connection show. This applies to scheduled CLI runs too.

Security

Secrets & the keyring

A secret field — password, crypt_password, crypt_salt — no longer has to sit in the config file. The value now says where the secret is:

ValueWhere the secret lives
password = "hunter2"In the config file, as before.
password = "env:NAS_PW"In the environment variable NAS_PW.
password = "keyring:"In the OS keyring — Secret Service on Linux and the BSDs, Credential Manager on Windows, Keychain on macOS.

The TOML format is unchanged, so an existing config keeps working exactly as it did. To move what you already have:

$ moraine secrets migrate --dry-run   # show what would move
$ moraine secrets migrate             # write to the keyring, repoint the config
$ moraine secrets check               # can every secret be read right now?

Each secret is written to the keyring and read back before the config is rewritten, so a keyring that silently fails to store cannot lose your password.

Run moraine secrets check from the same place your backups run. The keyring needs an unlocked desktop session. A secret that resolves perfectly in your terminal can be unreachable from cron or a headless server — and an unreadable secret now stops the run rather than being quietly treated as an empty password. For scheduled jobs on a server, prefer env: over keyring:.

Keyring support is a build feature. It is on in the default build, so the packages from this project's own channels have it. Builds that leave it out are the headless ones (CLI-only) and Debian's package — Debian builds Rust software against its own packaged crates and does not carry the keyring stack. A build without it has no Thern account control, and keyring: fails with an explanatory error; moraine secrets check tells you which build you are running.

Troubleshooting

MessageWhat it means / fix
--link-dest arg does not exist: ../latestNormal on the very first run (no previous snapshot yet). Harmless.
opendir "…" failed: Permission deniedA source folder isn't readable — fix its permissions (chown/chmod) or add it to exclude.
rsync: command not found (remote)Install rsync on the target — the SSH backend needs it on both ends.
Host key verification failedThe server's key changed. Verify it's legitimate, then update known_hosts.
rsync exit 23 (partial transfer)The run failed — some source files couldn't be read (permissions, I/O errors). The snapshot is not finalized and latest keeps pointing at the previous complete one. Fix the unreadable sources or exclude them, then run again. (Before 0.2.0 this counted as success — a silent way to lose data as retention pruned the snapshots that still held those files.)
rsync exit 24 (files vanished)Normal on a live system — files disappeared mid-run. Still a success; the snapshot is finalized and latest updates.
target is busyAnother run (scheduled, manual, CLI or desktop app) already holds this target's lock. Wait for it to finish — the lock releases itself even if that run crashes.
rclone anonymous-login / password errorThe FTP password couldn't be obscured — make sure rclone is installed and working.

Run moraine verify -t NAME for a quick health check of sources, connection and destination.

Desktop app (GUI)

The GTK app (moraine-gui, Linux) drives the same engine and config as the CLI — anything you set up in one shows up in the other. Six tabs:

Install the desktop app on Linux from the .deb (Debian/Ubuntu/Mint) or the Arch package — it drops both moraine and moraine-gui. See the download page.

← Back to the homepage   GitHub