mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
修复:server.py
This commit is contained in:
@@ -4,6 +4,7 @@ import hashlib
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
@@ -65,6 +66,9 @@ _BUNDLED_DIST = Path(__file__).parent / "dist"
|
||||
|
||||
|
||||
APP: Quart
|
||||
_ENV_PLACEHOLDER_RE = re.compile(
|
||||
r"\$(?:\{(?P<braced>[A-Za-z_][A-Za-z0-9_]*)(?::-(?P<default>[^}]*))?\}|(?P<plain>[A-Za-z_][A-Za-z0-9_]*))"
|
||||
)
|
||||
|
||||
|
||||
def _parse_env_bool(value: str | None, default: bool) -> bool:
|
||||
@@ -73,6 +77,39 @@ def _parse_env_bool(value: str | None, default: bool) -> bool:
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _expand_env_placeholders(value: str, field_name: str) -> str:
|
||||
missing_vars: list[str] = []
|
||||
|
||||
def _replace(match: re.Match[str]) -> str:
|
||||
var_name = match.group("braced") or match.group("plain")
|
||||
default = match.group("default")
|
||||
env_value = os.environ.get(var_name)
|
||||
if env_value is not None:
|
||||
return env_value
|
||||
if default is not None:
|
||||
return default
|
||||
missing_vars.append(var_name)
|
||||
return match.group(0)
|
||||
|
||||
expanded = _ENV_PLACEHOLDER_RE.sub(_replace, value)
|
||||
if missing_vars:
|
||||
missing = ", ".join(sorted(set(missing_vars)))
|
||||
raise ValueError(
|
||||
f"Unresolved environment variable(s) in dashboard {field_name}: {missing}"
|
||||
)
|
||||
return expanded
|
||||
|
||||
|
||||
def _resolve_dashboard_value(
|
||||
value: str | int | None,
|
||||
*,
|
||||
field_name: str,
|
||||
) -> str | int | None:
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
return _expand_env_placeholders(value, field_name).strip()
|
||||
|
||||
|
||||
class AstrBotJSONProvider(DefaultJSONProvider):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, datetime):
|
||||
@@ -431,16 +468,22 @@ class AstrBotDashboard:
|
||||
)
|
||||
|
||||
dashboard_config = self.config.get("dashboard", {})
|
||||
host = (
|
||||
host_value = (
|
||||
os.environ.get("ASTRBOT_HOST")
|
||||
or os.environ.get("DASHBOARD_HOST")
|
||||
or dashboard_config.get("host", "0.0.0.0")
|
||||
)
|
||||
port = int(
|
||||
host = _resolve_dashboard_value(host_value, field_name="host")
|
||||
if not isinstance(host, str) or not host:
|
||||
raise ValueError("Dashboard host must be a non-empty string")
|
||||
|
||||
port_value = (
|
||||
os.environ.get("ASTRBOT_PORT")
|
||||
or os.environ.get("DASHBOARD_PORT")
|
||||
or dashboard_config.get("port", 6185)
|
||||
)
|
||||
resolved_port = _resolve_dashboard_value(port_value, field_name="port")
|
||||
port = int(resolved_port)
|
||||
ssl_config = dashboard_config.get("ssl", {})
|
||||
ssl_enable = _parse_env_bool(
|
||||
os.environ.get("ASTRBOT_SSL_ENABLE")
|
||||
@@ -484,16 +527,19 @@ class AstrBotDashboard:
|
||||
or os.environ.get("DASHBOARD_SSL_CERT")
|
||||
or ssl_config.get("cert_file", "")
|
||||
)
|
||||
cert_file = _resolve_dashboard_value(cert_file, field_name="ssl.cert_file")
|
||||
key_file = (
|
||||
os.environ.get("ASTRBOT_SSL_KEY")
|
||||
or os.environ.get("DASHBOARD_SSL_KEY")
|
||||
or ssl_config.get("key_file", "")
|
||||
)
|
||||
key_file = _resolve_dashboard_value(key_file, field_name="ssl.key_file")
|
||||
ca_certs = (
|
||||
os.environ.get("ASTRBOT_SSL_CA_CERTS")
|
||||
or os.environ.get("DASHBOARD_SSL_CA_CERTS")
|
||||
or ssl_config.get("ca_certs", "")
|
||||
)
|
||||
ca_certs = _resolve_dashboard_value(ca_certs, field_name="ssl.ca_certs")
|
||||
|
||||
if cert_file and key_file:
|
||||
cert_path = await anyio.Path(cert_file).expanduser()
|
||||
|
||||
@@ -22,7 +22,7 @@ from astrbot.core.star.star import star_registry
|
||||
from astrbot.core.star.star_handler import star_handlers_registry
|
||||
from astrbot.core.utils.pip_installer import PipInstallError
|
||||
from astrbot.dashboard.routes.plugin import PluginRoute
|
||||
from astrbot.dashboard.server import AstrBotDashboard
|
||||
from astrbot.dashboard.server import AstrBotDashboard, _expand_env_placeholders
|
||||
from tests.fixtures.helpers import (
|
||||
MockPluginBuilder,
|
||||
create_mock_updater_install,
|
||||
@@ -80,6 +80,20 @@ def test_check_port_in_use_ignores_permission_errors(monkeypatch: pytest.MonkeyP
|
||||
assert server.check_port_in_use("127.0.0.1", 3002) is False
|
||||
|
||||
|
||||
def test_expand_env_placeholders_resolves_env_and_default(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
monkeypatch.setenv("HOST", "0.0.0.0")
|
||||
|
||||
assert _expand_env_placeholders("${HOST}", "host") == "0.0.0.0"
|
||||
assert _expand_env_placeholders("${MISSING:-3002}", "port") == "3002"
|
||||
|
||||
|
||||
def test_expand_env_placeholders_raises_for_unresolved_variable():
|
||||
with pytest.raises(ValueError, match="dashboard host: HOST"):
|
||||
_expand_env_placeholders("${HOST}", "host")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="module")
|
||||
async def core_lifecycle_td(tmp_path_factory):
|
||||
"""Creates and initializes a core lifecycle instance with a temporary database."""
|
||||
|
||||
Reference in New Issue
Block a user