Initial commit
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
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)",
|
||||
}
|
||||
Reference in New Issue
Block a user