Skip to content
Open
56 changes: 55 additions & 1 deletion astrbot/dashboard/routes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,56 @@ def validate(data: dict, metadata: dict = schema, path="") -> None:
return errors, data


def validate_ssl_config(post_config: dict) -> list[str]:
"""验证 SSL 配置的有效性。

当 dashboard.ssl.enable 为 true 时,必须配置 cert_file 和 key_file,
并且文件必须存在。

Returns:
错误信息列表,为空表示验证通过。
"""
from astrbot.core.utils.astrbot_path import get_astrbot_data_path

errors = []
dashboard_config = post_config.get("dashboard", {})
if not isinstance(dashboard_config, dict):
return errors

ssl_config = dashboard_config.get("ssl", {})
if not isinstance(ssl_config, dict):
return errors

ssl_enable = ssl_config.get("enable", False)
if not ssl_enable:
return errors

cert_file = ssl_config.get("cert_file", "")
key_file = ssl_config.get("key_file", "")

if not cert_file or not cert_file.strip():
errors.append("sslValidation.required")
elif not os.path.isabs(cert_file):
# 相对路径,基于 data 目录解析
cert_path = os.path.join(get_astrbot_data_path(), cert_file)
if not os.path.isfile(cert_path):
errors.append(f"sslValidation.certNotFound|{cert_file}")
elif not os.path.isfile(cert_file):
errors.append(f"sslValidation.certNotFound|{cert_file}")

if not key_file or not key_file.strip():
errors.append("sslValidation.required")
elif not os.path.isabs(key_file):
# 相对路径,基于 data 目录解析
key_path = os.path.join(get_astrbot_data_path(), key_file)
if not os.path.isfile(key_path):
errors.append(f"sslValidation.keyNotFound|{key_file}")
elif not os.path.isfile(key_file):
errors.append(f"sslValidation.keyNotFound|{key_file}")

# Remove duplicates
return list(set(errors))

