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 / MintAdd the thern.io APT repo, then sudo apt install moraine
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 LinuxAdd the pacman repo, then sudo pacman -S moraine
Arch Linux (AUR)paru -S moraine — or git clone https://aur.archlinux.org/moraine.git && makepkg -si
Gentooeselect repository add moraine git https://github.com/TheJonaz/moraine-overlay.git, then sudo emerge -av app-backup/moraineUSE="-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)Download, then 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
Raspberry Pi (CLI)Statically linked, no dependencies — unpack and run. uname -m tells you which: aarch64arm64 tarball, armv7larmv7 tarball
macOS (CLI)brew install TheJonaz/moraine/moraine
Windows (CLI)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.

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

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

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

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