diff --git a/build_hm.sh b/build_hm.sh index b675d9d..08a672b 100755 --- a/build_hm.sh +++ b/build_hm.sh @@ -17,7 +17,7 @@ function help { echo " -h: Display this help message." } -while getopts "hvb" OPTION +while getopts "h" OPTION do case "$OPTION" in h) diff --git a/curacao/backup/backup.sh b/curacao/backup/backup.sh new file mode 100755 index 0000000..38448c2 --- /dev/null +++ b/curacao/backup/backup.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +# Parse arguments +function help { + echo "Usage: $0 [-h|-i] volume" + echo "Backup BTRFS subvolume on rapido to razmo." + echo + echo "Arguments:" + echo " volume: Name of the subvolume to backup" + echo + echo "Options:" + echo " -h: Display this help message." + echo " -i: Don't fail if the receiving subvolume doesn't exist." +} + +init=false +while getopts "hi" OPTION +do + case "$OPTION" in + h) + help + exit 0 + ;; + i) + init=true + ;; + ?) + help + exit 2 + ;; + esac +done +shift "$((OPTIND - 1))" + +if [ "$#" -ne 1 ] +then + help + exit 2 +fi +volume="$1" + +# Assertions +[ -d "/mnt/rapido/${volume}" ] +[ -d "/mnt/rapido/${volume}.bkp" ] || "$init" +[ ! -d "/mnt/rapido/${volume}.new" ] +[ -d "/mnt/razmo/${volume}.bkp" ] || "$init" +[ -d "/mnt/razmo/${volume}" ] || "$init" +[ ! -d "/mnt/razmo/${volume}.new" ] +[ ! -d "/mnt/razmo/${volume}.snapshots" ] + +# Taking a snapshot of the running subvolume +btrfs subvolume snapshot -r "/mnt/rapido/${volume}" "/mnt/rapido/${volume}.new" + +# Sending (the difference with) the last backup to the backup disk +function error_handler() { + btrfs subvolume delete "/mnt/rapido/${volume}.new" || true + btrfs subvolume delete "/mnt/razmo/${volume}.new" || true +} +trap error_handler ERR +if [ -d "/mnt/rapido/${volume}.bkp" ] +then + btrfs send -p "/mnt/rapido/${volume}.bkp" "/mnt/rapido/${volume}.new" | btrfs receive /mnt/razmo +else + btrfs send "/mnt/rapido/${volume}.new" | btrfs receive /mnt/razmo +fi +trap - ERR + +# Removing old backups and putting the new one in place +[ ! -d "/mnt/rapido/${volume}.bkp" ] || btrfs subvolume delete "/mnt/rapido/${volume}.bkp" +mv "/mnt/rapido/${volume}.new" "/mnt/rapido/${volume}.bkp" +[ ! -d "/mnt/razmo/${volume}.bkp" ] || btrfs subvolume delete "/mnt/razmo/${volume}.bkp" +mv "/mnt/razmo/${volume}.new" "/mnt/razmo/${volume}.bkp" + +# Create a writeable clone in case we need to boot on the HDD +# Needs to move away then back the .snapshots folder +[ ! -d "/mnt/razmo/${volume}/.snapshots" ] || mv "/mnt/razmo/${volume}/.snapshots" "/mnt/razmo/${volume}.snapshots" +[ ! -d "/mnt/razmo/${volume}" ] || btrfs subvolume delete "/mnt/razmo/${volume}" +btrfs subvolume snapshot "/mnt/razmo/${volume}.bkp" "/mnt/razmo/${volume}" +[ ! -d "/mnt/razmo/${volume}.snapshots" ] || mv "/mnt/razmo/${volume}.snapshots" "/mnt/razmo/${volume}/.snapshots" + +sync diff --git a/curacao/backup/default.nix b/curacao/backup/default.nix new file mode 100644 index 0000000..5041c60 --- /dev/null +++ b/curacao/backup/default.nix @@ -0,0 +1,64 @@ +{ pkgs, lib, ... }: +# MANU Snapper is not able to create the snapshot directory, so you'll need to do this after eventually running the backup script: +# sudo btrfs subvol create /mnt/razmo/$subvolume/.snapshots +let + backup_subvolumes = [ "nixos" "home.rapido" ]; + backup_app = pkgs.writeShellApplication { + name = "backup-subvolume"; + runtimeInputs = with pkgs; [ coreutils btrfs-progs ]; + text = builtins.readFile ./backup.sh; + }; + snapper_subvolumes = [ "nixos" "home.rapido" "home.razmo" ]; +in +{ + services = + let + default = { + # filesystem type + FSTYPE = "btrfs"; + + # run daily number cleanup + NUMBER_CLEANUP = true; + NUMBER_MIN_AGE = 1800; + NUMBER_LIMIT = 25; + + # create hourly snapshots + TIMELINE_CREATE = true; + + # cleanup hourly snapshots after some time + TIMELINE_CLEANUP = true; + TIMELINE_MIN_AGE = 1800; + TIMELINE_LIMIT_HOURLY = 24; + TIMELINE_LIMIT_DAILY = 31; + TIMELINE_LIMIT_WEEKLY = 8; + TIMELINE_LIMIT_MONTHLY = 0; + TIMELINE_LIMIT_YEARLY = 0; + + # cleanup empty pre-post-pairs + EMPTY_PRE_POST_CLEANUP = true; + }; + in + { + snapper.configs = lib.attrsets.mergeAttrsList (map (s: { "${s}" = default // { SUBVOLUME = "/mnt/razmo/${s}"; }; }) snapper_subvolumes); + }; + + + systemd = { + services.bkp_rapido = { + description = "Make a snapshot of the SSD to the HDD"; + before = [ "snapper-timeline.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = map (s: "${backup_app}/bin/backup-subvolume ${s}") backup_subvolumes; + }; + # TODO Harden + }; + timers.bkp_rapido = { + description = "Regular snapshot of SSD to HDD"; + timerConfig = { + OnCalendar = "hourly"; + }; + wantedBy = [ "timers.target" ]; + }; + }; +} diff --git a/curacao/dk.nix b/curacao/dk.nix index fa2660b..62323c8 100644 --- a/curacao/dk.nix +++ b/curacao/dk.nix @@ -1,5 +1,4 @@ { passwordFile ? "/should_not_be_needed_in_this_context", ... }: -# FIXME Subvolumes for backup. If they're not created with the script. Add the script btw. # TODO Not relatime everywhere, thank you # TODO Default options let diff --git a/curacao/os.nix b/curacao/os.nix index b92c72c..9e9d2ea 100644 --- a/curacao/os.nix +++ b/curacao/os.nix @@ -5,6 +5,7 @@ ./options.nix ./hardware.nix ./dk.nix + ./backup ]; networking.hostName = "curacao";