Live off-site Postgres copy on 247 replaces the nightly pg_dump; backup retention in deploy.sh

- ops/pgbackup: SSH tunnel + pg_receivewal (slot zareian_247) + daily verified pg_basebackup (keep 7)
  and a restore check, all run on 78.38.35.247 as systemd units
- /api/health reports offsiteBackup (slot active/lag/lost) and warns; OFFSITE_WAL_SLOT setting
- removed statuslog backup_db/backup_verify and their health warnings
- deploy.sh keeps 3 pre-deploy dumps, 3 local patches, drops manual backups after 30 days
- docs: operations backup section, security item 14 closed, CLAUDE.md status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 16:53:10 +03:30
parent 19072ee534
commit a9cccc6550
22 changed files with 374 additions and 447 deletions

51
ops/pgbackup/restore-check.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# آزمونِ بازیابی روی خودِ ۲۴۷: آخرین نسخه‌ی کامل + همه‌ی WALِ رسیده را در پوشه‌ی موقت بالا می‌آورد
# (Postgresِ موقت روی سوکتِ داخلِ همان پوشه، بی‌پورتِ شبکه؛ به Postgresِ دیگرانِ این ماشین دست نمی‌زند)،
# شمارشِ جدول‌های کاربر/ورود/پرداخت و آخرین زمان‌ها را چاپ می‌کند و همه‌چیز را پاک می‌کند.
# همان پرسش را روی ۲۴۹ بزنید و مقایسه کنید. با آرگومان، تا یک لحظه برمی‌گردد:
# restore-check.sh '2026-10-10 16:30:00+03:30'
# با KEEP=1 پوشه و Postgresِ موقت می‌مانند (برای بازیابیِ واقعی: از آن pg_dump بگیرید، بعد pg_ctl stop و rm).
set -euo pipefail
ROOT=/home/administrator/zareian-pgbackup
BIN=/usr/lib/postgresql/14/bin
TARGET="${1:-}"
latest=$(ls -1d "$ROOT"/base/2* | tail -1)
work=$(mktemp -d "$ROOT/restore-check.XXXXXX")
if [ "${KEEP:-0}" != 1 ]; then
trap '"$BIN/pg_ctl" -D "$work/data" -m immediate stop >/dev/null 2>&1 || true; rm -rf "$work"' EXIT
fi
cp -a "$latest" "$work/data"
mkdir "$work/wal"
cp "$ROOT"/wal/* "$work/wal/"
# فایلِ در حالِ نوشتن (.partial) هم داده‌ی واقعی است؛ برای بازیابی نامِ کامل می‌گیرد.
for f in "$work"/wal/*.partial; do [ -e "$f" ] && mv "$f" "${f%.partial}"; done
# postgresql.conf و pg_hba.conf روی اوبونتو در /etc اند، نه در پوشه‌ی داده؛ برای نمونه‌ی موقت ساخته می‌شوند.
cat > "$work/data/postgresql.conf" <<CONF
listen_addresses = ''
unix_socket_directories = '$work'
restore_command = 'cp $work/wal/%f %p'
recovery_target_action = 'promote'
${TARGET:+recovery_target_time = '$TARGET'}
CONF
echo 'local all all trust' > "$work/data/pg_hba.conf"
: > "$work/data/postgresql.auto.conf"
touch "$work/data/recovery.signal"
"$BIN/pg_ctl" -D "$work/data" -l "$work/log" -w -t 600 start >/dev/null
for _ in $(seq 120); do
[ "$("$BIN/psql" -h "$work" -U postgres -d zareian_mathapp -Atc 'select pg_is_in_recovery()')" = f ] && break
sleep 1
done
echo "base: $(basename "$latest") target: ${TARGET:-end of WAL}"
"$BIN/psql" -h "$work" -U postgres -d zareian_mathapp -c "
select 'users' what, count(*)::text n, max(created_at)::text latest from accounts_user
union all select 'login_events', count(*)::text, max(created_at)::text from accounts_loginevent
union all select 'orders', count(*)::text, max(created_at)::text from payment_order
union all select 'transactions', count(*)::text, max(created_at)::text from payment_transaction
union all select 'status_changes', count(*)::text, max(created_at)::text from statuslog_statuschangelog;"
[ "${KEEP:-0}" = 1 ] && echo "kept: $work (psql -h $work -U postgres -d zareian_mathapp)"
exit 0