#!/usr/bin/env python3
"""SecGate - 安全网关一体化管理工具

用法:
    secgate start          启动所有服务（网关 + 看板 + AI 助手）
    secgate stop           停止所有服务
    secgate restart        重启所有服务
    secgate status         查看运行状态
    secgate creds          查看登录凭证
    secgate setup          首次部署（安装依赖 + 配置 Nginx/iptables/UFW + systemd）
    secgate version        查看版本号
    secgate update         更新到最新版本
    secgate uninstall      卸载 SecGate 并清理所有系统配置
"""

import os
import sys
import json
import shutil
import signal
import subprocess
import time

PROJECT_DIR = os.path.dirname(os.path.realpath(__file__))
PID_FILE = os.path.join(PROJECT_DIR, ".pids.json")
sys.path.insert(0, PROJECT_DIR)


def load_pids():
    if os.path.exists(PID_FILE):
        with open(PID_FILE) as f:
            return json.load(f)
    return {}


def save_pids(pids):
    with open(PID_FILE, "w") as f:
        json.dump(pids, f)


def is_running(pid):
    try:
        os.kill(pid, 0)
        return True
    except (OSError, TypeError):
        return False


def get_public_ip():
    from shared import detect_public_ip
    return detect_public_ip()


def get_credentials():
    from shared import CREDENTIALS_FILE
    if os.path.exists(CREDENTIALS_FILE):
        with open(CREDENTIALS_FILE) as f:
            return json.load(f)
    return {}


def mask_secret(s):
    """掩码敏感信息：Se******6!"""
    if not s or len(s) < 5:
        return "******"
    return s[:2] + "******" + s[-2:]


# ============ 命令实现 ============

def cmd_start():
    """启动所有服务"""
    pids = load_pids()

    # 检查是否已在运行
    for name in ["gateway", "dashboard", "agent"]:
        if name in pids and is_running(pids[name]):
            print(f"  [跳过] {name} 已在运行 (PID: {pids[name]})")

    # Flask 服务列表 (name, script, log, port)
    flask_services = [
        ("gateway", os.path.join(PROJECT_DIR, "gateway", "app.py"), "/tmp/secgate-gateway.log", 5002),
        ("dashboard", os.path.join(PROJECT_DIR, "dashboard", "app.py"), "/tmp/secgate-dashboard.log", 5000),
    ]

    for name, script, log, port in flask_services:
        if name in pids and is_running(pids[name]):
            continue
        # 清理端口上残留的旧进程
        r = subprocess.run(["lsof", "-ti", f":{port}"], capture_output=True, text=True)
        if r.stdout.strip():
            for old_pid in r.stdout.strip().split("\n"):
                try:
                    os.kill(int(old_pid), signal.SIGKILL)
                except Exception:
                    pass
            time.sleep(1)
        proc = subprocess.Popen(
            ["python3", script],
            stdout=open(log, "a"),
            stderr=subprocess.STDOUT,
            cwd=os.path.dirname(script),
            start_new_session=True,
        )
        pids[name] = proc.pid
        print(f"  [启动] {name} (PID: {proc.pid}, 日志: {log})")

    # 启动 Chainlit AI 助手（可选，未安装 chainlit 则跳过）
    if "agent" not in pids or not is_running(pids.get("agent")):
        if shutil.which("chainlit"):
            agent_port = 8502
            agent_dir = os.path.join(PROJECT_DIR, "agent")
            agent_log = "/tmp/secgate-agent.log"
            # 清理残留进程
            r = subprocess.run(["lsof", "-ti", f":{agent_port}"], capture_output=True, text=True)
            if r.stdout.strip():
                for old_pid in r.stdout.strip().split("\n"):
                    try:
                        os.kill(int(old_pid), signal.SIGKILL)
                    except Exception:
                        pass
                time.sleep(1)
            env = os.environ.copy()
            env["DISABLE_CLAUDE_TELEMETRY"] = "1"
            env.pop("CLAUDECODE", None)
            # Chainlit 密码认证需要 JWT secret
            from shared import get_or_create_credential
            import secrets as _sec
            env["CHAINLIT_AUTH_SECRET"] = get_or_create_credential(
                "chainlit_auth_secret", lambda: _sec.token_hex(32)
            )
            proc = subprocess.Popen(
                ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", str(agent_port)],
                stdout=open(agent_log, "a"),
                stderr=subprocess.STDOUT,
                cwd=agent_dir,
                start_new_session=True,
                env=env,
            )
            pids["agent"] = proc.pid
            print(f"  [启动] agent (PID: {proc.pid}, 端口: {agent_port}, 日志: {agent_log})")
        else:
            print("  [跳过] agent (未安装 chainlit，AI 助手为可选功能)")

    save_pids(pids)
    time.sleep(2)

    # 显示信息
    ip = get_public_ip()
    creds = get_credentials()
    print()
    print("=" * 50)
    print("  SecGate 安全网关已启动")
    print("=" * 50)
    print(f"  看板地址:  http://{ip}:5000")
    print(f"  AI 助手:   http://{ip}:8502")
    print(f"  用户名:    admin")
    print(f"  密码:      {mask_secret(creds.get('dashboard_password', ''))}")
    print()
    print(f"  凭证文件:  {os.path.join(PROJECT_DIR, '.credentials.json')}")
    print("=" * 50)


