Files
mirror/app/__init__.py
T
mohammadian7 63d0699cdd Fix Docker apply crash and add safe profile test flow
Remove invalid _mirror_manager from daemon.json that prevented Docker from starting. Add 2-minute test apply with auto-rollback, final apply confirmation, emergency-restore CLI/UI, and post-restart Docker health checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 20:20:10 +03:30

185 lines
5.8 KiB
Python

import os
import click
from flask import Flask
from flask.cli import with_appcontext
from app.config import Config
from app.extensions import db, login_manager
from app.models import AdminUser
from app.routes.apply import apply_bp, restore_bp, settings_bp
from app.routes.auth import auth_bp
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,
emergency_restore_last_apply,
restore_initial,
)
from app.services.backup import get_latest_apply_backup, restore_backup
from app.services.scheduler import init_scheduler
def create_app(config_class=Config) -> Flask:
app = Flask(__name__)
app.config.from_object(config_class)
Config.ensure_dirs()
db.init_app(app)
login_manager.init_app(app)
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(mirrors_bp)
app.register_blueprint(profiles_bp)
app.register_blueprint(apply_bp)
app.register_blueprint(restore_bp)
app.register_blueprint(settings_bp)
with app.app_context():
db.create_all()
seed_all()
_ensure_admin_user()
ensure_initial_backup()
if not app.config.get("TESTING") and not os.environ.get("DISABLE_SCHEDULER"):
init_scheduler(app)
_register_cli(app)
return app
def _ensure_admin_user() -> None:
if AdminUser.query.first():
return
user = AdminUser(username=Config.ADMIN_USERNAME)
user.set_password(Config.ADMIN_PASSWORD)
db.session.add(user)
db.session.commit()
def _register_cli(app: Flask) -> None:
@app.cli.command("init-db")
@with_appcontext
def init_db():
db.create_all()
seed_all()
_ensure_admin_user()
ensure_initial_backup()
click.echo("Database initialized.")
@app.cli.command("create-admin")
@click.argument("username")
@click.argument("password")
@with_appcontext
def create_admin(username, password):
user = AdminUser.query.filter_by(username=username).first()
if not user:
user = AdminUser(username=username)
db.session.add(user)
user.set_password(password)
db.session.commit()
click.echo(f"Admin user '{username}' ready.")
@click.group()
def cli():
"""Mirror Manager CLI — قابل استفاده از SSH"""
@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, 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، --last یا --backup را مشخص کنید.")
return
if result.get("success"):
click.echo("بازگردانی موفق:")
for item in result.get("restored", []):
click.echo(f" - {item}")
else:
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")
import subprocess
result = subprocess.run(["docker", "stop", container], capture_output=True, text=True)
if result.returncode == 0:
click.echo(f"سرویس {container} متوقف شد.")
else:
click.echo(f"خطا: {result.stderr or result.stdout}")
click.echo("دستی: docker stop mirror-manager")
@cli.command()
def enable():
container = os.environ.get("MIRROR_CONTAINER_NAME", "mirror-manager")
import subprocess
result = subprocess.run(["docker", "start", container], capture_output=True, text=True)
if result.returncode == 0:
click.echo(f"سرویس {container} راه‌اندازی شد.")
else:
click.echo(f"خطا: {result.stderr or result.stdout}")
click.echo("دستی: docker compose up -d")
@cli.command()
def status():
os.environ["DISABLE_SCHEDULER"] = "1"
app = create_app()
with app.app_context():
from app.services.applier import get_system_status
info = get_system_status()
click.echo(f"Ubuntu: {info.get('ubuntu_description')}")
click.echo(f"Host root: {info.get('host_root')}")
if __name__ == "__main__":
cli()