diff --git a/CTFd/__init__.py b/CTFd/__init__.py index 03c708e2..3dc480cc 100644 --- a/CTFd/__init__.py +++ b/CTFd/__init__.py @@ -19,6 +19,7 @@ from CTFd.constants.themes import ADMIN_THEME, DEFAULT_THEME from CTFd.plugins import init_plugins from CTFd.utils.crypto import sha256 from CTFd.utils.initialization import ( + init_cli, init_events, init_logs, init_request_processors, @@ -312,5 +313,6 @@ def create_app(config="CTFd.config.Config"): init_logs(app) init_events(app) init_plugins(app) + init_cli(app) return app diff --git a/CTFd/cli/__init__.py b/CTFd/cli/__init__.py new file mode 100644 index 00000000..1886ac50 --- /dev/null +++ b/CTFd/cli/__init__.py @@ -0,0 +1,92 @@ +import datetime +import shutil +from pathlib import Path + +import click +from flask import Blueprint, current_app + +from CTFd.utils import get_config as get_config_util +from CTFd.utils import set_config as set_config_util +from CTFd.utils.config import ctf_name +from CTFd.utils.exports import export_ctf as export_ctf_util +from CTFd.utils.exports import import_ctf as import_ctf_util +from CTFd.utils.exports import set_import_end_time, set_import_error + +_cli = Blueprint("cli", __name__) + + +def jsenums(): + import json + import os + + from CTFd.constants import JS_ENUMS + + path = os.path.join(current_app.root_path, "themes/core/assets/js/constants.js") + + with open(path, "w+") as f: + for k, v in JS_ENUMS.items(): + f.write("const {} = Object.freeze({});".format(k, json.dumps(v))) + + +BUILD_COMMANDS = {"jsenums": jsenums} + + +@_cli.cli.command("get_config") +@click.argument("key") +def get_config(key): + print(get_config_util(key)) + + +@_cli.cli.command("set_config") +@click.argument("key") +@click.argument("value") +def set_config(key, value): + print(set_config_util(key, value).value) + + +@_cli.cli.command("build") +@click.argument("cmd") +def build(cmd): + cmd = BUILD_COMMANDS.get(cmd) + cmd() + + +@_cli.cli.command("export_ctf") +@click.argument("path", default="") +def export_ctf(path): + backup = export_ctf_util() + + if path: + with open(path, "wb") as target: + shutil.copyfileobj(backup, target) + else: + name = ctf_name() + day = datetime.datetime.now().strftime("%Y-%m-%d_%T") + full_name = f"{name}.{day}.zip" + + with open(full_name, "wb") as target: + shutil.copyfileobj(backup, target) + + print(f"Exported {full_name}") + + +@_cli.cli.command("import_ctf") +@click.argument("path", type=click.Path(exists=True)) +@click.option( + "--delete_import_on_finish", + default=False, + is_flag=True, + help="Delete import file when import is finished", +) +def import_ctf(path, delete_import_on_finish=False): + try: + import_ctf_util(path) + except Exception as e: + from CTFd.utils.dates import unix_time + + set_import_error("Import Failure: " + str(e)) + set_import_end_time(value=unix_time(datetime.datetime.utcnow())) + + if delete_import_on_finish: + print(f"Deleting {path}") + Path(path).unlink() diff --git a/CTFd/utils/initialization/__init__.py b/CTFd/utils/initialization/__init__.py index 9eacb0f8..5f38bf41 100644 --- a/CTFd/utils/initialization/__init__.py +++ b/CTFd/utils/initialization/__init__.py @@ -43,6 +43,12 @@ from CTFd.utils.user import ( ) +def init_cli(app): + from CTFd.cli import _cli + + app.register_blueprint(_cli, cli_group=None) + + def init_template_filters(app): app.jinja_env.filters["markdown"] = markdown app.jinja_env.filters["unix_time"] = unix_time diff --git a/manage.py b/manage.py index 7c93c3a6..234366ee 100644 --- a/manage.py +++ b/manage.py @@ -1,95 +1,11 @@ -import datetime -import shutil -from pathlib import Path - -from flask_migrate import MigrateCommand -from flask_script import Manager +from flask.cli import FlaskGroup from CTFd import create_app -from CTFd.utils import get_config as get_config_util -from CTFd.utils import set_config as set_config_util -from CTFd.utils.config import ctf_name -from CTFd.utils.exports import export_ctf as export_ctf_util -from CTFd.utils.exports import import_ctf as import_ctf_util -from CTFd.utils.exports import ( - set_import_end_time, - set_import_error, -) app = create_app() -manager = Manager(app) -manager.add_command("db", MigrateCommand) - - -def jsenums(): - from CTFd.constants import JS_ENUMS - import json - import os - - path = os.path.join(app.root_path, "themes/core/assets/js/constants.js") - - with open(path, "w+") as f: - for k, v in JS_ENUMS.items(): - f.write("const {} = Object.freeze({});".format(k, json.dumps(v))) - - -BUILD_COMMANDS = {"jsenums": jsenums} - - -@manager.command -def get_config(key): - with app.app_context(): - print(get_config_util(key)) - - -@manager.command -def set_config(key, value): - with app.app_context(): - print(set_config_util(key, value).value) - - -@manager.command -def build(cmd): - with app.app_context(): - cmd = BUILD_COMMANDS.get(cmd) - cmd() - - -@manager.command -def export_ctf(path=None): - with app.app_context(): - backup = export_ctf_util() - - if path: - with open(path, "wb") as target: - shutil.copyfileobj(backup, target) - else: - name = ctf_name() - day = datetime.datetime.now().strftime("%Y-%m-%d_%T") - full_name = f"{name}.{day}.zip" - - with open(full_name, "wb") as target: - shutil.copyfileobj(backup, target) - - print(f"Exported {full_name}") - - -@manager.command -def import_ctf(path, delete_import_on_finish=False): - with app.app_context(): - try: - import_ctf_util(path) - except Exception as e: - from CTFd.utils.dates import unix_time - - set_import_error(f"Import Failure: " + str(e)) - set_import_end_time(value=unix_time(datetime.datetime.utcnow())) - - if delete_import_on_finish: - print(f"Deleting {path}") - Path(path).unlink() +cli = FlaskGroup(app) if __name__ == "__main__": - manager.run() + cli()