def cmd_stop():
    """停止所有服务"""
    pids = load_pids()
    for name in ["agent", "dashboard", "gateway"]:
        pid = pids.get(name)
        if pid and is_running(pid):
            os.kill(pid, signal.SIGTERM)
            print(f"  [停止] {name} (PID: {pid})")
        else:
            print(f"  [跳过] {name} 未运行")
    save_pids({})


def cmd_restart():
    """重启所有服务"""
    print("[停止服务]")
    cmd_stop()
    time.sleep(2)
    print("\n[启动服务]")
    cmd_start()


def cmd_status():
    """查看运行状态"""
    pids = load_pids()
    print()
    for name in ["gateway", "dashboard", "agent"]:
        pid = pids.get(name)
        if pid and is_running(pid):
            print(f"  {name:12s}  运行中  PID={pid}")
        else:
            print(f"  {name:12s}  未运行")

    # 检查 Nginx
    r = subprocess.run(["pgrep", "nginx"], capture_output=True)
    nginx_ok = r.returncode == 0
    print(f"  {'nginx':12s}  {'运行中' if nginx_ok else '未运行'}")
    print()


def cmd_creds():
    """查看登录凭证"""
    creds = get_credentials()
    ip = get_public_ip()
    print()
    print("  访问地址:  http://{ip}:5000".format(ip=ip))
    print()
    print("  第一步 — 网关认证（非白名单 IP 需要）:")
    gw_config = os.path.join(PROJECT_DIR, "gateway", "config.json")
    if os.path.exists(gw_config):
        with open(gw_config) as f:
            cfg = json.load(f)
        tokens = cfg.get("tokens", {})
        if tokens:
            for t, info in tokens.items():
                print(f"  Token:     {t}")
                print(f"  名称:      {info.get('name', 'unnamed')}")
        else:
            print("  (暂无 Token，请在管理页面创建)")
    else:
        print("  (网关配置未初始化)")
    print()
    print("  第二步 — 看板登录:")
    print(f"  用户名:    admin")
    print(f"  密码:      {creds.get('dashboard_password', '未生成')}")
    print()


def cmd_version():
    """查看版本号"""
    version_file = os.path.join(PROJECT_DIR, "VERSION")
    if os.path.exists(version_file):
        with open(version_file) as f:
            version = f.read().strip()
    else:
        version = "未知"
    print(f"SecGate v{version}")


def cmd_update():
    """更新到最新版本"""
    print("[更新] 停止服务...")
    cmd_stop()

    # 如果是 git 仓库，执行 git pull
    if os.path.exists(os.path.join(PROJECT_DIR, ".git")):
        print("[更新] 拉取最新代码...")
        subprocess.run(["git", "pull"], cwd=PROJECT_DIR, check=True)

    # 更新依赖
    req_file = os.path.join(PROJECT_DIR, "requirements.txt")
    if os.path.exists(req_file):
        print("[更新] 更新 Python 依赖...")
        subprocess.run(["pip3", "install", "-q", "-r", req_file])

    print("[更新] 启动服务...")
    cmd_start()
    print("[更新] 完成!")


