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.
This commit is contained in:
2026-09-16 09:46:01 +02:00
parent e227e27ec8
commit 457db3934c

14
apt-zfs-snapshot.sh Normal file → Executable file
View File

@@ -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