import secrets from .runner import Result from .server import Server MAIN_INBOUND = 'main' class XuiPanel(Server): """Сервер с панелью 3x-ui.""" def install( self, username: str, password: str, panel_port: int, web_base_path: str, sub_port: int, sub_path: str, domain: str = '', ) -> Result: """Ставит панель, включает подписку на sub_port по пути /sub_path/ и DNS-over-TLS к Cloudflare. Открывает порты 80 (acme), 443, panel_port и sub_port. Возвращает data: {'api_token': ...}. """ return self.run( 'xui', username=username, password=password, panel_port=panel_port, web_base_path=web_base_path, sub_port=sub_port, sub_path=sub_path, domain=domain, ) def create_inbound(self, remark: str = MAIN_INBOUND, target: str = '') -> Result: """Создает подключение vless + xhttp + reality на 443 порту, uTLS firefox, сброс трафика ежемесячно. target - цель reality (host:port) или список через запятую. Без него панель выбирает из своих кандидатов. Уже созданное подключение с тем же remark не меняется. Возвращает data: {'inbound_id', 'port', 'target', 'server_name', 'public_key', 'short_ids', 'path'}. """ return self.run( 'xui_inbound', remark=remark, target=target, short_ids=generate_short_ids(), xhttp_path=f'/{secrets.token_hex(8)}', ) def generate_short_ids(count: int = 4) -> list[str]: """shortId reality: hex длиной от 2 до 16 символов.""" return [secrets.token_hex(secrets.randbelow(8) + 1) for _ in range(count)]