نسخهٔ پشتیبان چه زمانی واقعاً قابل استفاده است؟

داشتن فایل فشرده به معنی داشتن نسخهٔ پشتیبان قابل بازیابی نیست. یک سایت وردپرسی معمولاً به پایگاه داده، فایل‌های بارگذاری‌شده، افزونه‌ها، قالب و تنظیمات وابسته است. اگر فقط یکی از این قسمت‌ها ذخیره شود، بازگردانی می‌تواند ناقص بماند. این آموزش یک مسیر روشن برای ثبت مجموعهٔ هماهنگ، تشخیص تغییر بایت‌ها و طراحی تمرین بازیابی ارائه می‌کند؛ خروجی ابزار کنترل تمامیت، جای اجرای سایت بازیابی‌شده را نمی‌گیرد.

پیش از اجرای نمونه، همهٔ مسیرهای نوشتن را در سایت آزمایش متوقف کنید. صفحهٔ تعمیرات به‌تنهایی کار زمان‌بندی‌شده، صف، واردسازی یا اتصال مستقیم بیرونی را متوقف نمی‌کند. گزینهٔ ثبت تراکنشی برای جدول‌های تراکنشی مفید است، اما هماهنگی فایل‌های بارگذاری‌شده و جدول‌های غیرتراکنشی را تضمین نمی‌کند. نام و زمان مجموعه، نسخهٔ برنامه و دامنهٔ توقف نوشتن را در گزارش عملیاتی خود ثبت کنید تا بعداً بدانید این فایل‌ها متعلق به کدام وضعیت‌اند.

ثبت یک مجموعهٔ هماهنگ و کنترل تمامیت فایل‌ها

ابزار پشتیبان‌گیری پوشه‌ای تازه بیرون از ریشهٔ عمومی سایت می‌سازد. پایگاه داده با ابزار خط فرمان وردپرس صادر و درخت فایل سایت جداگانه بایگانی می‌شود. سپس چکیدهٔ هر دو خروجی ثبت می‌شود. فایل تنظیمات می‌تواند رمز اتصال داشته باشد؛ مقصد باید خصوصی، دارای دسترسی محدود و در نسخهٔ انتقالی رمزگذاری‌شده باشد. روی ویندوز، علاوه بر مجوز فایل در نمونه، دسترسی پوشه را با مجوزهای واقعی سیستم بررسی کنید.

چهار فایل را کنار هم ذخیره و ابتدا python -m unittest test_backups.py را اجرا کنید. آزمون، خرابی عمدی یک فایل و جلوگیری از ایجاد پشتیبان داخل سایت را بررسی می‌کند. برای سایت آزمایشی منجمدشده، python backup.py /srv/staging/site /srv/private/backups/run-001 --writers-frozen را اجرا کنید. این دستور برای محیط دارای ابزار وردپرس و ابزارهای پایگاه داده است؛ از مسیرهای نمونه بدون تطبیق با سرور خود استفاده نکنید.

با python verify_backup.py /srv/private/backups/run-001 تمامیت را بررسی کنید. سپس ابزار restore_plan.py را با همان پوشه و یک مقصد تازه اجرا کنید. ابزار فقط برنامهٔ فرمان‌ها را چاپ می‌کند و چیزی استخراج یا وارد پایگاه داده نمی‌کند. فهرست بایگانی و پیوندهای نمادین را پیش از استخراج بازبینی کنید. بازیابی باید روی میزبان جدا، با پایگاه دادهٔ خالی و ارسال ایمیل، پرداخت و کارهای خروجی غیرفعال انجام شود.

کدهای کامل و قابل ذخیره

چهار فایل زیر را در یک پوشه ذخیره کنید. دستورهای اجرا و بررسی در متن آمده‌اند؛ هیچ کد با سه‌نقطه جایگزین نشده است.

1. ساخت مجموعهٔ خصوصی پشتیبان

نام فایل: backup.py؛ همهٔ سطرها را با همین نام ذخیره کنید.

