Skip to content

Commit 314ed40

Browse files
committed
feat(ostool): migrate legacy board server_ip config to URL format
1 parent 92a5094 commit 314ed40

4 files changed

Lines changed: 80 additions & 13 deletions

File tree

README.en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ Use the TUI editor to update it:
317317
ostool board config
318318
```
319319

320-
`server` should be a complete URL including `http://` or `https://`; the optional `port` overrides the URL port. For legacy LAN configurations, a bare IPv4 or IPv6 address is interpreted as `http://`; bare host names are not supported, and the removed `server_ip` field is not restored. Project-local `.board.toml` `server` / `port` fields still apply to `ostool board run`, with precedence lower than CLI flags and higher than the global config.
320+
`server` should be a complete URL including `http://` or `https://`; the optional `port` overrides the URL port. For legacy LAN configurations, a bare IPv4 or IPv6 address is interpreted as `http://`. The base release's persisted `server_ip` / `port` pair is also migrated to `server` / `port` when read; the next configuration save writes only the new format. Bare host names are not supported. Project-local `.board.toml` `server` / `port` fields still apply to `ostool board run`, with precedence lower than CLI flags and higher than the global config.
321321

322322
### Public board authentication
323323

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ auth_mode = "disabled"
314314
ostool board config
315315
```
316316

317-
`server` 应使用包含 `http://``https://` 的完整 URL;可选的 `port` 会覆盖 URL 中的端口。为兼容旧的局域网配置,裸 IPv4 或 IPv6 地址会自动补为 `http://`;不支持无 scheme 的主机名,也不恢复已移除的 `server_ip` 字段。项目级 `.board.toml` 中的 `server` / `port` 仍可用于 `ostool board run`,其优先级低于命令行参数,高于全局配置。
317+
`server` 应使用包含 `http://``https://` 的完整 URL;可选的 `port` 会覆盖 URL 中的端口。为兼容旧的局域网配置,裸 IPv4 或 IPv6 地址会自动补为 `http://`。基线版本写出的 `server_ip` / `port` 也会在读取时迁移为 `server` / `port`,下一次保存配置时只写新格式;无 scheme 的主机名不支持。项目级 `.board.toml` 中的 `server` / `port` 仍可用于 `ostool board run`,其优先级低于命令行参数,高于全局配置。
318318

319319
### 公网开发板认证
320320

docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
本文根据当前 `ostool` 客户端实现整理认证网关和开发板服务的调用接口;不包含 `ostool-server` 管理后台的 `/api/v1/admin/...` 接口。
44

5-
服务地址来自全局或项目配置中的 `board.server`(完整 URL),可被命令行 `--server` 覆盖;可选的 `board.port``--port` 用于覆盖 URL 中的端口。为兼容旧的局域网配置,`board.server` 为裸 IPv4 或 IPv6 地址时客户端自动补为 `http://`;无 scheme 的主机名不支持,已移除的 `server_ip` 字段也不会恢复。认证网关和 board API 使用同一个 Base URL。
5+
服务地址来自全局或项目配置中的 `board.server`(完整 URL),可被命令行 `--server` 覆盖;可选的 `board.port``--port` 用于覆盖 URL 中的端口。为兼容旧的局域网配置,`board.server` 为裸 IPv4 或 IPv6 地址时客户端自动补为 `http://`。基线版本写出的 `board.server_ip` / `board.port` 也会在读取时迁移为 `board.server` / `board.port`,下一次保存配置时只写新格式;无 scheme 的主机名不支持。认证网关和 board API 使用同一个 Base URL。
66

77
- `auth_mode = "required"` 时,`board.server` 必须使用 HTTPS,所有请求携带下文描述的 Bearer Token;
88
- `auth_mode = "disabled"`(默认)时通常使用 HTTP,不会发送认证 Header,适合局域网直连 `ostool-server`

ostool/src/board/global_config.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
use anyhow::{Context, bail};
88
use reqwest::Url;
99
use schemars::JsonSchema;
10-
use serde::{Deserialize, Serialize};
10+
use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
1111

1212
// Keep the URL-based configuration aligned with ostool-server's established
1313
// default listen port so a newly generated config works without user edits.
@@ -90,8 +90,7 @@ pub struct BoardGlobalConfigFile {
9090
pub board: BoardGlobalConfig,
9191
}
9292

