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 | sudo 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 / 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 | paru -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 |
| Gentoo | sudo 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 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.
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
| 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. |
bwlimit | Transfer rate cap, e.g. "2M" or "500K" (rsync/rclone --bwlimit). Applies to backup and restore; empty = unlimited. |
healthcheck | A "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_salt | rclone/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.
| 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 recommend [--write] [--name N] | Suggest what to back up on this machine and emit it as a ready [[target]]. See above. |
moraine secrets check | Report 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 ~/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.
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:
| Value | Where 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
| 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. Six 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). ✨ Suggest sources scans the machine and turns the locations you tick into a ready-made target (see What should I back up?). ⚙ Settings per target: sources (File… / Folder… pickers), excludes, retention, VPN, strict host key, and a Move to keyring button under each secret field.
- 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.
- Thern account (optional) — sign in from the control next to the Help tab to attribute the feedback you send from the app to your account. It is a device-code login: the app shows a code, you approve it in the browser, and the token is kept in the OS keyring, never in the config. Backups behave identically whether you sign in or not.
- 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.