From 457db3934c86eb3ad8e544409385db39c0c47c8f Mon Sep 17 00:00:00 2001 From: hygienic-books Date: Wed, 16 Sep 2026 09:46:01 +0200 Subject: [PATCH] fix(apt): cache snapshot collision checks Cache the complete snapshot-name listing once per hook invocation instead\nof querying ZFS separately for every dataset and counter candidate. This\nremoves the repeated full-pool scan that becomes expensive when recursive\nsnapshot histories are large.\n\nCache the operation suffix as well and return an explicit success status\nfrom snapshot-name generation so collision retries are unambiguous. Ignore\nAPT configuration-only records because they do not represent package data\nchanges requiring a new pre-install snapshot. --- apt-zfs-snapshot.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) mode change 100644 => 100755 apt-zfs-snapshot.sh diff --git a/apt-zfs-snapshot.sh b/apt-zfs-snapshot.sh old mode 100644 new mode 100755 index b33d028..5ae41fb --- a/apt-zfs-snapshot.sh +++ b/apt-zfs-snapshot.sh @@ -7,6 +7,8 @@ declare -r zfs_property='space.quico:auto-snapshot' declare -r zfs_property_value='true' declare -r zfs_max_name_length=255 +set -xv + if [[ -r "${config_file}" ]]; then # shellcheck disable=SC1090 source "${config_file}" @@ -33,6 +35,7 @@ mixed_suffix="${mixed_suffix:-mixd}" package_separator="${package_separator:-:}" declare -a packages operations +declare existing_snapshots print_msg() { local level="$1" message="$2" @@ -77,7 +80,9 @@ parse_apt_input() { read -r package old_version _old_arch direction new_version _new_arch _multiarch action <<<"${line}" [[ -n "${package}" ]] || continue - if [[ "${action}" == '**REMOVE**' ]]; then + if [[ "${action}" == '**CONFIGURE**' ]]; then + continue + elif [[ "${action}" == '**REMOVE**' ]]; then add_package "${package}" 'remove' elif [[ "${direction}" == '>' ]]; then add_package "${package}" 'downgrade' @@ -157,17 +162,18 @@ shorten_packages() { package_list="${value}" } -existing_snapshot() { zfs list -H -t snapshot -o name | grep -Fxq -- "$1"; } +existing_snapshot() { grep -Fxq -- "$1" <<<"${existing_snapshots}"; } make_snapshot_name() { local counter="$1" dataset base names=() for dataset in "${snappable_datasets[@]}"; do - base="${dataset}@${snapshot_prefix}${field_separator}${date_string}${field_separator}${counter}${field_separator}op:$(operation_suffix)${field_separator}sev:${severity}${field_separator}pkgs:${package_list}" + base="${dataset}@${snapshot_prefix}${field_separator}${date_string}${field_separator}${counter}${field_separator}op:${operation_suffix_result}${field_separator}sev:${severity}${field_separator}pkgs:${package_list}" (( ${#base} <= zfs_max_name_length )) || { print_msg ERR "Snapshot name exceeds ${zfs_max_name_length} characters: ${base}"; return 2; } names+=("${base}") existing_snapshot "${base}" && return 1 done + return 0 } prune_snapshots() { @@ -196,9 +202,11 @@ main() { (( ${#packages[@]} > 0 )) || { print_msg INFO 'No package actions received; skipping snapshot'; return 0; } get_datasets (( ${#snappable_datasets[@]} > 0 )) || { print_msg INFO "No eligible ZFS datasets; skipping snapshot"; return 0; } + existing_snapshots="$(zfs list -H -t snapshot -o name)" date_string="$(TZ="${timezone}" date +"${date_format}")" severity_and_packages shorten_packages + operation_suffix_result="$(operation_suffix)" while :; do make_snapshot_name "$((++counter))" case "$?" in