Files
app-backup/lib/space.sh
T

71 lines
3.6 KiB
Bash

#!/usr/bin/env bash
# Called only under the shared lock; --check never invokes this maintenance.
clean_backup_scratch() {
python3 "$LIB_DIR/space.py" clean "$WORKDIR" "$STAGING_ROOT" "$ARCHIVE_DIR" "$ARCHIVE_PREFIX" "$RESTORE_ROOT"
}
ensure_space() {
local path=$1 additional=${2:-0} avail name dir candidates
[[ $additional =~ ^(0|[1-9][0-9]{0,15})$ ]] || die 'Invalid or excessive space request'
local required=$((MIN_FREE_GB * 1024 * 1024 * 1024 + additional))
avail=$(df -PB1 -- "$path" | awk 'END{print $4}')
(( avail < required )) || return 0
echo "-- Space recovery: need $required bytes, available $avail on $path"
if [[ $ENABLE_UPLOAD == true && $(stat -c %d "$path") == "$(stat -c %d "$ARCHIVE_DIR")" ]]; then
check_remote
candidates=$(python3 "$LIB_DIR/space.py" eligible "$ARCHIVE_DIR" "$ARCHIVE_PREFIX" "$RETENTION_KEEP_LAST")
while IFS= read -r name; do
[[ -n $name ]] || continue
dir="$ARCHIVE_DIR/$name"
# A manually selected local restore source is never a cleanup candidate.
[[ -z ${LOCAL_RUN:-} || $(realpath -m "$LOCAL_RUN") != "$dir" ]] || continue
echo "-- Space recovery: verifying offsite copy before local removal: $name"
if helper verify "$dir" >/dev/null &&
remote_readback "$dir" "${RCLONE_REMOTE_BASE%/}/$name" --exclude /UPLOADED; then
python3 "$LIB_DIR/space.py" remove "$dir"
echo "-- Space recovery: removed local copy $name; verified offsite copy retained"
else
echo "WARNING: Keeping $name: local/offsite verification failed"
fi
avail=$(df -PB1 -- "$path" | awk 'END{print $4}')
(( avail < required )) || return 0
done <<< "$candidates"
fi
die "Insufficient space after safe cleanup on $path: need $required bytes, available $avail; protected backups/recovery data retained"
}
prepare_backup_space() {
if [[ -f $WORKDIR/restore-test/.owner ]]; then
local cleanup_script="$LIB_DIR/../usr-local-sbin/app-restore-test.sh"
[[ -f $cleanup_script ]] || cleanup_script=/usr/local/sbin/app-restore-test.sh
APP_BACKUP_INHERIT_LOCK=1 bash "$cleanup_script" --config "$CONFIG_FILE" --cleanup
fi
clean_backup_scratch
local i stats bytes count total=0 db_size
# Dry-run obeys the exact copy exclusions. No dump, copy or service stop.
for i in "${!LABELS[@]}"; do
check_source_path "${SOURCES[i]}"
exclusions "${LABELS[i]}"
stats=$(rsync -aHAX --dry-run --stats --numeric-ids "${EXCLUDES[@]}" -- "${SOURCES[i]}/" "$STAGING_ROOT/.space-estimate/" 8>&- 9>&-)
bytes=$(awk '/^Total file size:/ {gsub(/,/, "", $4); print $4}' <<< "$stats")
count=$(awk '/^Number of files:/ {gsub(/,/, "", $4); print $4}' <<< "$stats")
[[ $bytes =~ ^[0-9]+$ && $count =~ ^[0-9]+$ ]] || die 'Cannot estimate source allocation'
total=$((total + bytes + count * 8192))
done
for i in "${!DB_NAMES[@]}"; do
secure_file "${DB_CNFS[i]}"
db_size=$("$DB_CLIENT_BIN" --defaults-extra-file="${DB_CNFS[i]}" --database="${DB_NAMES[i]}" --batch --skip-column-names --execute='SELECT COALESCE(SUM(data_length+index_length),0) FROM information_schema.tables WHERE table_schema=DATABASE()')
[[ $db_size =~ ^[0-9]+$ ]] || die 'Cannot estimate database size'
total=$((total + db_size * 3 + 67108864))
done
# Account for SQL expansion, tar metadata and growth between inventory/copy.
total=$((total * 12 / 10 + 67108864))
echo "-- Estimated snapshot budget: $total bytes (+ archive overlap and free-space reserve)"
if [[ $(stat -c %d "$STAGING_ROOT") == "$(stat -c %d "$ARCHIVE_DIR")" ]]; then
ensure_space "$STAGING_ROOT" "$((total * 2))"
else
ensure_space "$STAGING_ROOT" "$total"
ensure_space "$ARCHIVE_DIR" "$total"
fi
}