Borg backup
Borg is a backup tool to perform incremental backups, local or remote.
$ nix-env -iA nixpkgs.borgbackup
To be able to do remote backups it should be installed both locally and remotely, but usually no remote configuration required, only local one.
Creating backups
I'll describe remote SSH backups here, as this is the most important case. It should be as easy as:
services.borgbackup.jobs.home-danbst = {
paths = "/home/danbst";
encryption.mode = "none";
environment.BORG_RSH = "ssh -i /home/danbst/.ssh/id_ed25519";
repo = "ssh://user@example.com:23/path/to/backups-dir/home-danbst";
compression = "auto,zstd";
startAt = "daily";
};
First, create a directory for backups /path/to/backups-dir
on your remote machine, then rebuild local machine using this config and correctly specified paths
, BORG_RSH
, etc.
It will create "archives" with identifiers like station-home-danbst-2020-06-10T00:00:46
every day.
Personally, I've adapted that to exclude unrelated stuff and split into multiple repos, but you don't necessarily need that:
services.borgbackup.jobs =
let common-excludes = [
# Largest cache dirs
".cache"
"*/cache2" # firefox
"*/Cache"
".config/Slack/logs"
".config/Code/CachedData"
".container-diff"
".npm/_cacache"
# Work related dirs
"*/node_modules"
"*/bower_components"
"*/_build"
"*/.tox"
"*/venv"
"*/.venv"
];
work-dirs = [
"/home/danbst/dev/company1"
"/home/danbst/dev/company2"
];
basicBorgJob = name: {
encryption.mode = "none";
environment.BORG_RSH = "ssh -o 'StrictHostKeyChecking=no' -i /home/danbst/.ssh/id_ed25519";
environment.BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK = "yes";
extraCreateArgs = "--verbose --stats --checkpoint-interval 600";
repo = "ssh://user@example.com//media/backup/${name}";
compression = "zstd,1";
startAt = "daily";
user = "danbst";
};
in {
home-danbst = basicBorgJob "backups/station/home-danbst" // rec {
paths = "/home/danbst";
exclude = work-dirs ++ map (x: paths + "/" + x) (common-excludes ++ [
"Downloads"
]);
};
home-danbst-downloads = basicBorgJob "backups/station/home-danbst-downloads" // rec {
paths = "/home/danbst/Downloads";
exclude = map (x: paths + "/" + x) common-excludes;
};
extra-drive-important = basicBorgJob "backups/station/extra-drive-important" // rec {
paths = "/media/extra-drive/important";
exclude = map (x: paths + "/" + x) common-excludes;
};
};
After doing at least one successful backup don't forget to test mount it (see next)
Notifications when backup fails
Quite often backups do fail. To perform notifications about this situations, you can setup autonotifier for all NixOS borg jobs. This requires creating a separate module, but can be also done inplace in /etc/nixos/configuration.nix
Note, that example below was for Gnome-shell desktop! For other desktops it may require changes for how to get DBUS session properly!
{ pkgs, config, lib, ... }:
let
borgbackupMonitor = { config, pkgs, lib, ... }: with lib; {
key = "borgbackupMonitor";
_file = "borgbackupMonitor";
config.systemd.services = {
"notify-problems@" = {
enable = true;
serviceConfig.User = "danbst";
environment.SERVICE = "%i";
script = ''
export $(cat /proc/$(${pkgs.procps}/bin/pgrep "gnome-session" -u "$USER")/environ |grep -z '^DBUS_SESSION_BUS_ADDRESS=')
${pkgs.libnotify}/bin/notify-send -u critical "$SERVICE FAILED!" "Run journalctl -u $SERVICE for details"
'';
};
} // flip mapAttrs' config.services.borgbackup.jobs (name: value:
nameValuePair "borgbackup-job-${name}" {
unitConfig.OnFailure = "notify-problems@%i.service";
}
);
# optional, but this actually forces backup after boot in case laptop was powered off during scheduled event
# for example, if you scheduled backups daily, your laptop should be powered on at 00:00
config.systemd.timers = flip mapAttrs' config.services.borgbackup.jobs (name: value:
nameValuePair "borgbackup-job-${name}" {
timerConfig.Persistent = true;
}
);
};
in {
imports =
[
....
borgbackupMonitor
];
...
}
Don't try backup when network is unreachable
With persistent timers above you can get into a problem that after reboot backup is tried too fast, even when network is not yet available, and thus fails. This can be solved with systemd failed restart, or using internet-ready check in preStart
script.
Patching previous example:
} // flip mapAttrs' config.services.borgbackup.jobs (name: value:
nameValuePair "borgbackup-job-${name}" {
unitConfig.OnFailure = "notify-problems@%i.service";
preStart = lib.mkBefore ''
# waiting for internet after resume-from-suspend
until /run/wrappers/bin/ping google.com -c1 -q >/dev/null; do :; done
'';
}
);
...
Mounting point-in-time archives
First, check if there are any archives:
$ borg list user@example.name:/media/backup/backups/station/home-danbst
...
station-home-danbst-2020-06-02T00:00:02 Mon, 2020-06-01 21:00:09 [24e6318a379ac3b494448fb2ab2ca7b2df7188426d0814978165cab8e09cd642]
station-home-danbst-2020-06-03T00:00:12 Tue, 2020-06-02 21:00:20 [2912b78d306f5b4a254e099f0878d743849c1b99c4441815e3fa43d485414438]
station-home-danbst-2020-06-04T00:00:07 Wed, 2020-06-03 21:00:29 [8a5e03a8f9a1a397e5e05585e112f2206043cfdc8cca4d97f73079be89ba1009]
station-home-danbst-2020-06-05T00:00:02 Thu, 2020-06-04 21:00:11 [14df97419fd2e2a243d24cd04b643044a5c732b815cc25d3bddd8df6cf1f2549]
station-home-danbst-2020-06-06T00:00:00 Fri, 2020-06-05 21:00:11 [69684418497adc43a0ae8ebc617494247bf6ecfe42ba6c04db354253c7f5e144]
station-home-danbst-2020-06-08T00:00:52 Sun, 2020-06-07 21:00:59 [d5b08fa57cfb3727c5f7d855509158a70907b6fa009abe30579328def74fd2a6]
station-home-danbst-2020-06-10T00:00:46 Tue, 2020-06-09 21:00:53 [85a5329ae39c46a03e2e925ef740f8d5090a61c35161cdd539ac248a53e5b7d4]
Choose one of "archives" and mount it locally:
$ borgfs -f -o uid=1002 \
user@example.com:/media/backup/backups/station/home-danbst::station-home-danbst-2020-06-10T00:00:46 \
~/borg-home-danbst2
home/danbst
where uid is your user's UID, in case you are restoring on a different system. You also should have private ssh key in your ssh-agent, or specified in BORG_RSH
. The last arg is optional, but without it you'll get root view of backup.
Automounting backups using NixOS
Actually, I don't have a solution as of time being, so I'll share a setup which allows automount, but requires sudo
to access.
fileSystems."/run/user/1002/borg-home-danbst" = {
device = "user@example.com:/media/backup/backups/station/home-danbst::station-home-danbst-2020-06-10T00:00:46";
noCheck = true;
fsType = "fuse.borgfs"; # note that this requires a custom binary, see below
options = [ "x-systemd.automount" "noauto" "uid=1002" "exec" ]; # I'm using automount here to skip mount on boot, which slows startup
};
# this one should mount the actual directory from the root view of backup
fileSystems."/run/user/1002/home-danbst" = {
device = "/run/user/1002/borg-home-danbst/home/danbst";
options = [ "bind" ];
};
environment.systemPackages = [
...
(pkgs.writeScriptBin "mount.fuse.borgfs" ''
#!/bin/sh
export BORG_RSH="ssh -i /home/danbst/.ssh/id_ed25519"
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
exec ${pkgs.borgbackup}/bin/borgfs "$@"
'')
];
If anybody reading this have found a way to mount as a user properly, please update the code above.