backup/stages/snapshot-btrfs.sh

#!/usr/bin/env bash
# backup/stages/snapshot-btrfs.sh
# Take read-only btrfs snapshots of the subvolumes in SUBVOLS[], and
# prune to KEEP newest per subvolume.

set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
. "${HERE}/../../scripts/lib/log.sh"

SNAP_ROOT="${SNAP_ROOT:-/mnt/tank/.snapshots}"
KEEP="${KEEP:-30}"
TS="$(date +%Y%m%dT%H%M%S)"

SUBVOLS=(
    /mnt/tank/srv
    /mnt/tank/photos
    /mnt/tank/docs
    /mnt/tank/vaultwarden
)

have_btrfs() {
    command -v btrfs >/dev/null 2>&1
}

snapshot_one() {
    local src="$1"
    local name; name=$(basename "${src}")
    local dest="${SNAP_ROOT}/${name}/${TS}"
    install -d "${SNAP_ROOT}/${name}"
    if btrfs subvolume snapshot -r "${src}" "${dest}" >/dev/null; then
        log_info "snap ${name} -> ${dest}"
    else
        log_err "snap failed for ${name}"
        return 1
    fi
}

prune_one() {
    local subvol_dir="$1"
    local count; count=$(find "${subvol_dir}" -maxdepth 1 -mindepth 1 -type d | wc -l)
    if (( count <= KEEP )); then
        return 0
    fi
    find "${subvol_dir}" -maxdepth 1 -mindepth 1 -type d \
        | sort \
        | head -n $((count - KEEP)) \
        | while read -r old; do
              log_info "prune ${old}"
              btrfs subvolume delete "${old}" >/dev/null || log_err "prune failed ${old}"
          done
}

main() {
    if ! have_btrfs; then
        log_err "btrfs-progs not installed"
        exit 2
    fi
    install -d "${SNAP_ROOT}"
    for sv in "${SUBVOLS[@]}"; do
        snapshot_one "${sv}"
        prune_one "${SNAP_ROOT}/$(basename "${sv}")"
    done
}

main "$@"