From f1fc5c76e5d9c6548873d4dd58cc5c27ca3a2684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Tue, 2 Jun 2026 09:49:19 +0900 Subject: [PATCH 1/7] fix: allow LAN dashboard access by default --- src-tauri/src/backend/launch.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index a5dd2a7..72dd88c 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -24,7 +24,7 @@ const ASTRBOT_DASHBOARD_PORT_ENV: &str = "ASTRBOT_DASHBOARD_PORT"; const ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = "ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH"; const DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = "DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH"; -const DEFAULT_DASHBOARD_HOST: &str = "127.0.0.1"; +const DEFAULT_DASHBOARD_HOST: &str = "0.0.0.0"; const DEFAULT_DASHBOARD_PORT: &str = "6185"; fn sanitize_packaged_python_environment(command: &mut Command, log: F) @@ -51,9 +51,11 @@ fn configure_desktop_dashboard_environment(command: &mut Command) { let astrbot_skip_auth_env = env::var_os(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); let legacy_skip_auth_env = env::var_os(DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); + let default_dashboard_host = OsStr::new(DEFAULT_DASHBOARD_HOST); let effective_host = dashboard_host_env .as_deref() - .or(astrbot_dashboard_host_env.as_deref()); + .or(astrbot_dashboard_host_env.as_deref()) + .or(Some(default_dashboard_host)); let has_explicit_skip_auth = astrbot_skip_auth_env.is_some() || legacy_skip_auth_env.is_some(); if dashboard_host_env.is_none() && astrbot_dashboard_host_env.is_none() { @@ -360,20 +362,20 @@ mod tests { } #[test] - fn configure_desktop_dashboard_environment_enables_local_setup_without_default_password() { + fn configure_desktop_dashboard_environment_uses_lan_accessible_host_by_default() { with_clean_dashboard_env(|| { let mut command = Command::new("sh"); configure_desktop_dashboard_environment(&mut command); - assert_eq!( - get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), - Some(Some("true".to_string())) - ); assert_eq!( get_command_env_value(&command, DASHBOARD_HOST_ENV), Some(Some(DEFAULT_DASHBOARD_HOST.to_string())) ); + assert_eq!( + get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), + None + ); }); } From fd50669bc416fcc959b2d2edb375c9f9bda4cb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Tue, 2 Jun 2026 09:58:12 +0900 Subject: [PATCH 2/7] docs: explain manual LAN dashboard access --- README.md | 27 +++++++++++++++++++++++++++ README_zh.md | 27 +++++++++++++++++++++++++++ docs/environment-variables.md | 11 +++++++++++ src-tauri/src/backend/launch.rs | 16 +++++++--------- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ccce537..1129793 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,33 @@ If you need to migrate data between the desktop app and a source-based deploymen Not really. AstrBot Desktop is intended for local desktop usage and personal workflows. If you need long-running, stable server deployment, use the upstream AstrBot source, Docker, or panel-based deployment instead. + +### How can I access the WebUI from another device on my LAN? + +AstrBot Desktop listens on `127.0.0.1:6185` by default, so only the local machine can access the WebUI. If you explicitly want LAN access, set the dashboard host to `0.0.0.0`. + +Windows PowerShell: + +```powershell +setx ASTRBOT_DASHBOARD_HOST 0.0.0.0 +setx ASTRBOT_DASHBOARD_PORT 6185 +``` + +Fully quit and restart AstrBot Desktop after changing the variables, then visit this URL from another device: + +```text +http://:6185/ +``` + +On macOS / Linux, set the same environment variables before launching AstrBot Desktop: + +```bash +export ASTRBOT_DASHBOARD_HOST=0.0.0.0 +export ASTRBOT_DASHBOARD_PORT=6185 +``` + +Before enabling LAN access, make sure your system firewall allows port `6185`, and do not expose the port on untrusted networks or the public internet. To restore local-only access, remove the environment variables or set `ASTRBOT_DASHBOARD_HOST` back to `127.0.0.1`, then restart the app. + ### macOS says the app is damaged or cannot be opened diff --git a/README_zh.md b/README_zh.md index ee2af9c..3cdb23e 100644 --- a/README_zh.md +++ b/README_zh.md @@ -93,6 +93,33 @@ AstrBot Desktop 是面向本地桌面使用的 AstrBot 打包发行版。它内 不推荐。AstrBot Desktop 更适合本地桌面使用和个人工作流体验;如果你要长期稳定运行在服务器上,更建议使用上游 AstrBot 的源码、Docker 或面板部署方式。 + +### 如何从局域网内其他设备访问 WebUI? + +桌面端默认只监听 `127.0.0.1:6185`,仅允许本机访问。如果确认需要在同一局域网内访问,可以手动把 dashboard host 改为 `0.0.0.0`。 + +Windows PowerShell: + +```powershell +setx ASTRBOT_DASHBOARD_HOST 0.0.0.0 +setx ASTRBOT_DASHBOARD_PORT 6185 +``` + +设置后完全退出并重新启动 AstrBot Desktop,然后在其他设备访问: + +```text +http://<运行 AstrBot Desktop 的主机内网 IP>:6185/ +``` + +macOS / Linux 也可以在启动 AstrBot Desktop 前设置同名环境变量: + +```bash +export ASTRBOT_DASHBOARD_HOST=0.0.0.0 +export ASTRBOT_DASHBOARD_PORT=6185 +``` + +开启局域网访问前,请确认系统防火墙允许端口 `6185`,并不要在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可删除环境变量,或把 `ASTRBOT_DASHBOARD_HOST` 重新设为 `127.0.0.1` 后重启应用。 + ### macOS 提示“应用已损坏”或无法打开 diff --git a/docs/environment-variables.md b/docs/environment-variables.md index cbea4c6..90043a6 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -56,6 +56,17 @@ | `ASTRBOT_DESKTOP_CLIENT` | 标记桌面客户端环境 | 打包态启动后端时写入 `1` | | `ASTRBOT_BACKEND_STARTUP_HEARTBEAT_PATH` | 桌面端写给后端启动器的 heartbeat 文件路径 | 打包态默认写到 `ASTRBOT_ROOT/data/backend-startup-heartbeat.json` | +### 局域网访问 WebUI + +桌面端默认写入 `DASHBOARD_HOST=127.0.0.1`,因此 WebUI 默认仅允许本机访问。如果需要从同一局域网内的其他设备访问,可手动设置: + +```powershell +setx ASTRBOT_DASHBOARD_HOST 0.0.0.0 +setx ASTRBOT_DASHBOARD_PORT 6185 +``` + +设置后需要完全退出并重新启动 AstrBot Desktop,再通过 `http://<主机内网 IP>:6185/` 访问。开启前请确认系统防火墙允许端口 `6185`,并避免在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可删除环境变量,或将 `ASTRBOT_DASHBOARD_HOST` 设回 `127.0.0.1` 后重启应用。 + ## 4. 发布/CI(GitHub Actions) | 变量 | 用途 | 默认值/行为 | diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index 72dd88c..a5dd2a7 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -24,7 +24,7 @@ const ASTRBOT_DASHBOARD_PORT_ENV: &str = "ASTRBOT_DASHBOARD_PORT"; const ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = "ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH"; const DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = "DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH"; -const DEFAULT_DASHBOARD_HOST: &str = "0.0.0.0"; +const DEFAULT_DASHBOARD_HOST: &str = "127.0.0.1"; const DEFAULT_DASHBOARD_PORT: &str = "6185"; fn sanitize_packaged_python_environment(command: &mut Command, log: F) @@ -51,11 +51,9 @@ fn configure_desktop_dashboard_environment(command: &mut Command) { let astrbot_skip_auth_env = env::var_os(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); let legacy_skip_auth_env = env::var_os(DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); - let default_dashboard_host = OsStr::new(DEFAULT_DASHBOARD_HOST); let effective_host = dashboard_host_env .as_deref() - .or(astrbot_dashboard_host_env.as_deref()) - .or(Some(default_dashboard_host)); + .or(astrbot_dashboard_host_env.as_deref()); let has_explicit_skip_auth = astrbot_skip_auth_env.is_some() || legacy_skip_auth_env.is_some(); if dashboard_host_env.is_none() && astrbot_dashboard_host_env.is_none() { @@ -362,19 +360,19 @@ mod tests { } #[test] - fn configure_desktop_dashboard_environment_uses_lan_accessible_host_by_default() { + fn configure_desktop_dashboard_environment_enables_local_setup_without_default_password() { with_clean_dashboard_env(|| { let mut command = Command::new("sh"); configure_desktop_dashboard_environment(&mut command); assert_eq!( - get_command_env_value(&command, DASHBOARD_HOST_ENV), - Some(Some(DEFAULT_DASHBOARD_HOST.to_string())) + get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), + Some(Some("true".to_string())) ); assert_eq!( - get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), - None + get_command_env_value(&command, DASHBOARD_HOST_ENV), + Some(Some(DEFAULT_DASHBOARD_HOST.to_string())) ); }); } From ba30194c92985c7cc7494f35f2e60c262ef62237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Tue, 2 Jun 2026 10:16:06 +0900 Subject: [PATCH 3/7] feat: support desktop dashboard listen config --- README.md | 38 +++++-- README_zh.md | 38 +++++-- docs/environment-variables.md | 22 +++- src-tauri/src/backend/launch.rs | 191 ++++++++++++++++++++++++++++++-- 4 files changed, 254 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 1129793..77a6aac 100644 --- a/README.md +++ b/README.md @@ -96,29 +96,47 @@ Not really. AstrBot Desktop is intended for local desktop usage and personal wor ### How can I access the WebUI from another device on my LAN? -AstrBot Desktop listens on `127.0.0.1:6185` by default, so only the local machine can access the WebUI. If you explicitly want LAN access, set the dashboard host to `0.0.0.0`. +AstrBot Desktop listens on `127.0.0.1:6185` by default, so only the local machine can access the WebUI. If you explicitly want LAN access, set the dashboard host to `0.0.0.0` in the desktop config file. -Windows PowerShell: +Config file path: -```powershell -setx ASTRBOT_DASHBOARD_HOST 0.0.0.0 -setx ASTRBOT_DASHBOARD_PORT 6185 +```text +~/.astrbot/data/config/desktop.json ``` -Fully quit and restart AstrBot Desktop after changing the variables, then visit this URL from another device: +On Windows, this is usually: + +```text +C:\Users\\.astrbot\data\config\desktop.json +``` + +Write this content: + +```json +{ + "dashboard": { + "host": "0.0.0.0", + "port": 6185 + } +} +``` + +Fully quit and restart AstrBot Desktop after saving the file, then visit this URL from another device: ```text http://:6185/ ``` -On macOS / Linux, set the same environment variables before launching AstrBot Desktop: +To restore local-only access, remove the `dashboard` config or set `host` back to `127.0.0.1`, then restart the app. + +Environment variables still work as advanced overrides and take precedence over `desktop.json`: ```bash -export ASTRBOT_DASHBOARD_HOST=0.0.0.0 -export ASTRBOT_DASHBOARD_PORT=6185 +ASTRBOT_DASHBOARD_HOST=0.0.0.0 +ASTRBOT_DASHBOARD_PORT=6185 ``` -Before enabling LAN access, make sure your system firewall allows port `6185`, and do not expose the port on untrusted networks or the public internet. To restore local-only access, remove the environment variables or set `ASTRBOT_DASHBOARD_HOST` back to `127.0.0.1`, then restart the app. +Before enabling LAN access, make sure your system firewall allows port `6185`, and do not expose the port on untrusted networks or the public internet. ### macOS says the app is damaged or cannot be opened diff --git a/README_zh.md b/README_zh.md index 3cdb23e..5bfe952 100644 --- a/README_zh.md +++ b/README_zh.md @@ -96,29 +96,47 @@ AstrBot Desktop 是面向本地桌面使用的 AstrBot 打包发行版。它内 ### 如何从局域网内其他设备访问 WebUI? -桌面端默认只监听 `127.0.0.1:6185`,仅允许本机访问。如果确认需要在同一局域网内访问,可以手动把 dashboard host 改为 `0.0.0.0`。 +桌面端默认只监听 `127.0.0.1:6185`,仅允许本机访问。如果确认需要在同一局域网内访问,可以在桌面端配置文件中把 dashboard host 改为 `0.0.0.0`。 -Windows PowerShell: +配置文件路径: -```powershell -setx ASTRBOT_DASHBOARD_HOST 0.0.0.0 -setx ASTRBOT_DASHBOARD_PORT 6185 +```text +~/.astrbot/data/config/desktop.json ``` -设置后完全退出并重新启动 AstrBot Desktop,然后在其他设备访问: +Windows 对应路径通常是: + +```text +C:\Users\<用户名>\.astrbot\data\config\desktop.json +``` + +写入以下内容: + +```json +{ + "dashboard": { + "host": "0.0.0.0", + "port": 6185 + } +} +``` + +保存后完全退出并重新启动 AstrBot Desktop,然后在其他设备访问: ```text http://<运行 AstrBot Desktop 的主机内网 IP>:6185/ ``` -macOS / Linux 也可以在启动 AstrBot Desktop 前设置同名环境变量: +如需恢复默认本机访问,可删除 `dashboard` 配置,或把 `host` 改回 `127.0.0.1` 后重启应用。 + +环境变量仍可作为高级覆盖项,且优先级高于 `desktop.json`: ```bash -export ASTRBOT_DASHBOARD_HOST=0.0.0.0 -export ASTRBOT_DASHBOARD_PORT=6185 +ASTRBOT_DASHBOARD_HOST=0.0.0.0 +ASTRBOT_DASHBOARD_PORT=6185 ``` -开启局域网访问前,请确认系统防火墙允许端口 `6185`,并不要在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可删除环境变量,或把 `ASTRBOT_DASHBOARD_HOST` 重新设为 `127.0.0.1` 后重启应用。 +开启局域网访问前,请确认系统防火墙允许端口 `6185`,并不要在不可信网络或公网环境暴露该端口。 ### macOS 提示“应用已损坏”或无法打开 diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 90043a6..a518c26 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -58,14 +58,26 @@ ### 局域网访问 WebUI -桌面端默认写入 `DASHBOARD_HOST=127.0.0.1`,因此 WebUI 默认仅允许本机访问。如果需要从同一局域网内的其他设备访问,可手动设置: +桌面端默认写入 `DASHBOARD_HOST=127.0.0.1`,因此 WebUI 默认仅允许本机访问。如果需要从同一局域网内的其他设备访问,推荐编辑桌面端配置文件: -```powershell -setx ASTRBOT_DASHBOARD_HOST 0.0.0.0 -setx ASTRBOT_DASHBOARD_PORT 6185 +```text +~/.astrbot/data/config/desktop.json ``` -设置后需要完全退出并重新启动 AstrBot Desktop,再通过 `http://<主机内网 IP>:6185/` 访问。开启前请确认系统防火墙允许端口 `6185`,并避免在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可删除环境变量,或将 `ASTRBOT_DASHBOARD_HOST` 设回 `127.0.0.1` 后重启应用。 +Windows 对应路径通常为 `C:\Users\<用户名>\.astrbot\data\config\desktop.json`。 + +```json +{ + "dashboard": { + "host": "0.0.0.0", + "port": 6185 + } +} +``` + +设置后需要完全退出并重新启动 AstrBot Desktop,再通过 `http://<主机内网 IP>:6185/` 访问。开启前请确认系统防火墙允许端口 `6185`,并避免在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可删除 `dashboard` 配置,或将 `host` 设回 `127.0.0.1` 后重启应用。 + +环境变量优先级高于 `desktop.json`。如果同时设置了 `ASTRBOT_DASHBOARD_HOST` / `DASHBOARD_HOST` 或 `ASTRBOT_DASHBOARD_PORT` / `DASHBOARD_PORT`,桌面端会优先保留环境变量值。 ## 4. 发布/CI(GitHub Actions) diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index a5dd2a7..0a5959f 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -2,6 +2,7 @@ use std::{ env, ffi::OsStr, fs::{self, OpenOptions}, + path::Path, process::{Command, Stdio}, }; @@ -26,6 +27,13 @@ const ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = const DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = "DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH"; const DEFAULT_DASHBOARD_HOST: &str = "127.0.0.1"; const DEFAULT_DASHBOARD_PORT: &str = "6185"; +const DESKTOP_CONFIG_RELATIVE_PATH: &str = "data/config/desktop.json"; + +#[derive(Debug, Default)] +struct DesktopDashboardConfig { + host: Option, + port: Option, +} fn sanitize_packaged_python_environment(command: &mut Command, log: F) where @@ -43,30 +51,113 @@ where command.env("PYTHONNOUSERSITE", "1"); } -fn configure_desktop_dashboard_environment(command: &mut Command) { +fn configure_desktop_dashboard_environment( + command: &mut Command, + root_dir: Option<&Path>, + log: F, +) where + F: FnMut(&str), +{ let dashboard_host_env = env::var_os(DASHBOARD_HOST_ENV); let astrbot_dashboard_host_env = env::var_os(ASTRBOT_DASHBOARD_HOST_ENV); let dashboard_port_env = env::var_os(DASHBOARD_PORT_ENV); let astrbot_dashboard_port_env = env::var_os(ASTRBOT_DASHBOARD_PORT_ENV); let astrbot_skip_auth_env = env::var_os(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); let legacy_skip_auth_env = env::var_os(DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); + let desktop_config = read_desktop_dashboard_config(root_dir, log); + let config_host = desktop_config.host.as_deref().map(OsStr::new); let effective_host = dashboard_host_env .as_deref() - .or(astrbot_dashboard_host_env.as_deref()); + .or(astrbot_dashboard_host_env.as_deref()) + .or(config_host); let has_explicit_skip_auth = astrbot_skip_auth_env.is_some() || legacy_skip_auth_env.is_some(); if dashboard_host_env.is_none() && astrbot_dashboard_host_env.is_none() { - command.env(DASHBOARD_HOST_ENV, DEFAULT_DASHBOARD_HOST); + command.env( + DASHBOARD_HOST_ENV, + desktop_config + .host + .as_deref() + .unwrap_or(DEFAULT_DASHBOARD_HOST), + ); } if dashboard_port_env.is_none() && astrbot_dashboard_port_env.is_none() { - command.env(DASHBOARD_PORT_ENV, DEFAULT_DASHBOARD_PORT); + command.env( + DASHBOARD_PORT_ENV, + desktop_config + .port + .as_deref() + .unwrap_or(DEFAULT_DASHBOARD_PORT), + ); } if should_skip_default_password_auth(has_explicit_skip_auth, effective_host) { command.env(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, "true"); } } +fn read_desktop_dashboard_config(root_dir: Option<&Path>, mut log: F) -> DesktopDashboardConfig +where + F: FnMut(&str), +{ + let Some(root_dir) = root_dir else { + return DesktopDashboardConfig::default(); + }; + let config_path = root_dir.join(DESKTOP_CONFIG_RELATIVE_PATH); + if !config_path.is_file() { + return DesktopDashboardConfig::default(); + } + + let raw = match fs::read_to_string(&config_path) { + Ok(raw) => raw, + Err(error) => { + log(&format!( + "failed to read desktop config {}: {}", + config_path.display(), + error + )); + return DesktopDashboardConfig::default(); + } + }; + let parsed: serde_json::Value = match serde_json::from_str(&raw) { + Ok(parsed) => parsed, + Err(error) => { + log(&format!( + "failed to parse desktop config {}: {}", + config_path.display(), + error + )); + return DesktopDashboardConfig::default(); + } + }; + let dashboard = parsed.get("dashboard"); + DesktopDashboardConfig { + host: dashboard + .and_then(|value| value.get("host")) + .and_then(parse_dashboard_host), + port: dashboard + .and_then(|value| value.get("port")) + .and_then(parse_dashboard_port), + } +} + +fn parse_dashboard_host(value: &serde_json::Value) -> Option { + let host = value.as_str()?.trim(); + (!host.is_empty()).then(|| host.to_string()) +} + +fn parse_dashboard_port(value: &serde_json::Value) -> Option { + if let Some(port) = value.as_u64() { + if (1..=65535).contains(&port) { + return Some(port.to_string()); + } + return None; + } + let port = value.as_str()?.trim(); + let parsed = port.parse::().ok()?; + (parsed > 0).then(|| port.to_string()) +} + fn should_skip_default_password_auth( has_explicit_skip_auth: bool, effective_host: Option<&OsStr>, @@ -164,7 +255,11 @@ impl BackendState { if let Some(path_override) = backend_path_override() { command.env("PATH", path_override); } - configure_desktop_dashboard_environment(&mut command); + configure_desktop_dashboard_environment( + &mut command, + plan.root_dir.as_deref(), + append_desktop_log, + ); #[cfg(target_os = "windows")] { if plan.packaged_mode { @@ -261,6 +356,8 @@ mod tests { use std::{ env, ffi::{OsStr, OsString}, + fs, + path::Path, process::Command, sync::Mutex, }; @@ -272,7 +369,8 @@ mod tests { configure_desktop_dashboard_environment, sanitize_packaged_python_environment, ASTRBOT_DASHBOARD_HOST_ENV, ASTRBOT_DASHBOARD_PORT_ENV, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, DASHBOARD_HOST_ENV, DASHBOARD_PORT_ENV, - DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, DEFAULT_DASHBOARD_HOST, + DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, DEFAULT_DASHBOARD_HOST, DEFAULT_DASHBOARD_PORT, + DESKTOP_CONFIG_RELATIVE_PATH, }; static ENV_TEST_LOCK: Mutex<()> = Mutex::new(()); @@ -364,7 +462,7 @@ mod tests { with_clean_dashboard_env(|| { let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command); + configure_desktop_dashboard_environment(&mut command, None, |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -385,7 +483,7 @@ mod tests { env::set_var(DASHBOARD_PORT_ENV, "7000"); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command); + configure_desktop_dashboard_environment(&mut command, None, |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -402,7 +500,7 @@ mod tests { env::set_var(DASHBOARD_HOST_ENV, "0.0.0.0"); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command); + configure_desktop_dashboard_environment(&mut command, None, |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -418,13 +516,86 @@ mod tests { env::set_var(DASHBOARD_HOST_ENV, std::ffi::OsString::from_vec(vec![0xff])); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command); + configure_desktop_dashboard_environment(&mut command, None, |_| {}); + + assert_eq!( + get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), + None + ); + assert_eq!(get_command_env_value(&command, DASHBOARD_HOST_ENV), None); + }); + } + + fn write_desktop_config(root: &Path, contents: &str) { + let config_path = root.join(DESKTOP_CONFIG_RELATIVE_PATH); + fs::create_dir_all(config_path.parent().expect("config parent")) + .expect("create config dir"); + fs::write(config_path, contents).expect("write desktop config"); + } + + #[test] + fn configure_desktop_dashboard_environment_reads_desktop_config() { + with_clean_dashboard_env(|| { + let root = tempfile::tempdir().expect("temp root"); + write_desktop_config( + root.path(), + r#"{"dashboard":{"host":"0.0.0.0","port":6185}}"#, + ); + let mut command = Command::new("sh"); + + configure_desktop_dashboard_environment(&mut command, Some(root.path()), |_| {}); + assert_eq!( + get_command_env_value(&command, DASHBOARD_HOST_ENV), + Some(Some("0.0.0.0".to_string())) + ); + assert_eq!( + get_command_env_value(&command, DASHBOARD_PORT_ENV), + Some(Some("6185".to_string())) + ); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), None ); + }); + } + + #[test] + fn configure_desktop_dashboard_environment_env_overrides_desktop_config() { + with_clean_dashboard_env(|| { + let root = tempfile::tempdir().expect("temp root"); + write_desktop_config( + root.path(), + r#"{"dashboard":{"host":"0.0.0.0","port":6185}}"#, + ); + env::set_var(DASHBOARD_HOST_ENV, "localhost"); + env::set_var(DASHBOARD_PORT_ENV, "7000"); + let mut command = Command::new("sh"); + + configure_desktop_dashboard_environment(&mut command, Some(root.path()), |_| {}); + assert_eq!(get_command_env_value(&command, DASHBOARD_HOST_ENV), None); + assert_eq!(get_command_env_value(&command, DASHBOARD_PORT_ENV), None); + }); + } + + #[test] + fn configure_desktop_dashboard_environment_ignores_invalid_desktop_config() { + with_clean_dashboard_env(|| { + let root = tempfile::tempdir().expect("temp root"); + write_desktop_config(root.path(), r#"{"dashboard":{"host":" ","port":70000}}"#); + let mut command = Command::new("sh"); + + configure_desktop_dashboard_environment(&mut command, Some(root.path()), |_| {}); + + assert_eq!( + get_command_env_value(&command, DASHBOARD_HOST_ENV), + Some(Some(DEFAULT_DASHBOARD_HOST.to_string())) + ); + assert_eq!( + get_command_env_value(&command, DASHBOARD_PORT_ENV), + Some(Some(DEFAULT_DASHBOARD_PORT.to_string())) + ); }); } } From 86d1e9848325c1643fca0bbcdd6a8d32d1fd1bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Tue, 2 Jun 2026 10:51:28 +0900 Subject: [PATCH 4/7] fix: log invalid desktop dashboard config --- src-tauri/src/backend/launch.rs | 38 +++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index 0a5959f..e4259f1 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -131,14 +131,25 @@ where } }; let dashboard = parsed.get("dashboard"); - DesktopDashboardConfig { - host: dashboard - .and_then(|value| value.get("host")) - .and_then(parse_dashboard_host), - port: dashboard - .and_then(|value| value.get("port")) - .and_then(parse_dashboard_port), - } + let (host, port) = if let Some(dashboard) = dashboard { + let raw_host = dashboard.get("host"); + let parsed_host = raw_host.and_then(parse_dashboard_host); + if raw_host.is_some() && parsed_host.is_none() { + log("desktop config: ignoring invalid dashboard.host value"); + } + + let raw_port = dashboard.get("port"); + let parsed_port = raw_port.and_then(parse_dashboard_port); + if raw_port.is_some() && parsed_port.is_none() { + log("desktop config: ignoring invalid dashboard.port value"); + } + + (parsed_host, parsed_port) + } else { + (None, None) + }; + + DesktopDashboardConfig { host, port } } fn parse_dashboard_host(value: &serde_json::Value) -> Option { @@ -585,8 +596,11 @@ mod tests { let root = tempfile::tempdir().expect("temp root"); write_desktop_config(root.path(), r#"{"dashboard":{"host":" ","port":70000}}"#); let mut command = Command::new("sh"); + let mut logs = Vec::new(); - configure_desktop_dashboard_environment(&mut command, Some(root.path()), |_| {}); + configure_desktop_dashboard_environment(&mut command, Some(root.path()), |message| { + logs.push(message.to_string()) + }); assert_eq!( get_command_env_value(&command, DASHBOARD_HOST_ENV), @@ -596,6 +610,12 @@ mod tests { get_command_env_value(&command, DASHBOARD_PORT_ENV), Some(Some(DEFAULT_DASHBOARD_PORT.to_string())) ); + assert!(logs + .iter() + .any(|message| message.contains("invalid dashboard.host"))); + assert!(logs + .iter() + .any(|message| message.contains("invalid dashboard.port"))); }); } } From 2d4eaef1159f37ad020cd55080a2b70869cad92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Tue, 2 Jun 2026 11:26:00 +0900 Subject: [PATCH 5/7] refactor: simplify desktop dashboard config parsing --- src-tauri/src/backend/launch.rs | 190 +++++++++++++++++++------------- 1 file changed, 112 insertions(+), 78 deletions(-) diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index e4259f1..5ef28ee 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -1,6 +1,6 @@ use std::{ env, - ffi::OsStr, + ffi::{OsStr, OsString}, fs::{self, OpenOptions}, path::Path, process::{Command, Stdio}, @@ -9,6 +9,7 @@ use std::{ #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; +use serde::Deserialize; use tauri::AppHandle; use crate::{ @@ -35,6 +36,49 @@ struct DesktopDashboardConfig { port: Option, } +#[derive(Debug, Default, Deserialize)] +struct DesktopFileConfig { + dashboard: Option, +} + +#[derive(Debug, Deserialize)] +struct DesktopFileDashboard { + host: Option, + port: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum DesktopPortField { + Num(u64), + Str(String), +} + +impl DesktopDashboardConfig { + fn from_file_config(config: DesktopFileConfig, log: &mut dyn FnMut(&str)) -> Self { + let Some(dashboard) = config.dashboard else { + return Self::default(); + }; + + let host = dashboard.host.and_then(|host| { + let trimmed = host.trim(); + if trimmed.is_empty() { + log("desktop config: ignoring invalid dashboard.host value"); + None + } else { + Some(trimmed.to_string()) + } + }); + + let port = dashboard.port.and_then(|port| match port { + DesktopPortField::Num(port) => parse_dashboard_port_number(port, log), + DesktopPortField::Str(port) => parse_dashboard_port_string(&port, log), + }); + + Self { host, port } + } +} + fn sanitize_packaged_python_environment(command: &mut Command, log: F) where F: Fn(&str), @@ -51,13 +95,11 @@ where command.env("PYTHONNOUSERSITE", "1"); } -fn configure_desktop_dashboard_environment( +fn configure_desktop_dashboard_environment( command: &mut Command, root_dir: Option<&Path>, - log: F, -) where - F: FnMut(&str), -{ + log: &mut dyn FnMut(&str), +) { let dashboard_host_env = env::var_os(DASHBOARD_HOST_ENV); let astrbot_dashboard_host_env = env::var_os(ASTRBOT_DASHBOARD_HOST_ENV); let dashboard_port_env = env::var_os(DASHBOARD_PORT_ENV); @@ -65,41 +107,50 @@ fn configure_desktop_dashboard_environment( let astrbot_skip_auth_env = env::var_os(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); let legacy_skip_auth_env = env::var_os(DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); let desktop_config = read_desktop_dashboard_config(root_dir, log); - let config_host = desktop_config.host.as_deref().map(OsStr::new); + let (host_env_any, effective_host) = resolve_dashboard_value( + dashboard_host_env, + astrbot_dashboard_host_env, + desktop_config.host, + DEFAULT_DASHBOARD_HOST, + ); + let (port_env_any, effective_port) = resolve_dashboard_value( + dashboard_port_env, + astrbot_dashboard_port_env, + desktop_config.port, + DEFAULT_DASHBOARD_PORT, + ); - let effective_host = dashboard_host_env - .as_deref() - .or(astrbot_dashboard_host_env.as_deref()) - .or(config_host); let has_explicit_skip_auth = astrbot_skip_auth_env.is_some() || legacy_skip_auth_env.is_some(); - if dashboard_host_env.is_none() && astrbot_dashboard_host_env.is_none() { - command.env( - DASHBOARD_HOST_ENV, - desktop_config - .host - .as_deref() - .unwrap_or(DEFAULT_DASHBOARD_HOST), - ); + if host_env_any.is_none() { + command.env(DASHBOARD_HOST_ENV, &effective_host); } - if dashboard_port_env.is_none() && astrbot_dashboard_port_env.is_none() { - command.env( - DASHBOARD_PORT_ENV, - desktop_config - .port - .as_deref() - .unwrap_or(DEFAULT_DASHBOARD_PORT), - ); + if port_env_any.is_none() { + command.env(DASHBOARD_PORT_ENV, &effective_port); } - if should_skip_default_password_auth(has_explicit_skip_auth, effective_host) { + if should_skip_default_password_auth(has_explicit_skip_auth, Some(effective_host.as_os_str())) { command.env(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, "true"); } } -fn read_desktop_dashboard_config(root_dir: Option<&Path>, mut log: F) -> DesktopDashboardConfig -where - F: FnMut(&str), -{ +fn resolve_dashboard_value( + primary_env: Option, + legacy_env: Option, + config: Option, + default: &str, +) -> (Option, OsString) { + let env_value = primary_env.or(legacy_env); + let effective = env_value + .clone() + .or_else(|| config.map(OsString::from)) + .unwrap_or_else(|| OsString::from(default)); + (env_value, effective) +} + +fn read_desktop_dashboard_config( + root_dir: Option<&Path>, + log: &mut dyn FnMut(&str), +) -> DesktopDashboardConfig { let Some(root_dir) = root_dir else { return DesktopDashboardConfig::default(); }; @@ -119,7 +170,7 @@ where return DesktopDashboardConfig::default(); } }; - let parsed: serde_json::Value = match serde_json::from_str(&raw) { + let parsed: DesktopFileConfig = match serde_json::from_str(&raw) { Ok(parsed) => parsed, Err(error) => { log(&format!( @@ -130,43 +181,27 @@ where return DesktopDashboardConfig::default(); } }; - let dashboard = parsed.get("dashboard"); - let (host, port) = if let Some(dashboard) = dashboard { - let raw_host = dashboard.get("host"); - let parsed_host = raw_host.and_then(parse_dashboard_host); - if raw_host.is_some() && parsed_host.is_none() { - log("desktop config: ignoring invalid dashboard.host value"); - } - - let raw_port = dashboard.get("port"); - let parsed_port = raw_port.and_then(parse_dashboard_port); - if raw_port.is_some() && parsed_port.is_none() { - log("desktop config: ignoring invalid dashboard.port value"); - } - - (parsed_host, parsed_port) - } else { - (None, None) - }; - - DesktopDashboardConfig { host, port } + DesktopDashboardConfig::from_file_config(parsed, log) } -fn parse_dashboard_host(value: &serde_json::Value) -> Option { - let host = value.as_str()?.trim(); - (!host.is_empty()).then(|| host.to_string()) +fn parse_dashboard_port_number(port: u64, log: &mut dyn FnMut(&str)) -> Option { + if (1..=65535).contains(&port) { + Some(port.to_string()) + } else { + log("desktop config: ignoring invalid dashboard.port value"); + None + } } -fn parse_dashboard_port(value: &serde_json::Value) -> Option { - if let Some(port) = value.as_u64() { - if (1..=65535).contains(&port) { - return Some(port.to_string()); +fn parse_dashboard_port_string(port: &str, log: &mut dyn FnMut(&str)) -> Option { + let trimmed = port.trim(); + match trimmed.parse::() { + Ok(parsed) => parse_dashboard_port_number(parsed, log), + Err(_) => { + log("desktop config: ignoring invalid dashboard.port value"); + None } - return None; } - let port = value.as_str()?.trim(); - let parsed = port.parse::().ok()?; - (parsed > 0).then(|| port.to_string()) } fn should_skip_default_password_auth( @@ -266,11 +301,8 @@ impl BackendState { if let Some(path_override) = backend_path_override() { command.env("PATH", path_override); } - configure_desktop_dashboard_environment( - &mut command, - plan.root_dir.as_deref(), - append_desktop_log, - ); + let mut log = |message: &str| append_desktop_log(message); + configure_desktop_dashboard_environment(&mut command, plan.root_dir.as_deref(), &mut log); #[cfg(target_os = "windows")] { if plan.packaged_mode { @@ -473,7 +505,7 @@ mod tests { with_clean_dashboard_env(|| { let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command, None, |_| {}); + configure_desktop_dashboard_environment(&mut command, None, &mut |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -494,7 +526,7 @@ mod tests { env::set_var(DASHBOARD_PORT_ENV, "7000"); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command, None, |_| {}); + configure_desktop_dashboard_environment(&mut command, None, &mut |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -511,7 +543,7 @@ mod tests { env::set_var(DASHBOARD_HOST_ENV, "0.0.0.0"); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command, None, |_| {}); + configure_desktop_dashboard_environment(&mut command, None, &mut |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -527,7 +559,7 @@ mod tests { env::set_var(DASHBOARD_HOST_ENV, std::ffi::OsString::from_vec(vec![0xff])); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command, None, |_| {}); + configure_desktop_dashboard_environment(&mut command, None, &mut |_| {}); assert_eq!( get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), @@ -554,7 +586,7 @@ mod tests { ); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command, Some(root.path()), |_| {}); + configure_desktop_dashboard_environment(&mut command, Some(root.path()), &mut |_| {}); assert_eq!( get_command_env_value(&command, DASHBOARD_HOST_ENV), @@ -583,7 +615,7 @@ mod tests { env::set_var(DASHBOARD_PORT_ENV, "7000"); let mut command = Command::new("sh"); - configure_desktop_dashboard_environment(&mut command, Some(root.path()), |_| {}); + configure_desktop_dashboard_environment(&mut command, Some(root.path()), &mut |_| {}); assert_eq!(get_command_env_value(&command, DASHBOARD_HOST_ENV), None); assert_eq!(get_command_env_value(&command, DASHBOARD_PORT_ENV), None); @@ -598,9 +630,11 @@ mod tests { let mut command = Command::new("sh"); let mut logs = Vec::new(); - configure_desktop_dashboard_environment(&mut command, Some(root.path()), |message| { - logs.push(message.to_string()) - }); + configure_desktop_dashboard_environment( + &mut command, + Some(root.path()), + &mut |message| logs.push(message.to_string()), + ); assert_eq!( get_command_env_value(&command, DASHBOARD_HOST_ENV), From 8a94f21e7db3516f84f334aef21d8e2f09ec6095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 4 Jun 2026 10:43:29 +0900 Subject: [PATCH 6/7] refactor: use cmd config for desktop dashboard listen --- README.md | 10 ++-- README_zh.md | 10 ++-- docs/environment-variables.md | 10 ++-- src-tauri/src/backend/launch.rs | 99 ++++++++++++++++++--------------- 4 files changed, 68 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 77a6aac..74afa28 100644 --- a/README.md +++ b/README.md @@ -96,18 +96,18 @@ Not really. AstrBot Desktop is intended for local desktop usage and personal wor ### How can I access the WebUI from another device on my LAN? -AstrBot Desktop listens on `127.0.0.1:6185` by default, so only the local machine can access the WebUI. If you explicitly want LAN access, set the dashboard host to `0.0.0.0` in the desktop config file. +AstrBot Desktop listens on `127.0.0.1:6185` by default, so only the local machine can access the WebUI. If you explicitly want LAN access, set the dashboard host to `0.0.0.0` in AstrBot's command config. Config file path: ```text -~/.astrbot/data/config/desktop.json +~/.astrbot/data/cmd_config.json ``` On Windows, this is usually: ```text -C:\Users\\.astrbot\data\config\desktop.json +C:\Users\\.astrbot\data\cmd_config.json ``` Write this content: @@ -127,9 +127,9 @@ Fully quit and restart AstrBot Desktop after saving the file, then visit this UR http://:6185/ ``` -To restore local-only access, remove the `dashboard` config or set `host` back to `127.0.0.1`, then restart the app. +To restore local-only access, set `dashboard.host` back to `127.0.0.1`, then restart the app. -Environment variables still work as advanced overrides and take precedence over `desktop.json`: +Environment variables still work as advanced overrides and take precedence over `cmd_config.json`: ```bash ASTRBOT_DASHBOARD_HOST=0.0.0.0 diff --git a/README_zh.md b/README_zh.md index 5bfe952..1654814 100644 --- a/README_zh.md +++ b/README_zh.md @@ -96,18 +96,18 @@ AstrBot Desktop 是面向本地桌面使用的 AstrBot 打包发行版。它内 ### 如何从局域网内其他设备访问 WebUI? -桌面端默认只监听 `127.0.0.1:6185`,仅允许本机访问。如果确认需要在同一局域网内访问,可以在桌面端配置文件中把 dashboard host 改为 `0.0.0.0`。 +桌面端默认只监听 `127.0.0.1:6185`,仅允许本机访问。如果确认需要在同一局域网内访问,可以在 AstrBot 命令配置中把 dashboard host 改为 `0.0.0.0`。 配置文件路径: ```text -~/.astrbot/data/config/desktop.json +~/.astrbot/data/cmd_config.json ``` Windows 对应路径通常是: ```text -C:\Users\<用户名>\.astrbot\data\config\desktop.json +C:\Users\<用户名>\.astrbot\data\cmd_config.json ``` 写入以下内容: @@ -127,9 +127,9 @@ C:\Users\<用户名>\.astrbot\data\config\desktop.json http://<运行 AstrBot Desktop 的主机内网 IP>:6185/ ``` -如需恢复默认本机访问,可删除 `dashboard` 配置,或把 `host` 改回 `127.0.0.1` 后重启应用。 +如需恢复默认本机访问,可把 `dashboard.host` 改回 `127.0.0.1` 后重启应用。 -环境变量仍可作为高级覆盖项,且优先级高于 `desktop.json`: +环境变量仍可作为高级覆盖项,且优先级高于 `cmd_config.json`: ```bash ASTRBOT_DASHBOARD_HOST=0.0.0.0 diff --git a/docs/environment-variables.md b/docs/environment-variables.md index a518c26..4493bbe 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -58,13 +58,13 @@ ### 局域网访问 WebUI -桌面端默认写入 `DASHBOARD_HOST=127.0.0.1`,因此 WebUI 默认仅允许本机访问。如果需要从同一局域网内的其他设备访问,推荐编辑桌面端配置文件: +桌面端默认写入 `DASHBOARD_HOST=127.0.0.1`,因此 WebUI 默认仅允许本机访问。如果需要从同一局域网内的其他设备访问,推荐编辑 AstrBot 命令配置: ```text -~/.astrbot/data/config/desktop.json +~/.astrbot/data/cmd_config.json ``` -Windows 对应路径通常为 `C:\Users\<用户名>\.astrbot\data\config\desktop.json`。 +Windows 对应路径通常为 `C:\Users\<用户名>\.astrbot\data\cmd_config.json`。 ```json { @@ -75,9 +75,9 @@ Windows 对应路径通常为 `C:\Users\<用户名>\.astrbot\data\config\desktop } ``` -设置后需要完全退出并重新启动 AstrBot Desktop,再通过 `http://<主机内网 IP>:6185/` 访问。开启前请确认系统防火墙允许端口 `6185`,并避免在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可删除 `dashboard` 配置,或将 `host` 设回 `127.0.0.1` 后重启应用。 +设置后需要完全退出并重新启动 AstrBot Desktop,再通过 `http://<主机内网 IP>:6185/` 访问。开启前请确认系统防火墙允许端口 `6185`,并避免在不可信网络或公网环境暴露该端口。如需恢复默认本机访问,可将 `dashboard.host` 设回 `127.0.0.1` 后重启应用。 -环境变量优先级高于 `desktop.json`。如果同时设置了 `ASTRBOT_DASHBOARD_HOST` / `DASHBOARD_HOST` 或 `ASTRBOT_DASHBOARD_PORT` / `DASHBOARD_PORT`,桌面端会优先保留环境变量值。 +环境变量优先级高于 `cmd_config.json`。如果同时设置了 `ASTRBOT_DASHBOARD_HOST` / `DASHBOARD_HOST` 或 `ASTRBOT_DASHBOARD_PORT` / `DASHBOARD_PORT`,桌面端会优先保留环境变量值。 ## 4. 发布/CI(GitHub Actions) diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index 5ef28ee..e4f6e7d 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -28,21 +28,21 @@ const ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = const DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV: &str = "DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH"; const DEFAULT_DASHBOARD_HOST: &str = "127.0.0.1"; const DEFAULT_DASHBOARD_PORT: &str = "6185"; -const DESKTOP_CONFIG_RELATIVE_PATH: &str = "data/config/desktop.json"; +const CMD_CONFIG_RELATIVE_PATH: &str = "data/cmd_config.json"; #[derive(Debug, Default)] -struct DesktopDashboardConfig { +struct CmdDashboardConfig { host: Option, port: Option, } #[derive(Debug, Default, Deserialize)] -struct DesktopFileConfig { - dashboard: Option, +struct CmdConfigFile { + dashboard: Option, } #[derive(Debug, Deserialize)] -struct DesktopFileDashboard { +struct CmdConfigDashboard { host: Option, port: Option, } @@ -54,8 +54,8 @@ enum DesktopPortField { Str(String), } -impl DesktopDashboardConfig { - fn from_file_config(config: DesktopFileConfig, log: &mut dyn FnMut(&str)) -> Self { +impl CmdDashboardConfig { + fn from_file_config(config: CmdConfigFile, log: &mut dyn FnMut(&str)) -> Self { let Some(dashboard) = config.dashboard else { return Self::default(); }; @@ -63,7 +63,9 @@ impl DesktopDashboardConfig { let host = dashboard.host.and_then(|host| { let trimmed = host.trim(); if trimmed.is_empty() { - log("desktop config: ignoring invalid dashboard.host value"); + log(&format!( + "cmd_config: ignoring invalid dashboard.host value: {host:?}" + )); None } else { Some(trimmed.to_string()) @@ -106,26 +108,26 @@ fn configure_desktop_dashboard_environment( let astrbot_dashboard_port_env = env::var_os(ASTRBOT_DASHBOARD_PORT_ENV); let astrbot_skip_auth_env = env::var_os(ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); let legacy_skip_auth_env = env::var_os(DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV); - let desktop_config = read_desktop_dashboard_config(root_dir, log); - let (host_env_any, effective_host) = resolve_dashboard_value( + let cmd_config = read_cmd_dashboard_config(root_dir, log); + let (has_host_env, effective_host) = resolve_dashboard_value( dashboard_host_env, astrbot_dashboard_host_env, - desktop_config.host, + cmd_config.host, DEFAULT_DASHBOARD_HOST, ); - let (port_env_any, effective_port) = resolve_dashboard_value( + let (has_port_env, effective_port) = resolve_dashboard_value( dashboard_port_env, astrbot_dashboard_port_env, - desktop_config.port, + cmd_config.port, DEFAULT_DASHBOARD_PORT, ); let has_explicit_skip_auth = astrbot_skip_auth_env.is_some() || legacy_skip_auth_env.is_some(); - if host_env_any.is_none() { + if !has_host_env { command.env(DASHBOARD_HOST_ENV, &effective_host); } - if port_env_any.is_none() { + if !has_port_env { command.env(DASHBOARD_PORT_ENV, &effective_port); } if should_skip_default_password_auth(has_explicit_skip_auth, Some(effective_host.as_os_str())) { @@ -138,57 +140,60 @@ fn resolve_dashboard_value( legacy_env: Option, config: Option, default: &str, -) -> (Option, OsString) { - let env_value = primary_env.or(legacy_env); - let effective = env_value - .clone() - .or_else(|| config.map(OsString::from)) +) -> (bool, OsString) { + if let Some(value) = primary_env.or(legacy_env) { + return (true, value); + } + let effective = config + .map(OsString::from) .unwrap_or_else(|| OsString::from(default)); - (env_value, effective) + (false, effective) } -fn read_desktop_dashboard_config( +fn read_cmd_dashboard_config( root_dir: Option<&Path>, log: &mut dyn FnMut(&str), -) -> DesktopDashboardConfig { +) -> CmdDashboardConfig { let Some(root_dir) = root_dir else { - return DesktopDashboardConfig::default(); + return CmdDashboardConfig::default(); }; - let config_path = root_dir.join(DESKTOP_CONFIG_RELATIVE_PATH); + let config_path = root_dir.join(CMD_CONFIG_RELATIVE_PATH); if !config_path.is_file() { - return DesktopDashboardConfig::default(); + return CmdDashboardConfig::default(); } let raw = match fs::read_to_string(&config_path) { Ok(raw) => raw, Err(error) => { log(&format!( - "failed to read desktop config {}: {}", + "failed to read cmd_config {}: {}", config_path.display(), error )); - return DesktopDashboardConfig::default(); + return CmdDashboardConfig::default(); } }; - let parsed: DesktopFileConfig = match serde_json::from_str(&raw) { + let parsed: CmdConfigFile = match serde_json::from_str(&raw) { Ok(parsed) => parsed, Err(error) => { log(&format!( - "failed to parse desktop config {}: {}", + "failed to parse cmd_config {}: {}", config_path.display(), error )); - return DesktopDashboardConfig::default(); + return CmdDashboardConfig::default(); } }; - DesktopDashboardConfig::from_file_config(parsed, log) + CmdDashboardConfig::from_file_config(parsed, log) } fn parse_dashboard_port_number(port: u64, log: &mut dyn FnMut(&str)) -> Option { if (1..=65535).contains(&port) { Some(port.to_string()) } else { - log("desktop config: ignoring invalid dashboard.port value"); + log(&format!( + "cmd_config: ignoring invalid dashboard.port value: {port}" + )); None } } @@ -198,7 +203,9 @@ fn parse_dashboard_port_string(port: &str, log: &mut dyn FnMut(&str)) -> Option< match trimmed.parse::() { Ok(parsed) => parse_dashboard_port_number(parsed, log), Err(_) => { - log("desktop config: ignoring invalid dashboard.port value"); + log(&format!( + "cmd_config: ignoring invalid dashboard.port value: {port:?}" + )); None } } @@ -411,9 +418,9 @@ mod tests { use super::{ configure_desktop_dashboard_environment, sanitize_packaged_python_environment, ASTRBOT_DASHBOARD_HOST_ENV, ASTRBOT_DASHBOARD_PORT_ENV, - ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, DASHBOARD_HOST_ENV, DASHBOARD_PORT_ENV, - DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, DEFAULT_DASHBOARD_HOST, DEFAULT_DASHBOARD_PORT, - DESKTOP_CONFIG_RELATIVE_PATH, + ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, CMD_CONFIG_RELATIVE_PATH, + DASHBOARD_HOST_ENV, DASHBOARD_PORT_ENV, DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV, + DEFAULT_DASHBOARD_HOST, DEFAULT_DASHBOARD_PORT, }; static ENV_TEST_LOCK: Mutex<()> = Mutex::new(()); @@ -569,18 +576,18 @@ mod tests { }); } - fn write_desktop_config(root: &Path, contents: &str) { - let config_path = root.join(DESKTOP_CONFIG_RELATIVE_PATH); + fn write_cmd_config(root: &Path, contents: &str) { + let config_path = root.join(CMD_CONFIG_RELATIVE_PATH); fs::create_dir_all(config_path.parent().expect("config parent")) .expect("create config dir"); - fs::write(config_path, contents).expect("write desktop config"); + fs::write(config_path, contents).expect("write cmd config"); } #[test] - fn configure_desktop_dashboard_environment_reads_desktop_config() { + fn configure_desktop_dashboard_environment_reads_cmd_config() { with_clean_dashboard_env(|| { let root = tempfile::tempdir().expect("temp root"); - write_desktop_config( + write_cmd_config( root.path(), r#"{"dashboard":{"host":"0.0.0.0","port":6185}}"#, ); @@ -604,10 +611,10 @@ mod tests { } #[test] - fn configure_desktop_dashboard_environment_env_overrides_desktop_config() { + fn configure_desktop_dashboard_environment_env_overrides_cmd_config() { with_clean_dashboard_env(|| { let root = tempfile::tempdir().expect("temp root"); - write_desktop_config( + write_cmd_config( root.path(), r#"{"dashboard":{"host":"0.0.0.0","port":6185}}"#, ); @@ -623,10 +630,10 @@ mod tests { } #[test] - fn configure_desktop_dashboard_environment_ignores_invalid_desktop_config() { + fn configure_desktop_dashboard_environment_ignores_invalid_cmd_config() { with_clean_dashboard_env(|| { let root = tempfile::tempdir().expect("temp root"); - write_desktop_config(root.path(), r#"{"dashboard":{"host":" ","port":70000}}"#); + write_cmd_config(root.path(), r#"{"dashboard":{"host":" ","port":70000}}"#); let mut command = Command::new("sh"); let mut logs = Vec::new(); From 15a0d80e5454b6858be20a0bfbf75f7d71d92bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 4 Jun 2026 10:48:46 +0900 Subject: [PATCH 7/7] fix: ignore blank dashboard env overrides --- src-tauri/src/backend/launch.rs | 115 ++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/src-tauri/src/backend/launch.rs b/src-tauri/src/backend/launch.rs index e4f6e7d..d0e15a4 100644 --- a/src-tauri/src/backend/launch.rs +++ b/src-tauri/src/backend/launch.rs @@ -10,6 +10,7 @@ use std::{ use std::os::windows::process::CommandExt; use serde::Deserialize; +use serde_json::Value; use tauri::AppHandle; use crate::{ @@ -44,14 +45,7 @@ struct CmdConfigFile { #[derive(Debug, Deserialize)] struct CmdConfigDashboard { host: Option, - port: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(untagged)] -enum DesktopPortField { - Num(u64), - Str(String), + port: Option, } impl CmdDashboardConfig { @@ -72,9 +66,42 @@ impl CmdDashboardConfig { } }); - let port = dashboard.port.and_then(|port| match port { - DesktopPortField::Num(port) => parse_dashboard_port_number(port, log), - DesktopPortField::Str(port) => parse_dashboard_port_string(&port, log), + let port = dashboard.port.and_then(|value| match value { + Value::Number(port) => match port.as_u64() { + Some(port) if (1..=65535).contains(&port) => Some(port.to_string()), + Some(port) => { + log(&format!( + "cmd_config: ignoring invalid dashboard.port value: {port}" + )); + None + } + None => { + log("cmd_config: ignoring non-u64 dashboard.port number"); + None + } + }, + Value::String(port) => { + let trimmed = port.trim(); + match trimmed.parse::() { + Ok(port) if (1..=65535).contains(&port) => Some(port.to_string()), + Ok(port) => { + log(&format!( + "cmd_config: ignoring invalid dashboard.port value: {port}" + )); + None + } + Err(_) => { + log(&format!( + "cmd_config: ignoring invalid dashboard.port value: {port:?}" + )); + None + } + } + } + _ => { + log("cmd_config: ignoring non-number/string dashboard.port value"); + None + } }); Self { host, port } @@ -141,7 +168,9 @@ fn resolve_dashboard_value( config: Option, default: &str, ) -> (bool, OsString) { - if let Some(value) = primary_env.or(legacy_env) { + if let Some(value) = + non_blank_env_value(primary_env).or_else(|| non_blank_env_value(legacy_env)) + { return (true, value); } let effective = config @@ -150,6 +179,15 @@ fn resolve_dashboard_value( (false, effective) } +fn non_blank_env_value(value: Option) -> Option { + value.filter(|value| { + value + .to_str() + .map(|value| !value.trim().is_empty()) + .unwrap_or(true) + }) +} + fn read_cmd_dashboard_config( root_dir: Option<&Path>, log: &mut dyn FnMut(&str), @@ -187,30 +225,6 @@ fn read_cmd_dashboard_config( CmdDashboardConfig::from_file_config(parsed, log) } -fn parse_dashboard_port_number(port: u64, log: &mut dyn FnMut(&str)) -> Option { - if (1..=65535).contains(&port) { - Some(port.to_string()) - } else { - log(&format!( - "cmd_config: ignoring invalid dashboard.port value: {port}" - )); - None - } -} - -fn parse_dashboard_port_string(port: &str, log: &mut dyn FnMut(&str)) -> Option { - let trimmed = port.trim(); - match trimmed.parse::() { - Ok(parsed) => parse_dashboard_port_number(parsed, log), - Err(_) => { - log(&format!( - "cmd_config: ignoring invalid dashboard.port value: {port:?}" - )); - None - } - } -} - fn should_skip_default_password_auth( has_explicit_skip_auth: bool, effective_host: Option<&OsStr>, @@ -629,6 +643,35 @@ mod tests { }); } + #[test] + fn configure_desktop_dashboard_environment_ignores_blank_dashboard_env() { + with_clean_dashboard_env(|| { + let root = tempfile::tempdir().expect("temp root"); + write_cmd_config( + root.path(), + r#"{"dashboard":{"host":"0.0.0.0","port":"7000"}}"#, + ); + env::set_var(DASHBOARD_HOST_ENV, " "); + env::set_var(DASHBOARD_PORT_ENV, ""); + let mut command = Command::new("sh"); + + configure_desktop_dashboard_environment(&mut command, Some(root.path()), &mut |_| {}); + + assert_eq!( + get_command_env_value(&command, DASHBOARD_HOST_ENV), + Some(Some("0.0.0.0".to_string())) + ); + assert_eq!( + get_command_env_value(&command, DASHBOARD_PORT_ENV), + Some(Some("7000".to_string())) + ); + assert_eq!( + get_command_env_value(&command, ASTRBOT_DASHBOARD_SKIP_DEFAULT_PASSWORD_AUTH_ENV), + None + ); + }); + } + #[test] fn configure_desktop_dashboard_environment_ignores_invalid_cmd_config() { with_clean_dashboard_env(|| {