diff --git a/personal/cron_scripts/clean_walfiles.sh b/personal/cron_scripts/clean_walfiles.sh new file mode 100644 index 0000000..9967925 --- /dev/null +++ b/personal/cron_scripts/clean_walfiles.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ARCHIVE_DIR="/var/lib/postgresql_archive/18/main/pg_wal" +MIN_AGE_DAYS=7 +DRY_RUN=1 + +usage() { + cat <<'EOF' +Usage: clean_walfiles.sh [--apply] [--archive-dir PATH] [--min-age-days N] + +Cleans PostgreSQL archive WAL files using both rules: +1) Candidate file mtime is older than the oldest *.backup file mtime +2) Candidate file mtime is older than N days (default: 7) + +Defaults to dry-run. Use --apply to actually delete files. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) + DRY_RUN=0 + shift + ;; + --archive-dir) + ARCHIVE_DIR="$2" + shift 2 + ;; + --min-age-days) + MIN_AGE_DAYS="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage + exit 2 + ;; + esac +done + +if [[ ! -d "$ARCHIVE_DIR" ]]; then + echo "Archive directory does not exist: $ARCHIVE_DIR" >&2 + exit 1 +fi + +if ! [[ "$MIN_AGE_DAYS" =~ ^[0-9]+$ ]]; then + echo "--min-age-days must be a non-negative integer" >&2 + exit 2 +fi + +mapfile -d '' backup_files < <(find "$ARCHIVE_DIR" -maxdepth 1 -type f -name '*.backup' -print0) + +if [[ ${#backup_files[@]} -eq 0 ]]; then + echo "No .backup files found in $ARCHIVE_DIR; nothing to do." + exit 0 +fi + +oldest_backup="" +oldest_backup_ts="" + +for backup_file in "${backup_files[@]}"; do + backup_ts=$(stat -c %Y "$backup_file") + if [[ -z "$oldest_backup_ts" || "$backup_ts" -lt "$oldest_backup_ts" ]]; then + oldest_backup_ts="$backup_ts" + oldest_backup="$backup_file" + fi +done + +age_cutoff_ts=$(date -d "$MIN_AGE_DAYS days ago" +%s) + +echo "Archive dir : $ARCHIVE_DIR" +echo "Oldest .backup file : $oldest_backup" +echo "Oldest .backup mtime: $oldest_backup_ts" +echo "Age cutoff (epoch) : $age_cutoff_ts" + +declare -a files_to_delete=() + +while IFS= read -r -d '' file; do + file_ts=$(stat -c %Y "$file") + + # Delete only when both constraints are true. + if [[ "$file_ts" -lt "$oldest_backup_ts" && "$file_ts" -lt "$age_cutoff_ts" ]]; then + files_to_delete+=("$file") + fi +done < <(find "$ARCHIVE_DIR" -maxdepth 1 -type f ! -name '*.backup' -print0) + +if [[ ${#files_to_delete[@]} -eq 0 ]]; then + echo "No files matched cleanup criteria." + exit 0 +fi + +echo "" +echo "Files matching cleanup criteria (${#files_to_delete[@]}):" +for file in "${files_to_delete[@]}"; do + echo " $file" +done + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "" + echo "Dry-run mode: no files were deleted." + echo "Re-run with --apply to delete the files above." + exit 0 +fi + +deleted=0 +for file in "${files_to_delete[@]}"; do + rm -f -- "$file" + ((deleted += 1)) +done + +echo "" +echo "Deleted $deleted file(s)."