93-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
94-
#[serde(deny_unknown_fields)]
93+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
9594
pub struct BoardGlobalConfig {
9695
/// Complete board service URL, including its scheme and optional base path.
9796
#[serde(default = "default_server")]
@@ -103,6 +102,53 @@ pub struct BoardGlobalConfig {
103102
pub auth_mode: AuthMode,
104103
}
105104

105+
#[derive(Deserialize)]
106+
#[serde(deny_unknown_fields)]
107+
struct BoardGlobalConfigInput {
108+
#[serde(default)]
109+
server: Option<String>,
110+
// The base release persisted this field. It exists only in the input
111+
// representation so upgraded installations can be read and rewritten in
112+
// the URL-based format; new configuration is always serialized as `server`.
113+
#[serde(default)]
114+
server_ip: Option<String>,
115+
#[serde(default)]
116+
port: Option<u16>,
117+
#[serde(default)]
118+
auth_mode: AuthMode,
119+
}
120+
121+
impl<'de> Deserialize<'de> for BoardGlobalConfig {
122+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123+
where
124+
D: Deserializer<'de>,
125+
{
126+
let input = BoardGlobalConfigInput::deserialize(deserializer)?;
127+
let server = match (input.server, input.server_ip) {
128+
(Some(server), None) => server,
129+
(None, Some(server_ip)) => {
130+
let server_ip = server_ip.trim();
131+
match server_ip.parse::<IpAddr>() {
132+
Ok(IpAddr::V6(_)) => format!("http://[{server_ip}]"),
133+
Ok(IpAddr::V4(_)) | Err(_) => format!("http://{server_ip}"),
134+
}
135+
}
136+
(None, None) => default_server(),
137+
(Some(_), Some(_)) => {
138+
return Err(D::Error::custom(
139+
"`board.server` and legacy `board.server_ip` cannot be used together",
140+
));
141+
}
142+
};
143+
144+
Ok(Self {
145+
server,
146+
port: input.port,
147+
auth_mode: input.auth_mode,
148+
})
149+
}
150+
}
151+
106152
impl Default for BoardGlobalConfig {
107153
fn default() -> Self {
108154
Self {
@@ -213,7 +259,7 @@ fn write_config_file(path: &Path, file: &BoardGlobalConfigFile) -> anyhow::Resul
213259
mod tests {
214260
use tempfile::tempdir;
215261

216-
use super::{AuthMode, BoardGlobalConfig, BoardGlobalConfigFile, LoadedBoardGlobalConfig};
262+
use super::{AuthMode, BoardGlobalConfig, LoadedBoardGlobalConfig};
217263

218264
#[test]
219265
fn load_or_create_creates_url_based_default_config_when_missing() {
@@ -327,15 +373,36 @@ mod tests {
327373
}
328374

329375
#[test]
330-
fn legacy_config_fields_are_rejected() {
331-
let err = toml::from_str::<BoardGlobalConfigFile>(
376+
fn load_migrates_base_server_ip_config_and_save_writes_url_format() {
377+
let temp = tempdir().unwrap();
378+
let path = temp.path().join(".ostool/config.toml");
379+
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
380+
std::fs::write(
381+
&path,
332382
r#"
333383
[board]
334-
server_ip = "10.0.0.2"
335-
port = 9000
384+
server_ip = "localhost"
385+
port = 2999
336386
"#,
337387
)
338-
.unwrap_err();
339-
assert!(err.to_string().contains("server_ip"));
388+
.unwrap();
389+
390+
let loaded = LoadedBoardGlobalConfig::load_or_create_at(&path).unwrap();
391+
assert!(!loaded.created);
392+
assert_eq!(loaded.board.server, "http://localhost");
393+
assert_eq!(loaded.board.port, Some(2999));
394+
assert_eq!(
395+
loaded
396+
.resolve_endpoint(None, None)
397+
.unwrap()
398+
.base_url
399+
.as_str(),
400+
"http://localhost:2999/"
401+
);
402+
403+
loaded.save().unwrap();
404+
let content = std::fs::read_to_string(path).unwrap();
405+
assert!(content.contains("server = \"http://localhost\""));
406+
assert!(!content.contains("server_ip"));
340407
}
341408
}

0 commit comments

Comments
 (0)