"""Use a frozen staging site; destination must be outside the public site tree."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
import tarfile


def digest(path):
    with path.open('rb') as stream:
        return hashlib.file_digest(stream, 'sha256').hexdigest()


def create(site, destination):
    site, destination = Path(site).resolve(), Path(destination).resolve()
    if destination == site or site in destination.parents:
        raise ValueError('Backup must be outside the public site directory')
    destination.mkdir(parents=True, exist_ok=False, mode=0o700)
    # This does not stop external writers. Freeze cron, queues and imports first.
    subprocess.run(['wp', f'--path={site}', 'db', 'export', str(destination / 'database.sql'), '--single-transaction'], check=True)
    with tarfile.open(destination / 'site.tar.gz', 'w:gz', dereference=False) as archive:
        archive.add(site, arcname='site')
    files = ['database.sql', 'site.tar.gz']
    manifest = {name: digest(destination / name) for name in files}
    (destination / 'manifest.json').write_text(json.dumps(manifest, indent=2) + '\n', encoding='utf-8')
    for name in [*files, 'manifest.json']:
        os.chmod(destination / name, 0o600)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('site')
    parser.add_argument('destination')
    parser.add_argument('--writers-frozen', action='store_true', required=True)
    args = parser.parse_args()
    create(args.site, args.destination)

این فایل بخشی از مثال کامل بالاست؛ ترتیب اجرا، پیش‌نیازها و محدودیت‌های نتیجه در متن مقاله توضیح داده شده است.

2. تشخیص تغییر یا خرابی فایل

نام فایل: verify_backup.py؛ همهٔ سطرها را با همین نام ذخیره کنید.

import json
from pathlib import Path
import sys
from backup import digest


def verify(folder):
    folder = Path(folder)
    manifest = json.loads((folder / 'manifest.json').read_text(encoding='utf-8'))
    if set(manifest) != {'database.sql', 'site.tar.gz'}:
        raise ValueError('Unexpected backup contents')
    for name, expected in manifest.items():
        if (folder / name).stat().st_size == 0 or digest(folder / name) != expected:
            raise ValueError(f'Invalid backup file: {name}')
    return True


if __name__ == '__main__':
    verify(Path(sys.argv[1]))
    print('PASS: byte integrity only; a staging restore is still required.')

این فایل بخشی از مثال کامل بالاست؛ ترتیب اجرا، پیش‌نیازها و محدودیت‌های نتیجه در متن مقاله توضیح داده شده است.

3. چاپ برنامهٔ بازیابی روی مقصد جدا

نام فایل: restore_plan.py؛ همهٔ سطرها را با همین نام ذخیره کنید.

"""Print a plan for a NEW isolated staging site. Executes no shell commands."""
import argparse
from pathlib import Path
import shlex
from verify_backup import verify


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('backup')
    parser.add_argument('empty_staging_directory')
    args = parser.parse_args()
    source, target = Path(args.backup).resolve(), Path(args.empty_staging_directory).resolve()
    verify(source)
    if target.exists() or source == target or source in target.parents or target in source.parents:
        raise ValueError('Choose a new staging directory separate from the backup')
    q = lambda p: shlex.quote(str(p))
    print('Use an isolated host with mail, payments and outbound jobs disabled.')
    print('Review archive entries and symlinks before extracting trusted backup:')
    print(f'tar -tzf {q(source / "site.tar.gz")}')
    print(f'mkdir -p {q(target)}')
    print(f'tar -xzf {q(source / "site.tar.gz")} -C {q(target)}')
    print('Edit site/wp-config.php to use a NEW empty staging database, then:')
    print(f'wp --path={q(target / "site")} db import {q(source / "database.sql")}')
    print('Check login, Persian text, uploads, record counts and isolated test orders.')

این فایل بخشی از مثال کامل بالاست؛ ترتیب اجرا، پیش‌نیازها و محدودیت‌های نتیجه در متن مقاله توضیح داده شده است.

4. آزمون خرابی و مقصد غیرمجاز

نام فایل: test_backups.py؛ همهٔ سطرها را با همین نام ذخیره کنید.

import json
from pathlib import Path
import tempfile
import unittest
from backup import create, digest
from verify_backup import verify


class BackupTests(unittest.TestCase):
    def test_corruption_is_detected(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            for name in ('database.sql', 'site.tar.gz'):
                (root / name).write_bytes(b'test fixture')
            manifest = {name: digest(root / name) for name in ('database.sql', 'site.tar.gz')}
            (root / 'manifest.json').write_text(json.dumps(manifest), encoding='utf-8')
            self.assertTrue(verify(root))
            (root / 'database.sql').write_bytes(b'corrupted')
            with self.assertRaises(ValueError):
                verify(root)

    def test_backup_cannot_be_public(self):
        with tempfile.TemporaryDirectory() as directory:
            with self.assertRaises(ValueError):
                create(directory, Path(directory) / 'public-backup')


if __name__ == '__main__':
    unittest.main()

این فایل بخشی از مثال کامل بالاست؛ ترتیب اجرا، پیش‌نیازها و محدودیت‌های نتیجه در متن مقاله توضیح داده شده است.

آزمون بازیابی در محیط جدا و بدون پیام واقعی

پس از بازگردانی، ورود مدیر، متن فارسی، تصاویر، شمار رکوردها و یک سفارش آزمایشی را بررسی کنید. اگر نشانی سایت عوض شده است، جایگزینی آدرس باید با ابزار سازگار با دادهٔ سریال‌شده انجام شود. زمان لازم برای بازیابی و فاصلهٔ آخرین دادهٔ موجود را ثبت کنید. نگهداری چند نسخه در مقصد جدا مفید است، اما تنها یک تمرین موفق نشان می‌دهد در زمان حادثه چه مقدار کار و زمان برای بازگشت سرویس لازم دارید.

چک‌لیست اجرای عملی

  • نوشتن از مسیرهای وب، صف، زمان‌بندی و واردسازی متوقف شود.
  • پایگاه داده و فایل‌ها در یک مجموعه ذخیره شوند.
  • پشتیبان خصوصی باشد و از وب قابل دریافت نباشد.
  • بازیابی واقعی روی مقصد جدا تمرین شود.

منابع

تدوین و راهنمای عملی: دانشنامه لیان.