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()