dots/modules/btrfs-rollback.nix

94 lines
3.0 KiB
Nix
Raw Normal View History

# TODO: Allow the rollback of multiple different subvolumes. Probably utilizing service templates (https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Service%20Templates)
{
lib,
config,
pkgs,
...
}:
let
cfg = config.services.btrfs-rollback;
in
{
options.services.btrfs-rollback = {
enable = lib.mkEnableOption "BTRFS Rollback on boot";
diskLabel = lib.mkOption {
description = "The disk with the given label to rollback";
type = lib.types.str;
};
subvolume = lib.mkOption {
description = "The subvolume to rollback";
type = lib.types.str;
};
snapshot = lib.mkOption {
description = "The base snapshot to restore on rollback";
type = lib.types.str;
};
searchAttempts = lib.mkOption {
description = ''
The number of attempts that should be made to locate the labeled disk.
This may need to be increased if the given disk does in fact exist, but the rollback is
failing.
'';
type = lib.types.ints.unsigned;
default = 50;
};
};
config = lib.mkIf (cfg.enable && config.boot.initrd.systemd.enable) {
boot.initrd.systemd = {
initrdBin = with pkgs; [
libuuid
gawk
];
services.rollback = {
description = "Rollback btrfs root subvolume";
wantedBy = [ "initrd.target" ];
before = [ "sysroot.mount" ];
after = [ "initrd-root-device.target" ];
unitConfig.DefaultDependencies = "no";
serviceConfig.Type = "oneshot";
script = # bash
''
mkdir -p /mnt
DISK_LABEL="${cfg.diskLabel}"
FOUND_DISK=0
SEARCH_ATTEMPTS="${builtins.toString cfg.searchAttempts}"
printf "Attempting to find disk with label '%s'\n" "$DISK_LABEL"
while ((SEARCH_ATTEMPTS > 0)); do
if findfs LABEL="$DISK_LABEL"; then
FOUND_DISK=1
printf "Found disk!\n"
break;
fi
((SEARCH_ATTEMPTS--))
sleep .1
printf "Remaining disk discovery attempts: %s\n" "$SEARCH_ATTEMPTS"
done
if (( FOUND_DISK == 0 )); then
printf "Discovery of disk with label '%s' failed! Cannot rollback!\n" "$DISK_LABEL" >&2
exit 1
fi
mount -t btrfs -o subvol=/ $(findfs LABEL="$DISK_LABEL") /mnt
btrfs subvolume list -to "/mnt/${cfg.subvolume}" \
| awk 'NR>2 { printf $4"\n" }' \
| while read subvol; do
printf "Removing Subvolume: %s\n" "$subvol";
btrfs subvolume delete "/mnt/$subvol"
done
printf "Removing ${cfg.subvolume} subvolume\n"
btrfs subvolume delete "/mnt/${cfg.subvolume}"
printf "Restoring base ${cfg.subvolume} subvolume from snapshot ${cfg.snapshot}\n"
btrfs subvolume snapshot "/mnt/${cfg.snapshot}" "/mnt/${cfg.subvolume}"
umount /mnt
'';
};
};
};
}