diff --git a/.env.example b/.env.example index 56378a1..f62805c 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ DATA_DIR=/app/data # ─── رفتار ─── ROLLBACK_MINUTES=15 +TEST_ROLLBACK_MINUTES=2 AUTO_TEST_INTERVAL_MINUTES=30 FLASK_ENV=production diff --git a/README.md b/README.md index 5a6ae54..2c275fe 100644 --- a/README.md +++ b/README.md @@ -99,14 +99,21 @@ echo 'export PYTHONPATH=/opt/mirror-manager' | sudo tee /etc/profile.d/mirror-ma 1. به **https://mirror.itistan.ir** بروید 2. با `ADMIN_USERNAME` / `ADMIN_PASSWORD` وارد شوید 3. در **اولین ورود** backup اولیه خودکار گرفته می‌شود -4. یک **پروفایل** انتخاب و **اعمال** کنید -5. در پنجره rollback تست کنید → **تأیید** یا **rollback** +4. یک **پروفایل** انتخاب کنید → **تست موقت (۲ دقیقه)** بزنید +5. Dokploy و deploy را تست کنید → **اعمال نهایی** (نگه‌داشتن تغییرات) +6. اگر مشکل پیش آمد: `mirror-manager emergency-restore` از SSH --- ## CLI (از SSH) ```bash +# بازگردانی فوری — آخرین backup قبل از apply (توصیه در emergency) +mirror-manager emergency-restore + +# یا معادل: +mirror-manager restore --last + # بازگردانی تنظیمات اولیه (قبل از نصب Mirror Manager) mirror-manager restore --initial diff --git a/app/__init__.py b/app/__init__.py index 2bba85c..d50a9fe 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -13,8 +13,12 @@ from app.routes.main import main_bp from app.routes.mirrors import mirrors_bp from app.routes.profiles import profiles_bp from app.seed import seed_all -from app.services.applier import ensure_initial_backup, restore_initial -from app.services.backup import restore_backup +from app.services.applier import ( + ensure_initial_backup, + emergency_restore_last_apply, + restore_initial, +) +from app.services.backup import get_latest_apply_backup, restore_backup from app.services.scheduler import init_scheduler @@ -87,17 +91,29 @@ def cli(): @cli.command() @click.option("--initial", is_flag=True, help="بازگردانی تنظیمات اولیه") +@click.option("--last", is_flag=True, help="آخرین backup قبل از apply") @click.option("--backup", default=None, help="مسیر backup مشخص") -def restore(initial, backup): +def restore(initial, backup, last): os.environ["DISABLE_SCHEDULER"] = "1" + if not os.environ.get("HOST_ROOT"): + os.environ.setdefault("HOST_ROOT", "/") + if not os.environ.get("DATA_DIR"): + os.environ.setdefault("DATA_DIR", "/var/lib/mirror-manager/data") app = create_app() with app.app_context(): if initial: result = restore_initial() + elif last: + path = get_latest_apply_backup() + if not path: + click.echo("backup قبل از apply یافت نشد.") + return + click.echo(f"بازگردانی از: {path}") + result = restore_backup(path) elif backup: result = restore_backup(backup) else: - click.echo("یکی از --initial یا --backup را مشخص کنید.") + click.echo("یکی از --initial، --last یا --backup را مشخص کنید.") return if result.get("success"): click.echo("بازگردانی موفق:") @@ -107,6 +123,25 @@ def restore(initial, backup): click.echo(f"خطا: {result.get('error')}") +@cli.command("emergency-restore") +def emergency_restore_cmd(): + """بازگردانی فوری آخرین backup قبل از apply — یک دستور""" + os.environ["DISABLE_SCHEDULER"] = "1" + if not os.environ.get("HOST_ROOT"): + os.environ.setdefault("HOST_ROOT", "/") + if not os.environ.get("DATA_DIR"): + os.environ.setdefault("DATA_DIR", "/var/lib/mirror-manager/data") + app = create_app() + with app.app_context(): + result = emergency_restore_last_apply() + if result.get("success"): + click.echo(f"بازگردانی فوری موفق از: {result.get('backup_path', '')}") + for item in result.get("restored", []): + click.echo(f" - {item}") + else: + click.echo(f"خطا: {result.get('error')}") + + @cli.command() def disable(): container = os.environ.get("MIRROR_CONTAINER_NAME", "mirror-manager") diff --git a/app/config.py b/app/config.py index 9846f44..32ab008 100644 --- a/app/config.py +++ b/app/config.py @@ -18,6 +18,7 @@ class Config: HOST_ROOT = os.environ.get("HOST_ROOT", "") ROLLBACK_MINUTES = int(os.environ.get("ROLLBACK_MINUTES", "15")) + TEST_ROLLBACK_MINUTES = int(os.environ.get("TEST_ROLLBACK_MINUTES", "2")) AUTO_TEST_INTERVAL_MINUTES = int(os.environ.get("AUTO_TEST_INTERVAL_MINUTES", "30")) RESOLVED_DROPIN = "mirror-manager.conf" diff --git a/app/guides.py b/app/guides.py index 4f0bd85..e0f6ccc 100644 --- a/app/guides.py +++ b/app/guides.py @@ -71,7 +71,8 @@ GUIDES = {
هشدار
-

با restart Docker، containerها (از جمله Dokploy) چند ثانیه قطع می‌شوند. بهتر است در ساعات کم‌ترافیک apply کنید.

+

با restart Docker، containerها (از جمله Dokploy) چند ثانیه قطع می‌شوند. همیشه ابتدا «تست موقت» را بزنید.

+

فقط کلیدهای رسمی Docker در daemon.json نوشته می‌شوند — کلیدهای نامعتبر باعث crash شدن Docker نمی‌شوند.

میرور پیش‌فرض