Stateless backup tool for two Incus(OS) systems in a source → target topology.
- Creates snapshots on the source
- Replicates custom volumes and instances to the target
- Prunes only IAB-managed snapshots based on a retention policy
Inspired by: psy0rz: zfs_autobackup
This project is in an early stage. Expect bugs and sharp edges. Test thoroughly before using it on production systems.
Tested primarily with Incus OS, but it should also work with regular Incus setups. Make sure the Incus API is reachable.
There is a current Incus OS issue affecting snapshot handling/retention in some setups.
Practical impact:
- Keeping a different amount of snapshots on source vs. target does not work as expected
- This repository includes a workaround flag
--iOSfix(default:true) which forces the target retention policy to match the source retention policy.
Download a prebuilt binary from the GitHub Releases page and place it for example in your $HOME.
Requires a working Go toolchain.
Build for x86_64 linux platform:
make build
./iab --versionOr adjust the go compiler to fit your needs.
IAB is intended to be executed periodically (e.g. via cron or a systemd timer).
Example setup: run it inside an Incus container on one of the hosts and execute daily.
Adjust paths as needed:
ExecStartshould point to youriabbinaryWorkingDirectorymust contain yourconfig.json
/etc/systemd/system/iab.service
[Unit]
Description=IncusAutobackup (IAB)
[Service]
Type=oneshot
WorkingDirectory=/root/
ExecStart=/root/iab/etc/systemd/system/iab.timer
[Unit]
Description=Run IAB daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetEnable:
sudo systemctl daemon-reload
sudo systemctl enable --now iab.timer
sudo systemctl list-timers --all | grep iabOnboarding pins the server certificate and registers an IAB client certificate on each host using a trust token.
On each host generate a trust token (restricted is optional):
Restricted example:
incus config trust add iab --restricted --projects defaultUnrestricted example:
incus config trust add iab./bin/iab onboard \
--sourceURL "https://SOURCE:8443" \
--sourceToken "TOKEN_FROM_SOURCE" \
--targetURL "https://TARGET:8443" \
--targetToken "TOKEN_FROM_TARGET" Optional flags:
--iabCredDir <path>: where credentials and pinned server certs are stored Default:~/.config/incusAutobackup--outConfig <path>: where the config.json file is stored. Default is the current directory
On success, onboarding writes an initial config.json into the current directory.
IAB reads ./config.json from the current working directory.
Run:
./iabUseful flags:
--dryRun(implies both copy+prune dry-run)--dryRunCopy(skip snapshot + copy)--dryRunPrune(skip pruning)--log-level debug|info|warn|error(default: info)--iOSfix=true|false(see note above, default: true)--version
Example config: config.json.example (note: the filename is currently spelled exmaple in this repo).
iabCredDir: credential directory created by onboardinguuid: set by onboardingstopInstance: iftrue, stop running instances before snapshot/copy and start them afterwardshealthchecksUrl: optional Healthchecks ping URL (see below)gotifyURL: optional Gotify notification URL (see below)
Define exactly one source and one target:
"hosts": [
{ "name": "prod-source", "role": "source", "url": "https://192.168.1.100:8443" },
{ "name": "prod-target", "role": "target", "url": "https://192.168.1.101:8443" }
]Projects define what to replicate:
name: Incus project name (e.g.default)mode:push(default) orpull(Incus copy mode)instances: list of instances/VMsvolumes: list of custom volumes
Instance fields:
name: instance namestorage: target pool name for the root disk (IAB will set the root disk pool on the target)excludeDevices(optional): drop devices by device-name during copy
Example:
"instances": [
{
"name": "vm1",
"storage": "local",
"excludeDevices": ["eth0", "custom-volume-1"]
}
]Device handling note:
- If a NIC device references a managed network that does not exist on the target, IAB may drop that NIC device during copy to avoid a hard failure.
- If a disk device references a custom volume missing on the target, IAB may drop that disk device during copy.
- Use
excludeDevicesto explicitly drop known-problematic devices.
Retention policies apply to pruning of snapshots created by IAB.
- IAB creates snapshots with the prefix:
IAB_ - Format:
IAB_YYYYMMDD-HHMMSS - Only snapshots with this prefix are managed/pruned by IAB.
- Other snapshots are ignored.
Examples:
36,1h2d,1d2w,1w3m
Meaning:
-
Optional first number:
alwaysKeep(e.g.3) Always keep the newest 3 IAB snapshots, regardless of age. -
Then one or more thinning rules:
<period><TTL>(e.g.1h2d) For snapshots in the lastTTL, group them intoperiod-sized time buckets and keep at most one snapshot per bucket (the newest one in that bucket).
Older snapshots (outsideTTL) are not kept by this rule. -
Multiple rules are combined: a snapshot is kept if it is selected by any rule (or by
alwaysKeep).
Policy: 6,1h2d,1d2w
Assume you create snapshots very frequently (e.g. every 15 minutes):
-
alwaysKeep=6: Keeps the 6 newest snapshots no matter what. -
1h2d(period=1 hour, TTL=2 days): Looking back 2 days from “now”, split time into 1-hour buckets and keep at most 1 snapshot per hour (the newest snapshot that falls into each hour). -
1d2w(period=1 day, TTL=2 weeks): Looking back 2 weeks from “now”, split time into 1-day buckets and keep at most 1 snapshot per day (the newest snapshot that falls into each day).
The final set of kept snapshots is the union of all selections above. So in practice you end up with:
- the newest 6 snapshots,
- plus roughly "hourly" snapshots for the last 2 days,
- plus roughly "daily" snapshots for the last 2 weeks,
- and no additional snapshots older than 2 weeks (unless they are part of
alwaysKeep).
s,min,h,d,w,m(30 days),y(365 days)
If a policy is empty or omitted, IAB will not prune (keeps all IAB snapshots). Note: every month contains 30 days and every year consists of 365 days (not calendar-aware).
Retention can be defined per:
- specific instance/volume name (
byName) - kind (
instances/volumes) - project
- host role (
source/target)
The list is in descending priotity order.
See config.json.example for a full hierarchy.
Provide your healthchecks URL to enable start and finish notifications.
Provide your gotify URL (incl. App Token) to get notified when IAB finished with errors.
{
"iab": {
"iabCredDir": "./root/.config/incusAutobackup",
...
"healthchecksUrl": "https://hc.example.tld/ping/your-uuid-here",
"gotifyUrl": "https://gotify.example.tld/message?token=APP_TOKEN"
},
This project is licensed under the MIT License. See LICENSE.
This software depends on other open-source libraries, including:
-
Incus Go client library:
github.com/lxc/incus/v6(Apache License 2.0) License text in this repo:vendor/github.com/lxc/incus/v6/COPYINGUpstream: https://github.com/lxc/incus -
github.com/google/uuid(BSD-3-Clause) License text in this repo:vendor/github.com/google/uuid/LICENSEUpstream: https://github.com/google/uuid/blob/master/LICENSE
For a complete list of dependencies, see go.mod and vendor/.