def cmd_set_apikey():
    """提示用户去 Dashboard 设置 LLM"""
    ip = get_public_ip()
    print()
    print("  AI 助手已改为通过 Dashboard 页面配置。")
    print()
    print(f"  请访问: http://{ip}:5000 > AI 安全 > 助手设置")
    print("  支持通义千问、DeepSeek、OpenAI 等 OpenAI 兼容 API。")
    print()


def cmd_uninstall():
    """卸载 SecGate 并清理所有系统配置"""
    # 确认提示
    if sys.stdin.isatty():
        answer = input("确定要卸载 SecGate 并清理所有系统配置吗？[y/N] ").strip()
        if answer.lower() != "y":
            print("已取消卸载。")
            return
    else:
        print("[错误] 卸载命令需要交互式终端确认，请直接运行: sudo secgate uninstall")
        sys.exit(1)

    print()
    print("=" * 50)
    print("  SecGate 卸载程序")
    print("=" * 50)

    # [1/8] 停止所有服务
    print("\n[1/8] 停止所有服务...")
    cmd_stop()
    # 同时停止 systemd 管理的服务
    for svc in ["secgate", "secgate-dashboard", "secgate-gateway"]:
        subprocess.run(["systemctl", "stop", svc], capture_output=True)

    # [2/8] 删除 systemd 服务文件
    print("\n[2/8] 清理 systemd 服务...")
    service_files = [
        "/etc/systemd/system/secgate.service",
        "/etc/systemd/system/secgate-dashboard.service",
        "/etc/systemd/system/secgate-gateway.service",
    ]
    for sf in service_files:
        if os.path.exists(sf):
            subprocess.run(["systemctl", "disable", os.path.basename(sf).replace(".service", "")], capture_output=True)
            os.remove(sf)
            print(f"  已删除 {sf}")
    subprocess.run(["systemctl", "daemon-reload"], capture_output=True)

    # [3/8] 清理 iptables 规则
    print("\n[3/8] 清理 iptables 规则...")
    # 检查 GATEWAY_AUTH 链是否存在
    r = subprocess.run(
        ["iptables", "-t", "nat", "-L", "GATEWAY_AUTH", "-n"],
        capture_output=True,
    )
    if r.returncode == 0:
        subprocess.run(["iptables", "-t", "nat", "-D", "PREROUTING", "-j", "GATEWAY_AUTH"], capture_output=True)
        subprocess.run(["iptables", "-t", "nat", "-F", "GATEWAY_AUTH"], capture_output=True)
        subprocess.run(["iptables", "-t", "nat", "-X", "GATEWAY_AUTH"], capture_output=True)
        print("  已清理 GATEWAY_AUTH 链")
    else:
        print("  GATEWAY_AUTH 链不存在，跳过")

    # [4/8] 清理 Nginx 配置
    print("\n[4/8] 清理 Nginx 配置...")
    from shared import NGINX_CONF_PATH, NGINX_ENABLED_PATH
    for p in [NGINX_ENABLED_PATH, NGINX_CONF_PATH]:
        if os.path.exists(p) or os.path.islink(p):
            os.remove(p)
            print(f"  已删除 {p}")
    # 如果 nginx 在运行则 reload
    r = subprocess.run(["pgrep", "nginx"], capture_output=True)
    if r.returncode == 0:
        subprocess.run(["nginx", "-s", "reload"], capture_output=True)
        print("  已重载 Nginx")

    # [5/8] 清理 UFW 规则
    print("\n[5/8] 清理 UFW 规则...")
    gw_config_path = os.path.join(PROJECT_DIR, "gateway", "config.json")
    ufw_ports_to_remove = ["5002"]
    if os.path.exists(gw_config_path):
        try:
            with open(gw_config_path) as f:
                cfg = json.load(f)
            for port_str, info in cfg.get("protected_ports", {}).items():
                ufw_ports_to_remove.append(port_str)
                nginx_port = str(info.get("nginx_port", int(port_str) + 20000))
                ufw_ports_to_remove.append(nginx_port)
        except Exception:
            pass
    for port in ufw_ports_to_remove:
        r = subprocess.run(["ufw", "delete", "allow", port + "/tcp"], capture_output=True)
        if r.returncode == 0:
            print(f"  已删除 UFW 规则: {port}/tcp")
    print("  已保留 22/tcp (SSH)")

    # [6/8] 清理 Fail2Ban 配置
    print("\n[6/8] 清理 Fail2Ban 配置...")
    f2b_jail_local = "/etc/fail2ban/jail.local"
    if os.path.exists(f2b_jail_local):
        os.remove(f2b_jail_local)
        print("  已删除 /etc/fail2ban/jail.local")
        subprocess.run(["systemctl", "restart", "fail2ban"], capture_output=True)
        print("  已重启 Fail2Ban")
    else:
        print("  jail.local 不存在，跳过")

    # [7/8] 询问是否保留数据
    keep_data = False
    if sys.stdin.isatty():
        answer = input("\n[7/8] 是否保留配置和数据文件？[Y/n] ").strip()
        keep_data = answer.lower() != "n"
    else:
        keep_data = True

    if keep_data:
        from datetime import datetime
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_dir = f"/tmp/secgate-backup-{timestamp}"
        os.makedirs(backup_dir, exist_ok=True)
        backup_items = [
            (os.path.join(PROJECT_DIR, ".credentials.json"), "credentials.json"),
            (os.path.join(PROJECT_DIR, "gateway", "config.json"), "gateway-config.json"),
        ]
        for src, dst_name in backup_items:
            if os.path.exists(src):
                shutil.copy2(src, os.path.join(backup_dir, dst_name))
        for dir_name in ["dashboard/data", "scanner/data"]:
            src_dir = os.path.join(PROJECT_DIR, dir_name)
            if os.path.isdir(src_dir):
                shutil.copytree(src_dir, os.path.join(backup_dir, dir_name.replace("/", "-")))
        print(f"  数据已备份到: {backup_dir}")
    else:
        print("\n[7/8] 跳过数据备份")

    # [8/8] 清理文件
    print("\n[8/8] 清理文件...")
    # 删除日志
    log_dir = "/var/log/secgate"
    if os.path.isdir(log_dir):
        shutil.rmtree(log_dir)
        print(f"  已删除 {log_dir}")
    # 删除临时日志
    import glob as _glob
    for f in _glob.glob("/tmp/secgate-*.log"):
        os.remove(f)
        print(f"  已删除 {f}")
    # 删除软链接
    symlink = "/usr/local/bin/secgate"
    if os.path.islink(symlink):
        os.remove(symlink)
        print(f"  已删除 {symlink}")
    # 删除 PID 文件
    if os.path.exists(PID_FILE):
        os.remove(PID_FILE)
    # pip uninstall（静默）
    subprocess.run(["pip", "uninstall", "secgate", "-y"], capture_output=True)

    # 判断安装方式并清理目录
    is_git_repo = os.path.exists(os.path.join(PROJECT_DIR, ".git"))
    if is_git_repo:
        print(f"\n  检测到 Git 仓库，不自动删除项目目录。")
        print(f"  如需删除请手动执行: rm -rf {PROJECT_DIR}")
    elif PROJECT_DIR.startswith("/opt/secgate"):
        shutil.rmtree(PROJECT_DIR, ignore_errors=True)
        print(f"  已删除 {PROJECT_DIR}")
    else:
        print(f"\n  项目目录: {PROJECT_DIR}")
        print(f"  如需删除请手动执行: rm -rf {PROJECT_DIR}")

    print()
    print("=" * 50)
    print("  SecGate 已卸载完成")
    if keep_data:
        print(f"  备份位置: {backup_dir}")
    print("=" * 50)
    print()


