Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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}},
Expand Down Expand Up @@ -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",
Expand Down
18 changes: 17 additions & 1 deletion astrbot/core/utils/webhook_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import uuid

from astrbot.core import astrbot_config, logger
Expand All @@ -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"))
Comment on lines +24 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 这里使用 bool(config_value) 可能会把非布尔类型的配置值(例如非空字符串)当作启用状态。

配置中将 dashboard.ssl.enable 标注为布尔值,但这里的代码仍然只是做了一个通用的 bool(...) 转换。如果该值变成诸如 "false" 或 "0" 之类的非空字符串,它仍然会被评估为 True。为了与 server.run 保持一致并增强健壮性,建议对配置值也复用 _parse_env_bool 式的逻辑(先规范化字符串并只检查显式的真值),而不是依赖通用的真值判断。

Original comment in English

issue (bug_risk): Using bool(config_value) here may treat non-boolean config values (e.g. non-empty strings) as enabled.

Config marks dashboard.ssl.enable as a bool, but this code still does a generic bool(...) cast. If the value ever becomes a non-empty string like "false" or "0", it will still evaluate to True. To align with server.run and make this robust, consider reusing the same _parse_env_bool-style logic (normalize strings and check explicit truthy values) for the config value instead of relying on general truthiness.

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 信息日志

Expand All @@ -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://<your-ip>:{_get_dashboard_port()}/api/platform/webhook/{webhook_uuid}\n"
f" ➜ {scheme}://<your-ip>:{_get_dashboard_port()}/api/platform/webhook/{webhook_uuid}\n"
f" ➜ {webhook_url}\n"
"====================\n"
)
Expand Down
69 changes: 60 additions & 9 deletions astrbot/dashboard/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os
import socket
from pathlib import Path
from typing import Protocol, cast

import jwt
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)",
Expand Down Expand Up @@ -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)

Expand All @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions dashboard/src/i18n/locales/en-US/features/config-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
20 changes: 20 additions & 0 deletions dashboard/src/i18n/locales/zh-CN/features/config-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down