Documentation
Command reference, layout on disk and retention rules.
Commands
| Command | Effect |
|---|---|
tarn snap SRC DEST | Take a snapshot of SRC into the repository at DEST |
tarn list DEST | List snapshots, newest first, with unique size |
tarn prune DEST | Apply the retention policy and delete expired snapshots |
tarn diff DEST A B | Show files that changed between two snapshots |
tarn verify DEST | Re-hash every file and report mismatches |
Repository layout
A repository is an ordinary directory. Each snapshot is a subdirectory named after its UTC timestamp, plus a small metadata file:
/mnt/backup/work/
├── 2026-07-28T09-00-01/
├── 2026-07-29T09-00-04/
├── 2026-07-30T09-14-02/
└── tarn.index
tarn.index holds the file hashes used to decide what changed. If it
is lost, the next run falls back to size and mtime comparison and rebuilds it.
Excluding files
Patterns are read from .tarnignore in the source root, using the same
syntax as .gitignore:
node_modules/
*.tmp
.cache/
!.cache/keep-this
One-off exclusions
$ tarn snap ~/work /mnt/backup/work --exclude '*.iso' --exclude 'vm/'
Retention
tarn prune keeps the newest snapshot in each period and deletes the
rest. The default policy is:
| Period | Kept |
|---|---|
| Daily | 7 |
| Weekly | 4 |
| Monthly | 6 |
| Yearly | all |
Override per run, or set it once in the repository config:
$ tarn prune /mnt/backup/work --daily 14 --weekly 8 --monthly 12
Pruning never touches the most recent snapshot, even if the policy would
otherwise expire it.
Scheduling
A systemd timer is the usual way to run it unattended:
[Unit]
Description=Nightly snapshot of /home/work
[Service]
Type=oneshot
ExecStart=/usr/local/bin/tarn snap /home/work /mnt/backup/work
ExecStartPost=/usr/local/bin/tarn prune /mnt/backup/work
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Usage error |
2 | Source unreadable |
3 | Destination not writable, or no hard-link support |
4 | Verification found a mismatch |