def cmd_setup():
    """首次部署：安装依赖 + 配置系统"""
    print("=" * 50)
    print("  SecGate 一键部署")
    print("=" * 50)

    # 1. 检测系统
    ip = get_public_ip()
    print(f"\n[1/6] 检测服务器 IP: {ip}")

    # 2. 安装系统依赖
    print("\n[2/6] 安装系统依赖...")
    subprocess.run(["apt-get", "update", "-qq"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    pkgs = ["nginx", "iptables", "ufw", "fail2ban", "iproute2", "curl"]
    for pkg in pkgs:
        r = subprocess.run(["dpkg", "-s", pkg], capture_output=True)
        if r.returncode != 0:
            print(f"  安装 {pkg}...")
            subprocess.run(
                ["apt-get", "install", "-y", "-qq", pkg],
                stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
            )
        else:
            print(f"  {pkg} 已安装")

    # 3. 安装 Python 依赖
    print("\n[3/6] 安装 Python 依赖...")
    subprocess.run(
        ["pip3", "install", "-q", "-r", os.path.join(PROJECT_DIR, "requirements.txt")],
        stdout=subprocess.DEVNULL,
    )
    print("  Python 依赖安装完成")

    # 4. 生成凭证
    print("\n[4/6] 生成安全凭证...")
    from shared import get_or_create_credential
    import secrets as _secrets
    gw_secret = get_or_create_credential("gateway_secret", lambda: _secrets.token_hex(32))
    db_pass = get_or_create_credential("dashboard_password", lambda: _secrets.token_urlsafe(12))
    print(f"  Dashboard 密码已生成")
    print(f"  网关签名密钥已生成")

    # 初始化网关配置（生成默认 Token、端口保护，必须在 Nginx/UFW 之前）
    gw_config_path = os.path.join(PROJECT_DIR, "gateway", "config.json")
    if not os.path.exists(gw_config_path):
        from datetime import datetime
        default_token = _secrets.token_hex(32)
        gw_cfg = {
            "tokens": {
                default_token: {
                    "name": "default",
                    "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                }
            },
            "ip_whitelist": [],
            "protected_ports": {
                "5000": {"nginx_port": 25000, "type": "standard", "comment": "安全监控看板"},
                "8502": {"nginx_port": 28502, "type": "chainlit", "comment": "AI 安全助手"},
            },
        }
        with open(gw_config_path, "w") as f:
            json.dump(gw_cfg, f, indent=2, ensure_ascii=False)
        print("  网关认证 Token 已生成")
    else:
        print("  网关配置已存在，跳过")

    # 5. 配置 Nginx + iptables
    print("\n[5/6] 配置 Nginx 网关...")
    subprocess.run(["python3", os.path.join(PROJECT_DIR, "gateway", "generate-nginx.py")])

    # 确保 sites-enabled 软链接存在
    from shared import NGINX_CONF_PATH, NGINX_ENABLED_PATH
    src = NGINX_CONF_PATH
    dst = NGINX_ENABLED_PATH
    if os.path.exists(src) and not os.path.exists(dst):
        os.symlink(src, dst)
    subprocess.run(["nginx", "-t"], capture_output=True)
    subprocess.run(["nginx", "-s", "reload"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    print("\n[6/6] 配置 iptables...")
    subprocess.run(["bash", os.path.join(PROJECT_DIR, "gateway", "setup-iptables.sh")])

    # 配置 UFW 防火墙（根据 config.json 放行受保护端口及其 Nginx 代理端口）
    print("\n[+] 配置 UFW 防火墙...")
    ufw_ports = {"22": "SSH", "5002": "Gateway"}
    if os.path.exists(gw_config_path):
        with open(gw_config_path) as f:
            cfg = json.load(f)
        for port_str, info in cfg.get("protected_ports", {}).items():
            comment = info.get("comment", f"端口 {port_str}")
            ufw_ports[port_str] = comment
            nginx_port = str(info.get("nginx_port", int(port_str) + 20000))
            ufw_ports[nginx_port] = f"Nginx Auth Proxy :{port_str}"
    for port, comment in sorted(ufw_ports.items(), key=lambda x: int(x[0])):
        subprocess.run(
            ["ufw", "allow", port + "/tcp", "comment", comment],
            capture_output=True,
        )
    subprocess.run(["ufw", "--force", "enable"], capture_output=True)
    allowed = ", ".join(sorted(ufw_ports.keys(), key=int))
    print(f"  UFW 已启用，放行端口: {allowed}")

    # 配置并启动 Fail2Ban
    print("\n[+] 配置 Fail2Ban...")
    f2b_jail_local = "/etc/fail2ban/jail.local"
    if not os.path.exists(f2b_jail_local):
        with open(f2b_jail_local, "w") as f:
            f.write("""[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 ::1
backend = systemd
banaction = iptables-multiport

[sshd]
enabled = true
port = ssh
filter = sshd
maxretry = 3
findtime = 600
bantime = 3600
""")
        print("  已创建 /etc/fail2ban/jail.local")
    else:
        print("  jail.local 已存在，跳过")
    # 确保 sshd jail 在 jail.d 中启用
    f2b_defaults = "/etc/fail2ban/jail.d/defaults-debian.conf"
    if not os.path.exists(f2b_defaults):
        os.makedirs("/etc/fail2ban/jail.d", exist_ok=True)
        with open(f2b_defaults, "w") as f:
            f.write("[sshd]\nenabled = true\n")
    subprocess.run(["systemctl", "enable", "fail2ban"], capture_output=True)
    subprocess.run(["systemctl", "restart", "fail2ban"], capture_output=True)
    # 验证启动状态
    r = subprocess.run(["fail2ban-client", "status"], capture_output=True, text=True, timeout=5)
    if r.returncode == 0:
        print("  Fail2Ban 已启动")
    else:
        print("  [警告] Fail2Ban 启动可能失败，请手动检查: systemctl status fail2ban")

    # 安装 gunicorn
    print("\n[+] 安装 Gunicorn...")
    subprocess.run(["pip3", "install", "-q", "gunicorn"], stdout=subprocess.DEVNULL)

    # 生成 systemd service
    print("\n[+] 配置 systemd 服务...")
    service_content = f"""[Unit]
Description=SecGate Security Gateway
After=network.target

[Service]
Type=forking
ExecStart={sys.executable} {os.path.join(PROJECT_DIR, 'secgate')} start
ExecStop={sys.executable} {os.path.join(PROJECT_DIR, 'secgate')} stop
ExecReload={sys.executable} {os.path.join(PROJECT_DIR, 'secgate')} restart
RemainAfterExit=yes
WorkingDirectory={PROJECT_DIR}

[Install]
WantedBy=multi-user.target
"""
    service_path = "/etc/systemd/system/secgate.service"
    with open(service_path, "w") as f:
        f.write(service_content)
    subprocess.run(["systemctl", "daemon-reload"], capture_output=True)
    subprocess.run(["systemctl", "enable", "secgate"], capture_output=True)
    print(f"  systemd 服务已配置: {service_path}")

    # 启动服务
    print("\n[+] 启动服务...")
    cmd_start()

    # 部署完成，展示全部凭证（含 Token）
    print("\n" + "=" * 50)
    print("  部署完成！以下是登录凭证")
    print("=" * 50)
    cmd_creds()


COMMANDS = {
    "start": cmd_start,
    "stop": cmd_stop,
    "restart": cmd_restart,
    "status": cmd_status,
    "creds": cmd_creds,
    "setup": cmd_setup,
    "version": cmd_version,
    "update": cmd_update,
    "uninstall": cmd_uninstall,
    "set-apikey": cmd_set_apikey,
}

HELP = """
SecGate - 安全网关一体化管理工具

用法: secgate <命令>

命令:
    setup          首次部署（安装依赖 + 配置系统服务 + 启动）
    start          启动所有服务
    stop           停止所有服务
    restart        重启所有服务
    status         查看运行状态
    creds          查看登录凭证
    set-apikey     配置 AI 助手（提示去 Dashboard 设置）
    version        查看版本号
    update         更新到最新版本
    uninstall      卸载 SecGate 并清理所有系统配置

快速开始:
    git clone https://github.com/zzmlb/secgate.git /opt/secgate
    cd /opt/secgate && sudo ./secgate setup

激活 AI 助手:
    在 Dashboard > AI 安全 > 助手设置 中配置 LLM
    支持通义千问（DashScope）、DeepSeek 等兼容 Anthropic 代理的 API

日常管理:
    secgate start     # 启动
    secgate creds     # 查看密码
    secgate status    # 查看状态
"""

if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help", "help"):
        print(HELP)
        sys.exit(0)

    cmd = sys.argv[1]
    if cmd not in COMMANDS:
        print(f"未知命令: {cmd}")
        print(HELP)
        sys.exit(1)

    COMMANDS[cmd]()
