From aa1a342a77ddc51e6cf5a446caf05f8e8806df7d Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 15 Feb 2026 17:30:36 +0800 Subject: [PATCH] feat: add SSL configuration options for WebUI and update related logging --- astrbot/core/config/default.py | 42 +++++++++++ astrbot/core/utils/webhook_utils.py | 18 ++++- astrbot/dashboard/server.py | 69 ++++++++++++++++--- .../en-US/features/config-metadata.json | 20 ++++++ .../zh-CN/features/config-metadata.json | 20 ++++++ 5 files changed, 159 insertions(+), 10 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 43d9991bd..bf3358c6b 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -196,6 +196,12 @@ "host": "0.0.0.0", "port": 6185, "disable_access_log": True, + "ssl": { + "enable": False, + "cert_file": "", + "key_file": "", + "ca_certs": "", + }, }, "platform": [], "platform_specific": { @@ -2406,6 +2412,19 @@ class ChatProviderTemplate(TypedDict): "type": "string", "options": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], }, + "dashboard.ssl.enable": {"type": "bool"}, + "dashboard.ssl.cert_file": { + "type": "string", + "condition": {"dashboard.ssl.enable": True}, + }, + "dashboard.ssl.key_file": { + "type": "string", + "condition": {"dashboard.ssl.enable": True}, + }, + "dashboard.ssl.ca_certs": { + "type": "string", + "condition": {"dashboard.ssl.enable": True}, + }, "log_file_enable": {"type": "bool"}, "log_file_path": {"type": "string", "condition": {"log_file_enable": True}}, "log_file_max_mb": {"type": "int", "condition": {"log_file_enable": True}}, @@ -3420,6 +3439,29 @@ class ChatProviderTemplate(TypedDict): "hint": "控制台输出日志的级别。", "options": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], }, + "dashboard.ssl.enable": { + "description": "启用 WebUI HTTPS", + "type": "bool", + "hint": "启用后,WebUI 将直接使用 HTTPS 提供服务。", + }, + "dashboard.ssl.cert_file": { + "description": "SSL 证书文件路径", + "type": "string", + "hint": "证书文件路径(PEM)。支持绝对路径和相对路径(相对于当前工作目录)。", + "condition": {"dashboard.ssl.enable": True}, + }, + "dashboard.ssl.key_file": { + "description": "SSL 私钥文件路径", + "type": "string", + "hint": "私钥文件路径(PEM)。支持绝对路径和相对路径(相对于当前工作目录)。", + "condition": {"dashboard.ssl.enable": True}, + }, + "dashboard.ssl.ca_certs": { + "description": "SSL CA 证书文件路径", + "type": "string", + "hint": "可选。用于指定 CA 证书文件路径。", + "condition": {"dashboard.ssl.enable": True}, + }, "log_file_enable": { "description": "启用文件日志", "type": "bool", diff --git a/astrbot/core/utils/webhook_utils.py b/astrbot/core/utils/webhook_utils.py index 07abc115a..40dada3cb 100644 --- a/astrbot/core/utils/webhook_utils.py +++ b/astrbot/core/utils/webhook_utils.py @@ -1,3 +1,4 @@ +import os import uuid from astrbot.core import astrbot_config, logger @@ -20,6 +21,20 @@ def _get_dashboard_port() -> int: return 6185 +def _is_dashboard_ssl_enabled() -> bool: + env_ssl = os.environ.get("DASHBOARD_SSL_ENABLE") or os.environ.get( + "ASTRBOT_DASHBOARD_SSL_ENABLE" + ) + if env_ssl is not None: + return env_ssl.strip().lower() in {"1", "true", "yes", "on"} + + try: + return bool(astrbot_config.get("dashboard", {}).get("ssl", {}).get("enable")) + except Exception as e: + logger.error(f"获取 dashboard SSL 配置失败: {e!s}") + return False + + def log_webhook_info(platform_name: str, webhook_uuid: str) -> None: """打印美观的 webhook 信息日志 @@ -38,12 +53,13 @@ def log_webhook_info(platform_name: str, webhook_uuid: str) -> None: callback_base = callback_base.rstrip("/") webhook_url = f"{callback_base}/api/platform/webhook/{webhook_uuid}" + scheme = "https" if _is_dashboard_ssl_enabled() else "http" display_log = ( "\n====================\n" f"🔗 机器人平台 {platform_name} 已启用统一 Webhook 模式\n" f"📍 Webhook 回调地址: \n" - f" ➜ http://:{_get_dashboard_port()}/api/platform/webhook/{webhook_uuid}\n" + f" ➜ {scheme}://:{_get_dashboard_port()}/api/platform/webhook/{webhook_uuid}\n" f" ➜ {webhook_url}\n" "====================\n" ) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 9d8dffa37..6a588a5d3 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -2,6 +2,7 @@ import logging import os import socket +from pathlib import Path from typing import Protocol, cast import jwt @@ -36,6 +37,12 @@ class _AddrWithPort(Protocol): APP: Quart +def _parse_env_bool(value: str | None, default: bool) -> bool: + if value is None: + return default + return value.strip().lower() in {"1", "true", "yes", "on"} + + class AstrBotDashboard: def __init__( self, @@ -201,23 +208,33 @@ def _init_jwt_secret(self) -> None: def run(self): ip_addr = [] + dashboard_config = self.core_lifecycle.astrbot_config.get("dashboard", {}) port = ( os.environ.get("DASHBOARD_PORT") or os.environ.get("ASTRBOT_DASHBOARD_PORT") - or self.core_lifecycle.astrbot_config["dashboard"].get("port", 6185) + or dashboard_config.get("port", 6185) ) host = ( os.environ.get("DASHBOARD_HOST") or os.environ.get("ASTRBOT_DASHBOARD_HOST") - or self.core_lifecycle.astrbot_config["dashboard"].get("host", "0.0.0.0") + or dashboard_config.get("host", "0.0.0.0") ) - enable = self.core_lifecycle.astrbot_config["dashboard"].get("enable", True) + enable = dashboard_config.get("enable", True) + ssl_config = dashboard_config.get("ssl", {}) + if not isinstance(ssl_config, dict): + ssl_config = {} + ssl_enable = _parse_env_bool( + os.environ.get("DASHBOARD_SSL_ENABLE") + or os.environ.get("ASTRBOT_DASHBOARD_SSL_ENABLE"), + bool(ssl_config.get("enable", False)), + ) + scheme = "https" if ssl_enable else "http" if not enable: logger.info("WebUI 已被禁用") return None - logger.info(f"正在启动 WebUI, 监听地址: http://{host}:{port}") + logger.info(f"正在启动 WebUI, 监听地址: {scheme}://{host}:{port}") if host == "0.0.0.0": logger.info( "提示: WebUI 将监听所有网络接口,请注意安全。(可在 data/cmd_config.json 中配置 dashboard.host 以修改 host)", @@ -245,9 +262,9 @@ def run(self): raise Exception(f"端口 {port} 已被占用") parts = [f"\n ✨✨✨\n AstrBot v{VERSION} WebUI 已启动,可访问\n\n"] - parts.append(f" ➜ 本地: http://localhost:{port}\n") + parts.append(f" ➜ 本地: {scheme}://localhost:{port}\n") for ip in ip_addr: - parts.append(f" ➜ 网络: http://{ip}:{port}\n") + parts.append(f" ➜ 网络: {scheme}://{ip}:{port}\n") parts.append(" ➜ 默认用户名和密码: astrbot\n ✨✨✨\n") display = "".join(parts) @@ -261,11 +278,45 @@ def run(self): # 配置 Hypercorn config = HyperConfig() config.bind = [f"{host}:{port}"] + if ssl_enable: + cert_file = ( + os.environ.get("DASHBOARD_SSL_CERT") + or os.environ.get("ASTRBOT_DASHBOARD_SSL_CERT") + or ssl_config.get("cert_file", "") + ) + key_file = ( + os.environ.get("DASHBOARD_SSL_KEY") + or os.environ.get("ASTRBOT_DASHBOARD_SSL_KEY") + or ssl_config.get("key_file", "") + ) + ca_certs = ( + os.environ.get("DASHBOARD_SSL_CA_CERTS") + or os.environ.get("ASTRBOT_DASHBOARD_SSL_CA_CERTS") + or ssl_config.get("ca_certs", "") + ) + + cert_path = Path(cert_file).expanduser() + key_path = Path(key_file).expanduser() + if not cert_file or not key_file: + raise ValueError( + "dashboard.ssl.enable 为 true 时,必须配置 cert_file 和 key_file。", + ) + if not cert_path.is_file(): + raise ValueError(f"SSL 证书文件不存在: {cert_path}") + if not key_path.is_file(): + raise ValueError(f"SSL 私钥文件不存在: {key_path}") + + config.certfile = str(cert_path.resolve()) + config.keyfile = str(key_path.resolve()) + + if ca_certs: + ca_path = Path(ca_certs).expanduser() + if not ca_path.is_file(): + raise ValueError(f"SSL CA 证书文件不存在: {ca_path}") + config.ca_certs = str(ca_path.resolve()) # 根据配置决定是否禁用访问日志 - disable_access_log = self.core_lifecycle.astrbot_config.get( - "dashboard", {} - ).get("disable_access_log", True) + disable_access_log = dashboard_config.get("disable_access_log", True) if disable_access_log: config.accesslog = None else: diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index 19330d6cb..c61be33ef 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -873,6 +873,26 @@ "description": "Externally Accessible Callback API Address", "hint": "External services may access AstrBot's backend through callback links generated by AstrBot (such as file download links). Since AstrBot cannot automatically determine the externally accessible host address in the deployment environment, this configuration item is needed to explicitly specify how external services should access AstrBot's address. Examples: [http://localhost:6185](http://localhost:6185), [https://example.com](https://example.com), etc." }, + "dashboard": { + "ssl": { + "enable": { + "description": "Enable WebUI HTTPS", + "hint": "When enabled, WebUI serves directly over HTTPS." + }, + "cert_file": { + "description": "SSL Certificate File Path", + "hint": "Certificate file path (PEM). Supports absolute and relative paths (relative to current working directory)." + }, + "key_file": { + "description": "SSL Private Key File Path", + "hint": "Private key file path (PEM). Supports absolute and relative paths (relative to current working directory)." + }, + "ca_certs": { + "description": "SSL CA Certificate File Path", + "hint": "Optional. Path to CA certificate file." + } + } + }, "timezone": { "description": "Timezone", "hint": "Timezone setting. Please enter an IANA timezone name, such as Asia/Shanghai. Uses system default timezone when empty. For all timezones, see: [https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab](https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab)" diff --git a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json index 6c9edd49f..a51723d59 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -876,6 +876,26 @@ "description": "对外可达的回调接口地址", "hint": "外部服务可能会通过 AstrBot 生成的回调链接(如文件下载链接)访问 AstrBot 后端。由于 AstrBot 无法自动判断部署环境中对外可达的主机地址(host),因此需要通过此配置项显式指定外部服务如何访问 AstrBot 的地址。如 [http://localhost:6185](http://localhost:6185),[https://example.com](https://example.com) 等。" }, + "dashboard": { + "ssl": { + "enable": { + "description": "启用 WebUI HTTPS", + "hint": "启用后,WebUI 将直接使用 HTTPS 提供服务。" + }, + "cert_file": { + "description": "SSL 证书文件路径", + "hint": "证书文件路径(PEM)。支持绝对路径和相对路径(相对于当前工作目录)。" + }, + "key_file": { + "description": "SSL 私钥文件路径", + "hint": "私钥文件路径(PEM)。支持绝对路径和相对路径(相对于当前工作目录)。" + }, + "ca_certs": { + "description": "SSL CA 证书文件路径", + "hint": "可选。用于指定 CA 证书文件路径。" + } + } + }, "timezone": { "description": "时区", "hint": "时区设置。请填写 IANA 时区名称, 如 Asia/Shanghai, 为空时使用系统默认时区。所有时区请查看: [https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab](https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab)"