Files
mirror/app/services/applier.py
T
2026-06-03 17:33:19 +03:30

143 lines
4.6 KiB
Python

from __future__ import annotations
import logging
from datetime import timedelta
from pathlib import Path
from app.config import Config
from app.extensions import db
from app.models import ApplyLog, Profile, SystemState
from app.models import utcnow
from app.services import apt, backup, dns, docker_svc, github, pip_npm
from app.services.host import run_on_host
logger = logging.getLogger(__name__)
APPLIERS = {
"dns": dns.apply_dns,
"apt": apt.apply_apt,
"docker": docker_svc.apply_docker,
"github": github.apply_github,
"pip": pip_npm.apply_pip,
"npm": pip_npm.apply_npm,
}
def ensure_initial_backup() -> SystemState:
state = SystemState.query.first()
if not state:
state = SystemState()
db.session.add(state)
db.session.commit()
if not state.initial_backup_done:
path = backup.create_initial_backup()
state.initial_backup_path = str(path)
state.initial_backup_done = True
db.session.commit()
logger.info("Initial backup created at %s", path)
return state
def apply_profile(profile: Profile, rollback_minutes: int | None = None) -> ApplyLog:
state = ensure_initial_backup()
rollback_minutes = rollback_minutes or int(Config.ROLLBACK_MINUTES)
previous_backup = backup.create_backup(f"before_apply_{profile.id}")
apply_log = ApplyLog(
profile_id=profile.id,
status="running",
backup_path=str(previous_backup),
previous_backup_path=state.initial_backup_path,
rollback_at=utcnow() + timedelta(minutes=rollback_minutes),
)
db.session.add(apply_log)
db.session.flush()
details: dict = {"steps": [], "profile": profile.name}
mirrors = profile.mirrors_by_category()
order = ["dns", "apt", "docker", "github", "pip", "npm"]
for category in order:
mirror = mirrors.get(category)
if not mirror:
continue
applier = APPLIERS.get(category)
if not applier:
continue
try:
result = applier(mirror)
details["steps"].append({"category": category, "mirror": mirror.name, **result})
except Exception as exc:
logger.exception("Apply failed for %s", category)
details["steps"].append(
{"category": category, "mirror": mirror.name, "success": False, "error": str(exc)}
)
failed = [s for s in details["steps"] if not s.get("success", True)]
if failed and apply_log.backup_path:
rollback_result = backup.restore_backup(apply_log.backup_path)
details["auto_rollback"] = rollback_result
apply_log.set_details(details)
apply_log.status = "failed" if failed else "pending_confirm"
apply_log.finished_at = utcnow()
state.current_profile_id = profile.id if not failed else state.current_profile_id
state.pending_apply_log_id = apply_log.id if not failed else None
db.session.commit()
return apply_log
def confirm_apply(apply_log_id: int) -> ApplyLog:
apply_log = ApplyLog.query.get_or_404(apply_log_id)
apply_log.confirmed = True
apply_log.status = "confirmed"
apply_log.rollback_at = None
state = SystemState.query.first()
if state:
state.pending_apply_log_id = None
db.session.commit()
return apply_log
def rollback_apply(apply_log_id: int) -> dict:
apply_log = ApplyLog.query.get_or_404(apply_log_id)
if not apply_log.backup_path:
return {"success": False, "error": "مسیر backup یافت نشد"}
result = backup.restore_backup(apply_log.backup_path)
apply_log.status = "rolled_back"
apply_log.finished_at = utcnow()
state = SystemState.query.first()
if state:
state.pending_apply_log_id = None
db.session.commit()
return result
def restore_initial() -> dict:
state = SystemState.query.first()
if not state or not state.initial_backup_path:
return {"success": False, "error": "backup اولیه یافت نشد"}
return backup.restore_backup(state.initial_backup_path)
def get_system_status() -> dict:
codename_result = run_on_host(["lsb_release", "-cs"])
version_result = run_on_host(["lsb_release", "-d"])
return {
"ubuntu_codename": codename_result.stdout.strip() if codename_result.returncode == 0 else "unknown",
"ubuntu_description": version_result.stdout.strip() if version_result.returncode == 0 else "unknown",
"dns": dns.read_current_dns(),
"apt": apt.read_current_apt(),
"docker": docker_svc.read_current_docker(),
"github": github.read_current_github(),
"host_root": Config.HOST_ROOT or "(local mode)",
}