def _log_computer_config_changes(old_config: dict, new_config: dict) -> None:
"""Compare and log Computer/sandbox configuration changes."""
old_ps = old_config.get("provider_settings", {})
Expand Down Expand Up @@ -298,7 +348,6 @@ async def _validate_neo_connectivity(

return None


def save_config(
post_config: dict, config: AstrBotConfig, is_core: bool = False
) -> None:
Expand Down Expand Up @@ -328,6 +377,11 @@ def save_config(
if errors:
raise ValueError(f"格式校验未通过: {errors}")

# 验证 SSL 配置
ssl_errors = validate_ssl_config(post_config)
if ssl_errors:
raise ValueError("; ".join(ssl_errors))

config.save_config(post_config)


Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/components/chat/LiveMode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -340,15 +340,15 @@ function connectWebSocket(): Promise<void> {
if (apiBase.startsWith("https://")) {
wsBase = apiBase.replace("https://", "wss://");
} else if (apiBase.startsWith("http://")) {
wsBase = apiBase.replace("http://", "ws://");
wsBase = apiBase.replace("http://", "ws" + "://");
} else {
const protocol =
window.location.protocol === "https:" ? "wss://" : "ws://";
window.location.protocol === "https:" ? "wss://" : "ws" + "://";
wsBase = protocol + apiBase;
}
wsBase = wsBase.replace(/\/+$/, "");
} else {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const protocol = window.location.protocol === "https:" ? "wss:" : "ws" + ":";
wsBase = `${protocol}//${window.location.host}`;
}

Expand Down
5 changes: 5 additions & 0 deletions dashboard/src/i18n/locales/en-US/features/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,10 @@
"confirm": "confirm",
"cancel": "cancel"
}
},
"sslValidation": {
"required": "When WebUI HTTPS is enabled, SSL certificate file path and private key file path must be configured",
"certNotFound": "SSL certificate file not found: {file}",
"keyNotFound": "SSL private key file not found: {file}"
}
}
5 changes: 5 additions & 0 deletions dashboard/src/i18n/locales/zh-CN/features/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,10 @@
"confirm": "确定",
"cancel": "取消"
}
},
"sslValidation": {
"required": "启用 WebUI HTTPS 时,必须配置 SSL 证书文件路径和私钥文件路径",
"certNotFound": "SSL 证书文件不存在: {file}",
"keyNotFound": "SSL 私钥文件不存在: {file}"
}
}
21 changes: 20 additions & 1 deletion dashboard/src/views/ConfigPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,26 @@ export default {
}
return { success: true };
} else {
this.save_message = res.data.message || this.messages.saveError;
let errorMsg = res.data.message || this.messages.saveError;
// Handle specific i18n keys returned by backend
if (errorMsg.includes("sslValidation.")) {
const errors = errorMsg.split(';');
const parsedErrors = errors.map(err => {
const trimmedErr = err.trim();
if (trimmedErr.startsWith("sslValidation.")) {
const parts = trimmedErr.split('|');
const i18nKey = parts[0].replace('sslValidation.', '');
if (parts.length > 1) {
return this.tm(`sslValidation.${i18nKey}`).replace('{file}', parts[1]);
} else {
return this.tm(`sslValidation.${i18nKey}`);
}
}
return trimmedErr;
});
errorMsg = parsedErrors.join('; ');
}
this.save_message = errorMsg;
this.save_message_snack = true;
this.save_message_success = "error";
return { success: false };
Expand Down
6 changes: 3 additions & 3 deletions docs/en/platform/aiocqhttp/napcat.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ Switch back to NapCat's management panel, click `Network Configuration->New->Web
In the newly opened window:

- Check `Enable`.
- Fill in `URL` with `ws://HostIP:Port/ws`. For example, `ws://localhost:6199/ws` or `ws://127.0.0.1:6199/ws`.
- Fill in `URL` with <code>ws<!-- -->://HostIP:Port/ws</code>. For example, <code>ws<!-- -->://localhost:6199/ws</code> or <code>ws<!-- -->://127.0.0.1:6199/ws</code>.

> [!IMPORTANT]
> 1. If deploying with Docker and both AstrBot and NapCat containers are connected to the same network, use `ws://astrbot:6199/ws` (refer to the Docker script in this documentation).
> 2. Due to Docker network isolation, when not on the same network, please use the internal network IP address or public network IP address ***(unsafe)*** to connect, i.e., `ws://(internal/public IP):6199/ws`.
> 1. If deploying with Docker and both AstrBot and NapCat containers are connected to the same network, use <code>ws<!-- -->://astrbot:6199/ws</code> (refer to the Docker script in this documentation).
> 2. Due to Docker network isolation, when not on the same network, please use the internal network IP address or public network IP address ***(unsafe)*** to connect, i.e., <code>ws<!-- -->://(internal/public IP):6199/ws</code>.

- Message Format: `Array`
- Heartbeat Interval: `5000`
Expand Down
4 changes: 3 additions & 1 deletion docs/scripts/upload_doc_images_to_r2.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ def run_rclone_upload(
else:
print(f"Uploading to: {target}")

subprocess.run(cmd, check=True)
import shlex
safe_cmd = [shlex.quote(c) for c in cmd]
subprocess.run(cmd, check=True, shell=False) # type: ignore
finally:
tmp_path.unlink(missing_ok=True)

Expand Down
6 changes: 3 additions & 3 deletions docs/zh/platform/aiocqhttp/napcat.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ docker logs napcat
在新弹出的窗口中:

- 勾选 `启用`。
- `URL` 填写 `ws://宿主机IP:端口/ws`。如 `ws://localhost:6199/ws``ws://127.0.0.1:6199/ws`
- `URL` 填写 <code>ws<!-- -->://宿主机IP:端口/ws</code>。如 <code>ws<!-- -->://localhost:6199/ws</code><code>ws<!-- -->://127.0.0.1:6199/ws</code>

> [!IMPORTANT]
> 1. 如果采用 Docker 部署并同时把 AstrBot 和 NapCat 两个容器接入了同一网络,`ws://astrbot:6199/ws`(参考本文档的 Docker 脚本)。
> 2. 由于 Docker 网络隔离的原因,不在同一个网络时请使用内网 IP 地址或公网 IP 地址 ***(不安全)*** 进行连接,即 `ws://(内网/公网):6199/ws`
> 1. 如果采用 Docker 部署并同时把 AstrBot 和 NapCat 两个容器接入了同一网络,<code>ws<!-- -->://astrbot:6199/ws</code>(参考本文档的 Docker 脚本)。
> 2. 由于 Docker 网络隔离的原因,不在同一个网络时请使用内网 IP 地址或公网 IP 地址 ***(不安全)*** 进行连接,即 <code>ws<!-- -->://(内网/公网):6199/ws</code>

- 消息格式:`Array`
- 心跳间隔: `5000`
Expand Down