Skip to content

Instantly share code, notes, and snippets.

@kampka

kampka/btrbk.nix Secret

Created December 8, 2016 13:30
Show Gist options
  • Save kampka/fbe40826d0a9bf38884d9bad192f56ff to your computer and use it in GitHub Desktop.
Save kampka/fbe40826d0a9bf38884d9bad192f56ff to your computer and use it in GitHub Desktop.
Btrbk backup module for nix
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.kampka.services.btrbk;
# WARNING:
# indention in the render functions is crucially important as
# it is partially lost by (concatStringsSep (map renderFunc ...))
renderSubvols = (subvolume:
''
subvolume ${subvolume.name}
${optionalString (subvolume.snapshotName != null) "snapshot_name ${subvolume.snapshotName}"}
snapshot_preserve ${subvolume.snapshotPreserve}
snapshot_preserve_min ${subvolume.snapshotPreserveMin}
target_preserve ${subvolume.targetPreserve}
target_preserve_min ${subvolume.targetPreserveMin}
# Currently only send-receive is supported
target send-receive ${subvolume.target}
''
);
renderVolume = (volume:
''
# Volume ${volume.name}
volume ${volume.name}
snapshot_dir ${volume.snapshotDir}
${concatStringsSep "\n " (map renderSubvols volume.subvolumes)}
''
);
# Defines options for subvolumes
subvolOpts = { name, config, ... }: {
options = {
name = mkOption {
type = types.str;
description = "Subvolume to be backed up, relative to the btrfs volume";
};
target = mkOption {
type = types.str;
description = "The target directory or url for the backups";
};
snapshotName = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Base name of the created snapshot (and backup).
This option is only valid in the subvolume section.
Defaults to subvolume name.
'';
};
snapshotPreserve = mkOption {
type = types.str;
default = "no";
description = ''
Set retention policy for snapshots. If set to “no”,
preserve snapshots according to snapshot_preserve_min only.
Defaults to "no".
'';
};
snapshotPreserveMin = mkOption {
type = types.str;
default = "latest";
description = ''
Preserve all snapshots for a minimum amount of
hours (h), days (d), weeks (w), months (m) or years (y),
regardless of how many there are. If set to "all",
preserve all snapshots forever.
If set to "latest", preserve latest snapshot.
Defaults to "latest".
'';
};
targetPreserve = mkOption {
type = types.str;
default = "no";
description = ''
Set retention policy for backups.
If set to “no”, preserve backups according
to target_preserve_min only.
Defaults to "no"
'';
};
targetPreserveMin = mkOption {
type = types.str;
default = "all";
description = ''
Preserve all backups for a minimum amount of
hours (h), days (d), weeks (w), months (m) or years (y),
regardless of how many there are.
If set to "all", preserve all backups forever.
If set to "latest", always preserve the latest backup
(useful in conjunction with "target_preserve no",
if you want to keep the latest backup only).
If set to "no", only the backups following the
target_preserve policy are created.
Defaults to "all".
'';
};
};
};
# Options for volumes
volumeOpts = { name, config, ... }: {
options = {
name = mkOption {
type = types.str;
description = "Directory of a btrfs volume containing the source subvolume(s) to be backed up";
};
snapshotDir = mkOption {
type = types.str;
description = "Directory in which the btrfs snapshots are created, relative to the volume.";
};
subvolumes = mkOption {
type = types.listOf (types.submodule subvolOpts);
description = "Subvolumes to backup on this volume";
};
};
};
btrbkConfig = pkgs.writeText "btrbk.conf" ''
timestamp_format long-iso
transaction_syslog daemon
${concatStringsSep "\n" (map renderVolume cfg.volumes )}
'';
in {
options.kampka.services.btrbk = {
enable = mkEnableOption "btrbk";
interval = mkOption {
type = types.str;
default = "hourly";
description = ''
The interval at which to trigger a backup.
Valid values must conform to systemd.time(7) format.
'';
};
volumes = mkOption {
type = types.listOf (types.submodule volumeOpts);
description = ''
The volumes to back up.
Example:
{
name = "/mnt/rootfs";
snapshotDir = "btrbk_snapshots";
subvolumes = [{
name = "etc/nixos";
target = "/mnt/backup/";
snapshotName = "etc-nixos";
} {
name = "home";
target = "/mnt/backup";
}
}
'';
};
};
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [ btrbk ];
environment.etc."btrbk/btrbk.conf".source = btrbkConfig;
# Defines the timer to trigger the backup via systemd
systemd.timers.btrbk = {
description = "Timer for btrbk backups";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.interval;
Unit = "btrbk.service";
Persistent = "true";
};
};
# Defines the actual serivce unit
systemd.services.btrbk = {
description = "btrbk backup";
restartTriggers = [ btrbkConfig ];
# Add eject (== util-linux) and btrfs progs to the Path
# so they are available in the preStart script
path = [ pkgs.eject pkgs.btrfs-progs ];
# The pre-start script ensures that all mount points are availabe
# creates all required subvolumes necessary to perform the backup
# as specified in the configuration.
preStart = ''
set -e
set -u
${concatStringsSep "\n" (map (volume:
''
if ! mountpoint -q "${volume.name}" ; then
echo "ERROR: Path ${volume.name} is not a mountpoint."
exit 1
fi
btrfs subvolume show "${volume.name}" 1>/dev/null
if ! btrfs subvolume show "${volume.name}/${volume.snapshotDir}" 1>/dev/null; then
snapshot="${volume.name}/${volume.snapshotDir}"
basedir="$(dirname ${volume.name}/${volume.snapshotDir})"
if [ ! -d "$basedir" ]; then
echo "INFO: Snapshot directory $basedir does not exist. Creating it."
mkdir -p "$basedir"
fi
if ! btrfs subvolume show "$snapshot" 1>/dev/null; then
echo "INFO: Snapshot subvolume $snapshot does not exist. Creating it."
btrfs subvolume create "$snapshot"
fi
fi
${concatStringsSep "\n" (map (subvolume:
''
subvol="${subvolume.target}"
basedir="$(dirname ${subvolume.target})"
if [ ! -d "$basedir" ]; then
echo "INFO: Base directory $basedir does not exist. Creating it."
mkdir -p "$basedir"
fi
if ! btrfs subvolume show "$subvol" 1>/dev/null; then
echo "INFO: Subvolume $subvol does not exist. Creating it."
btrfs subvolume create "$subvol"
fi
''
) volume.subvolumes)}
''
) cfg.volumes)}
'';
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.btrbk}/sbin/btrbk -c ${btrbkConfig} run";
# Set Nice and IONice so that backups are performed
# with minimal noticable impact to the machines resources
Nice = 19;
IOSchedulingClass = "idle";
};
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment