Scheduled Postgres backup, restore check, backup warnings in health, payment result logging
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,43 @@ def _ai_provider_state() -> dict:
|
||||
return {'state': row['new_status'], 'since': row['created_at'].isoformat()}
|
||||
|
||||
|
||||
def _backup_warnings() -> list[str]:
|
||||
"""بکاپِ روزانه، کپیِ بیرونی و آزمونِ بازیابی؛ فقط وقتی Postgres است (SQLite یعنی توسعهی محلی)."""
|
||||
from datetime import datetime, timezone as dt_tz
|
||||
|
||||
from statuslog.backup import read_status
|
||||
|
||||
if settings.DEBUG or connection.vendor != 'postgresql':
|
||||
return []
|
||||
st = read_status()
|
||||
now = datetime.now(dt_tz.utc)
|
||||
|
||||
def age_hours(iso: str):
|
||||
return (now - datetime.fromisoformat(iso)).total_seconds() / 3600 if iso else None
|
||||
|
||||
out = []
|
||||
if st.get('corrupt'):
|
||||
return ['status.json بکاپ خوانده نشد؛ لاگ را ببینید.']
|
||||
age = age_hours(st.get('last_ok_at'))
|
||||
if age is None:
|
||||
out.append('هیچ بکاپِ زمانبندیشدهای ثبت نشده (manage.py backup_db با cron اجرا نمیشود).')
|
||||
elif age > settings.BACKUP_MAX_AGE_HOURS:
|
||||
out.append(f'آخرین بکاپِ موفق {age:.0f} ساعت پیش بود (سقف {settings.BACKUP_MAX_AGE_HOURS}).')
|
||||
if st.get('last_error'):
|
||||
out.append(f"آخرین تلاشِ بکاپ شکست خورد: {st['last_error'][:200]}")
|
||||
if (st.get('offsite') or {}).get('state') != 'ok':
|
||||
out.append('کپیِ بکاپ بیرون از این ماشین نیست یا شکست خورده (BACKUP_OFFSITE_CMD).')
|
||||
rc = st.get('restore_check') or {}
|
||||
rc_age = age_hours(rc.get('at'))
|
||||
if rc_age is None:
|
||||
out.append('بازیابیِ بکاپ هنوز آزموده نشده (manage.py backup_verify).')
|
||||
elif not rc.get('ok'):
|
||||
out.append('آخرین آزمونِ بازیابی ناهمخوان بود.')
|
||||
elif rc_age > settings.RESTORE_CHECK_MAX_AGE_DAYS * 24:
|
||||
out.append(f'آخرین آزمونِ بازیابی {rc_age / 24:.0f} روز پیش بود.')
|
||||
return out
|
||||
|
||||
|
||||
def _warnings() -> list[str]:
|
||||
"""
|
||||
چیزهایی که خرابی نیستند ولی روی یک سرورِ عمومی خطرناکاند.
|
||||
@@ -182,6 +219,7 @@ def _warnings() -> list[str]:
|
||||
'اعتبارِ سرویسِ هوش مصنوعی (GapGPT) تمام شده: دستیار برای همهی کاربرها '
|
||||
'«در دسترس نیست» میدهد تا حساب شارژ شود.'
|
||||
)
|
||||
out.extend(_backup_warnings())
|
||||
backend = settings.CACHES['default']['BACKEND']
|
||||
if not settings.DEBUG and 'LocMem' in backend:
|
||||
out.append('کش محلیِ هر پروسه است؛ محدودیتِ نرخ بینِ workerها مشترک نیست.')
|
||||
|
||||
@@ -969,6 +969,12 @@ LOGGING = {
|
||||
# Promtail بتونه tail کنه. بدونش (اجرای عادی خارج از Docker)، فقط stdout.
|
||||
LOG_DIR = os.environ.get('LOG_DIR', '')
|
||||
|
||||
# بکاپِ زمانبندیشده (statuslog/backup.py). پیشفرض داخلِ همان پوشهی gitignoreشدهی deploy.sh است؛
|
||||
# مقصدِ بیرونِ ماشین جداست: BACKUP_OFFSITE_CMD در .env (ستنبودنش را /api/health هشدار میدهد).
|
||||
BACKUP_DIR = os.environ.get('BACKUP_DIR', str(BASE_DIR / '.deploy-backups' / 'scheduled'))
|
||||
BACKUP_MAX_AGE_HOURS = 26 # روزانه + دو ساعت جا؛ دیرتر از این یعنی cron نخورده
|
||||
RESTORE_CHECK_MAX_AGE_DAYS = 35
|
||||
|
||||
if LOG_DIR:
|
||||
os.makedirs(LOG_DIR, exist_ok=True)
|
||||
LOGGING['handlers']['file'] = {
|
||||
|
||||
Reference in New Issue
Block a user