Install
Moraine ships a command-line client (moraine) and a GTK desktop app (moraine-gui, Linux). The CLI is what this guide focuses on.
| Platform | Install |
|---|---|
| Debian / Ubuntu / Mint | Add the thern.io APT repo, then sudo apt install moraine |
| Fedora / RHEL | Add the DNF repo, then sudo dnf install moraine — or from Copr: sudo dnf copr enable jonaz/moraine |
| openSUSE | sudo zypper ar https://download.opensuse.org/repositories/home:/TheJonaz/openSUSE_Tumbleweed/home:TheJonaz.repo, then sudo zypper in moraine |
| Arch Linux | Add 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 |
| Gentoo | eselect repository add moraine git https://github.com/TheJonaz/moraine-overlay.git, then 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) | 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: aarch64 → arm64 tarball, armv7l → armv7 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 source | cargo 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
| Field | Meaning |
|---|---|
name | Unique, no / — becomes a folder under dest. |
backend | ssh (default), rclone or ftp. |
host, user, port | SSH connection (or the rclone remote name in host for the rclone backend). |
key | Path to a private SSH key. Omit to use ssh-agent. |
password | Only for the FTP backend, or an SSH key passphrase / login password (stored plaintext — the file is written mode 0600). |
strict_host_key | true = require the key in known_hosts (protects the first connect). Default trusts on first use. |
dest | Root directory on the target. Snapshots land in <dest>/<name>/<timestamp>/. |
sources | Files/folders on this machine to back up. Two sources can't share a base name. |
exclude | rsync exclude patterns. |
vpn | A 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.
| Command | What 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 NAME | List 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 ~/docsHow 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:
- ssh-agent — omit
key; your loaded agent key is used. - Key file — set
key = "~/.ssh/id_ed25519". - Passphrase / password — set
password; it's fed to ssh viaSSH_ASKPASS, never on the command line.
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).
| Key | Keeps |
|---|---|
keep_last | the N most recent snapshots, regardless of age |
keep_daily | the newest snapshot per day, for N days |
keep_weekly | the newest per ISO week, for N weeks |
keep_monthly | the 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 on disk are owner-only.
moraine.tomland the run log are written mode0600. Keep it that way if you store apassword. - Secrets from the config never reach the command line. SSH auth goes through
SSH_ASKPASS; FTP credentials go via rclone's environment — nothing frommoraine.tomlshows up inps. - Ad-hoc flags are the exception — use the environment. A passphrase passed as
--passwordor--crypt-passwordis visible to other local users inpsand/proc/<pid>/cmdline. PreferMORAINE_PASSWORDandMORAINE_CRYPT_PASSWORD. - Host keys. Trust-on-first-use by default;
strict_host_key = truerequires the key inknown_hostsup front. Recommended for any target that logs in with a password — trust-on-first-use would hand the password to whoever answers first. - Restores don't follow symlinks out of the tree. The restore file list comes from the destination — the side Moraine treats as untrustable — so restores use rsync
--safe-links. A compromised destination can't plantx → ~/.sshand have a restore write through it. Trade-off: legitimate absolute symlinks are skipped (rsync reports each one). - Downloads are checksum-verified. Every release publishes a
SHA256SUMScovering all assets, and the desktop app's updater checks the downloaded installer against it before offering Open. - Encrypted config export. In the desktop app, ⚙ Settings → Export config writes a password-protected (gpg AES-256) copy to move between machines.
Troubleshooting
| Message | What it means / fix |
|---|---|
--link-dest arg does not exist: ../latest | Normal on the very first run (no previous snapshot yet). Harmless. |
opendir "…" failed: Permission denied | A 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 failed | The 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 busy | Another 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 error | The 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:
- Quick Backup — pick a target, edit the connection, Test connection, then Dry run or Run backup with a live progress bar (amount, rate, ETA). ⚙ Settings per target: sources (File… / Folder… pickers), excludes, retention, VPN, strict host key.
- Schedule — build schedules (hourly/daily/weekly) and Install them into crontab.
- Restore — snapshots load automatically; pick one, browse the file tree, tick files/folders, and restore. Restore to: defaults to the original location and is editable.
- History — every backup/restore/prune, success or failure.
- Settings — encrypted config export/import, start-at-login (launches minimized to the taskbar), and app info.
- Help — the whole feature set explained inside the app: backups & snapshots, the backends, every Advanced target setting, scheduling, restore, and how secrets are handled. No internet needed.
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.