From 435bac8c1c48e525e0992eb982d371ef379bf7c6 Mon Sep 17 00:00:00 2001 From: HLMC Date: Thu, 25 Jun 2026 16:29:48 +0800 Subject: [PATCH 1/4] feat: multiple IPs webui --- astrbot/core/config/astrbot_config.py | 7 +++++++ astrbot/core/config/default.py | 2 +- astrbot/core/utils/io.py | 6 ++++++ astrbot/dashboard/server.py | 23 ++++++++++++++++------ astrbot/dashboard/services/auth_service.py | 8 +++++++- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/astrbot/core/config/astrbot_config.py b/astrbot/core/config/astrbot_config.py index 92df17114f..4806d136ef 100644 --- a/astrbot/core/config/astrbot_config.py +++ b/astrbot/core/config/astrbot_config.py @@ -78,6 +78,13 @@ def __init__( ) # 检查配置完整性,并插入 has_new = self.check_config_integrity(default_config, conf) + + if isinstance(dashboard_conf, dict): + host_val = dashboard_conf.get("host") + if isinstance(host_val, str) and host_val: + dashboard_conf["host"] = [host_val] + has_new = True + reset_dashboard_password = self._consume_reset_dashboard_password_flag() if reset_dashboard_password and "dashboard" in conf: self._reset_generated_dashboard_password(conf) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 700a345ccc..78238dde88 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -255,7 +255,7 @@ "password_storage_upgraded": False, "password_change_required": False, "jwt_secret": "", - "host": "0.0.0.0", + "host": ["0.0.0.0", "::"], "port": 6185, "disable_access_log": True, "trust_proxy_headers": False, diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py index 3e9c20e323..75c0e82c59 100644 --- a/astrbot/core/utils/io.py +++ b/astrbot/core/utils/io.py @@ -367,6 +367,12 @@ def get_local_ip_addresses(): for addr in addrs: if addr.family == socket.AF_INET: # 使用 socket.AF_INET 代替 psutil.AF_INET network_ips.append(addr.address) + elif addr.family == socket.AF_INET6: + address = addr.address + scope_idx = address.find("%") + if scope_idx != -1: + address = address[:scope_idx] + network_ips.append(address) return network_ips diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 6776927ba0..e7483f8fa4 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -553,11 +553,15 @@ def run(self): or os.environ.get("ASTRBOT_DASHBOARD_PORT") or dashboard_config.get("port", 6185) ) - host = ( + host_raw = ( os.environ.get("DASHBOARD_HOST") or os.environ.get("ASTRBOT_DASHBOARD_HOST") or dashboard_config.get("host", "0.0.0.0") ) + if isinstance(host_raw, list): + hosts = host_raw + else: + hosts = [h.strip() for h in str(host_raw).split(",")] enable = dashboard_config.get("enable", True) ssl_config = dashboard_config.get("ssl", {}) if not isinstance(ssl_config, dict): @@ -578,13 +582,15 @@ def run(self): logger.info("WebUI disabled.") return None - logger.info("Starting WebUI at %s://%s:%s", scheme, host, port) - if host == "0.0.0.0": + logger.info("Starting WebUI at %s://%s:%s", scheme, hosts, port) + all_interfaces = {"0.0.0.0", "::"} + local_hosts = {"localhost", "127.0.0.1", "::1"} + if all_interfaces & set(hosts): logger.info( "WebUI listens on all interfaces. Check security. Set dashboard.host in data/cmd_config.json to change it.", ) - if host not in ["localhost", "127.0.0.1"]: + if not set(hosts).issubset(local_hosts): try: ip_addr = get_local_ip_addresses() except Exception as _: @@ -614,7 +620,10 @@ def run(self): parts = [f"\n ✨✨✨\n AstrBot v{VERSION} {webui_status}\n\n"] parts.append(f" ➜ Local: {scheme}://localhost:{port}\n") for ip in ip_addr: - parts.append(f" ➜ Network: {scheme}://{ip}:{port}\n") + if ":" in ip: + parts.append(f" ➜ Network: {scheme}://[{ip}]:{port}\n") + else: + parts.append(f" ➜ Network: {scheme}://{ip}:{port}\n") parts.append(self._build_dashboard_credentials_display()) display = "".join(parts) @@ -627,7 +636,9 @@ def run(self): # 配置 Hypercorn config = HyperConfig() - config.bind = [f"{host}:{port}"] + config.bind = [ + f"[{h}]:{port}" if ":" in h else f"{h}:{port}" for h in hosts + ] if bool(self.config.get("dashboard", {}).get("trust_proxy_headers", False)): config.logger_class = _ProxyAwareHypercornLogger if ssl_enable: diff --git a/astrbot/dashboard/services/auth_service.py b/astrbot/dashboard/services/auth_service.py index bc6cbde507..083dee2e3b 100644 --- a/astrbot/dashboard/services/auth_service.py +++ b/astrbot/dashboard/services/auth_service.py @@ -432,7 +432,13 @@ def can_skip_default_password_auth(self) -> bool: or os.environ.get("ASTRBOT_DASHBOARD_HOST") or self.config["dashboard"].get("host", "") ) - return str(host).strip().lower() in LOCAL_DASHBOARD_HOSTS + if isinstance(host, list): + hosts = host + else: + hosts = [h.strip() for h in str(host).split(",")] + return all( + str(h).strip().lower() in LOCAL_DASHBOARD_HOSTS for h in hosts + ) @staticmethod def env_flag_enabled(name: str) -> bool: From 7a76a17fc4a35af82576ac7c356af523cd72f9aa Mon Sep 17 00:00:00 2001 From: HLMC Date: Thu, 25 Jun 2026 16:35:59 +0800 Subject: [PATCH 2/4] fix: display local ip --- astrbot/dashboard/server.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index e7483f8fa4..251b85e58b 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -595,6 +595,13 @@ def run(self): ip_addr = get_local_ip_addresses() except Exception as _: pass + + bound_v6 = all(":" in h for h in hosts) + bound_v4 = all(":" not in h for h in hosts) + if bound_v6 and not bound_v4: + ip_addr = [ip for ip in ip_addr if ":" in ip] + elif bound_v4 and not bound_v6: + ip_addr = [ip for ip in ip_addr if ":" not in ip] if isinstance(port, str): port = int(port) From 1d4971f46ebeaf3cfba9ca84db15b82b0352297b Mon Sep 17 00:00:00 2001 From: HLMC Date: Thu, 25 Jun 2026 17:03:19 +0800 Subject: [PATCH 3/4] refactor: normalize host --- astrbot/core/config/astrbot_config.py | 1 + astrbot/core/utils/io.py | 16 ++++++++++ astrbot/dashboard/server.py | 36 ++++++++++++---------- astrbot/dashboard/services/auth_service.py | 10 +++--- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/astrbot/core/config/astrbot_config.py b/astrbot/core/config/astrbot_config.py index 4806d136ef..5f7e8eb52b 100644 --- a/astrbot/core/config/astrbot_config.py +++ b/astrbot/core/config/astrbot_config.py @@ -79,6 +79,7 @@ def __init__( # 检查配置完整性,并插入 has_new = self.check_config_integrity(default_config, conf) + dashboard_conf = conf.get("dashboard") if isinstance(dashboard_conf, dict): host_val = dashboard_conf.get("host") if isinstance(host_val, str) and host_val: diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py index 75c0e82c59..2e30cc006c 100644 --- a/astrbot/core/utils/io.py +++ b/astrbot/core/utils/io.py @@ -377,6 +377,22 @@ def get_local_ip_addresses(): return network_ips +def normalize_host_list(host_raw: str | list[str] | None) -> list[str]: + """Normalize a host config value into a list of host strings. + + Args: + host_raw: A string, list of strings, or None. + + Returns: + A list of non-empty host strings. + """ + if host_raw is None: + return [] + if isinstance(host_raw, list): + return [h for h in host_raw if h] + return [h.strip() for h in str(host_raw).split(",") if h.strip()] + + def get_dashboard_dist_version(dist_dir: str | Path) -> str | None: """Read the WebUI version from a dashboard dist directory. diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 251b85e58b..370ff7a04a 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -25,6 +25,7 @@ get_dashboard_dist_version, get_local_ip_addresses, is_dashboard_dist_compatible, + normalize_host_list, should_use_bundled_dashboard_dist, ) from astrbot.dashboard.asgi_runtime import ( @@ -556,12 +557,9 @@ def run(self): host_raw = ( os.environ.get("DASHBOARD_HOST") or os.environ.get("ASTRBOT_DASHBOARD_HOST") - or dashboard_config.get("host", "0.0.0.0") + or dashboard_config.get("host", ["0.0.0.0"]) ) - if isinstance(host_raw, list): - hosts = host_raw - else: - hosts = [h.strip() for h in str(host_raw).split(",")] + hosts = normalize_host_list(host_raw) enable = dashboard_config.get("enable", True) ssl_config = dashboard_config.get("ssl", {}) if not isinstance(ssl_config, dict): @@ -591,17 +589,23 @@ def run(self): ) if not set(hosts).issubset(local_hosts): - try: - ip_addr = get_local_ip_addresses() - except Exception as _: - pass - - bound_v6 = all(":" in h for h in hosts) - bound_v4 = all(":" not in h for h in hosts) - if bound_v6 and not bound_v4: - ip_addr = [ip for ip in ip_addr if ":" in ip] - elif bound_v4 and not bound_v6: - ip_addr = [ip for ip in ip_addr if ":" not in ip] + if all_interfaces & set(hosts): + try: + ip_addr = get_local_ip_addresses() + except Exception as _: + ip_addr = [] + has_v4_wildcard = "0.0.0.0" in hosts + has_v6_wildcard = "::" in hosts + if has_v4_wildcard and not has_v6_wildcard: + specific_v6 = [h for h in hosts if ":" in h and h != "::"] + ip_addr = [ip for ip in ip_addr if ":" not in ip] + specific_v6 + elif has_v6_wildcard and not has_v4_wildcard: + specific_v4 = [h for h in hosts if ":" not in h and h != "0.0.0.0"] + ip_addr = [ip for ip in ip_addr if ":" in ip] + specific_v4 + else: + ip_addr = [h for h in hosts if h not in local_hosts] + else: + ip_addr = [] if isinstance(port, str): port = int(port) diff --git a/astrbot/dashboard/services/auth_service.py b/astrbot/dashboard/services/auth_service.py index 083dee2e3b..253e122830 100644 --- a/astrbot/dashboard/services/auth_service.py +++ b/astrbot/dashboard/services/auth_service.py @@ -12,6 +12,7 @@ from astrbot.core import DEMO_MODE from astrbot.core.config.astrbot_config import AstrBotConfig from astrbot.core.db import BaseDatabase +from astrbot.core.utils.io import normalize_host_list from astrbot.core.utils.auth_password import ( is_default_dashboard_password, is_md5_dashboard_password, @@ -427,15 +428,14 @@ async def is_setup_required(self) -> bool: def can_skip_default_password_auth(self) -> bool: if not self.env_flag_enabled(SKIP_DEFAULT_PASSWORD_AUTH_ENV): return False - host = ( + host_raw = ( os.environ.get("DASHBOARD_HOST") or os.environ.get("ASTRBOT_DASHBOARD_HOST") or self.config["dashboard"].get("host", "") ) - if isinstance(host, list): - hosts = host - else: - hosts = [h.strip() for h in str(host).split(",")] + hosts = normalize_host_list(host_raw) + if not hosts: + return False return all( str(h).strip().lower() in LOCAL_DASHBOARD_HOSTS for h in hosts ) From 4d166e8820fd7534b8217979930bc67555f3de07 Mon Sep 17 00:00:00 2001 From: HLMC Date: Thu, 25 Jun 2026 17:05:19 +0800 Subject: [PATCH 4/4] fix: ip display --- astrbot/dashboard/server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 370ff7a04a..3b0f5207d1 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -580,7 +580,11 @@ def run(self): logger.info("WebUI disabled.") return None - logger.info("Starting WebUI at %s://%s:%s", scheme, hosts, port) + bound_urls = [ + f"{scheme}://[{h}]:{port}" if ":" in h else f"{scheme}://{h}:{port}" + for h in hosts + ] + logger.info("Starting WebUI at %s", ", ".join(bound_urls)) all_interfaces = {"0.0.0.0", "::"} local_hosts = {"localhost", "127.0.0.1", "::1"} if all_interfaces & set(hosts):