mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(proxy): 增加 0.1.x 到 0.2.0 配置自动迁移,放宽 max_retries 上限至 999
- proxy: 启动时检测旧版配置并自动迁移(delegate_* -> upstream_*、单服务器 -> [[servers]]),备份原文件为 .v1.bak - proxy: 升级后 systemd 重启改为 best-effort,失败不中断升级流程 - provider: max_retries 上限从 10 放宽到 999(前端、后端模型同步调整)
This commit is contained in:
2
aether-proxy/Cargo.lock
generated
2
aether-proxy/Cargo.lock
generated
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aether-proxy"
|
||||
version = "0.1.6"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@@ -3,6 +3,36 @@ use std::path::Path;
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Fields that existed in 0.1.x but were removed in 0.2.0.
|
||||
const LEGACY_ONLY_KEYS: &[&str] = &[
|
||||
"hmac_key",
|
||||
"listen_port",
|
||||
"timestamp_tolerance",
|
||||
"connect_timeout_secs",
|
||||
"tls_handshake_timeout_secs",
|
||||
"enable_tls",
|
||||
"tls_cert",
|
||||
"tls_key",
|
||||
];
|
||||
|
||||
/// Fields renamed from 0.1.x `delegate_*` to 0.2.0 `upstream_*`.
|
||||
const DELEGATE_TO_UPSTREAM: &[(&str, &str)] = &[
|
||||
(
|
||||
"delegate_connect_timeout_secs",
|
||||
"upstream_connect_timeout_secs",
|
||||
),
|
||||
(
|
||||
"delegate_pool_max_idle_per_host",
|
||||
"upstream_pool_max_idle_per_host",
|
||||
),
|
||||
(
|
||||
"delegate_pool_idle_timeout_secs",
|
||||
"upstream_pool_idle_timeout_secs",
|
||||
),
|
||||
("delegate_tcp_keepalive_secs", "upstream_tcp_keepalive_secs"),
|
||||
("delegate_tcp_nodelay", "upstream_tcp_nodelay"),
|
||||
];
|
||||
|
||||
/// Aether tunnel proxy.
|
||||
///
|
||||
/// Deployed on overseas VPS to relay API traffic for Aether instances
|
||||
@@ -298,6 +328,84 @@ impl ConfigFile {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect and migrate a 0.1.x config file to 0.2.0 format in-place.
|
||||
///
|
||||
/// Returns `true` if migration was performed, `false` if already current.
|
||||
/// The original file is backed up as `<name>.v1.bak` before rewriting.
|
||||
pub fn migrate_legacy(path: &Path) -> anyhow::Result<bool> {
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let mut table: toml::map::Map<String, toml::Value> = toml::from_str(&content)?;
|
||||
|
||||
// Detect legacy format: presence of any 0.1.x-only key.
|
||||
let is_legacy = LEGACY_ONLY_KEYS.iter().any(|k| table.contains_key(*k))
|
||||
|| DELEGATE_TO_UPSTREAM
|
||||
.iter()
|
||||
.any(|(old, _)| table.contains_key(*old));
|
||||
|
||||
if !is_legacy {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 1. Rename delegate_* -> upstream_* (carry over user-customized values)
|
||||
for &(old, new) in DELEGATE_TO_UPSTREAM {
|
||||
if let Some(val) = table.remove(old) {
|
||||
table.entry(new.to_string()).or_insert(val);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Build [[servers]] from top-level aether_url + management_token + node_name
|
||||
if !table.contains_key("servers") {
|
||||
let aether_url = table.get("aether_url").and_then(|v| v.as_str());
|
||||
let management_token = table.get("management_token").and_then(|v| v.as_str());
|
||||
if let (Some(url), Some(token)) = (aether_url, management_token) {
|
||||
let mut entry = toml::map::Map::new();
|
||||
entry.insert("aether_url".into(), toml::Value::String(url.to_string()));
|
||||
entry.insert(
|
||||
"management_token".into(),
|
||||
toml::Value::String(token.to_string()),
|
||||
);
|
||||
if let Some(name) = table.get("node_name").and_then(|v| v.as_str()) {
|
||||
entry.insert("node_name".into(), toml::Value::String(name.to_string()));
|
||||
}
|
||||
table.insert(
|
||||
"servers".into(),
|
||||
toml::Value::Array(vec![toml::Value::Table(entry)]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Remove top-level fields that are now in [[servers]] or obsolete
|
||||
table.remove("aether_url");
|
||||
table.remove("management_token");
|
||||
table.remove("node_name");
|
||||
for &key in LEGACY_ONLY_KEYS {
|
||||
table.remove(key);
|
||||
}
|
||||
|
||||
// 4. Backup original file (abort migration if backup fails)
|
||||
let backup_path = path.with_extension("v1.bak");
|
||||
std::fs::copy(path, &backup_path).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to backup config before migration: {} -> {}: {}",
|
||||
path.display(),
|
||||
backup_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
// 5. Write migrated config
|
||||
let new_content = toml::to_string_pretty(&table)?;
|
||||
std::fs::write(path, &new_content)?;
|
||||
|
||||
eprintln!(" Config migrated from 0.1.x to 0.2.0 format.");
|
||||
eprintln!(" Backup saved: {}", backup_path.display());
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Resolve the effective server list.
|
||||
///
|
||||
/// If `[[servers]]` is present, use it. Otherwise fall back to the
|
||||
|
||||
@@ -56,8 +56,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Load config file as env-var defaults (before clap parsing)
|
||||
let config_file_path =
|
||||
std::env::var("AETHER_PROXY_CONFIG").unwrap_or_else(|_| DEFAULT_CONFIG.to_string());
|
||||
if std::path::Path::new(&config_file_path).exists() {
|
||||
if let Ok(file_cfg) = config::ConfigFile::load(std::path::Path::new(&config_file_path)) {
|
||||
let config_path = std::path::Path::new(&config_file_path);
|
||||
if config_path.exists() {
|
||||
// Migrate legacy 0.1.x config to 0.2.0 format if needed
|
||||
if let Err(e) = config::ConfigFile::migrate_legacy(config_path) {
|
||||
eprintln!(" WARNING: config migration failed: {}", e);
|
||||
}
|
||||
if let Ok(file_cfg) = config::ConfigFile::load(config_path) {
|
||||
file_cfg.inject_env();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,15 +341,22 @@ pub async fn cmd_upgrade(version: Option<String>) -> anyhow::Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
// Restart systemd service if running
|
||||
// Restart systemd service if running.
|
||||
// Use best-effort: binary is already replaced, so a restart failure should
|
||||
// not abort the whole upgrade -- the user can restart manually.
|
||||
if super::service::is_service_active() {
|
||||
if super::service::is_root() {
|
||||
eprintln!(" Restarting systemd service...");
|
||||
super::service::run_cmd("systemctl", &["restart", "aether-proxy"])?;
|
||||
eprintln!(" Service restarted.");
|
||||
match super::service::run_cmd("systemctl", &["restart", "aether-proxy"]) {
|
||||
Ok(()) => eprintln!(" Service restarted."),
|
||||
Err(e) => {
|
||||
eprintln!(" WARNING: failed to restart service: {}", e);
|
||||
eprintln!(" Run manually: sudo systemctl restart aether-proxy");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!(" Systemd service is active, but restart requires root.");
|
||||
eprintln!(" Run: sudo aether-proxy restart");
|
||||
eprintln!(" Run: sudo systemctl restart aether-proxy");
|
||||
eprintln!(" Skipping restart.");
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
:model-value="form.max_retries ?? ''"
|
||||
type="number"
|
||||
min="0"
|
||||
max="10"
|
||||
max="999"
|
||||
placeholder="默认 2"
|
||||
@update:model-value="(v) => form.max_retries = parseNumberInput(v)"
|
||||
/>
|
||||
|
||||
@@ -232,7 +232,7 @@ class CreateProviderRequest(BaseModel):
|
||||
is_active: bool | None = Field(True, description="是否启用")
|
||||
concurrent_limit: int | None = Field(None, ge=0, description="并发限制")
|
||||
# 请求配置(从 Endpoint 迁移)
|
||||
max_retries: int | None = Field(2, ge=0, le=10, description="最大重试次数")
|
||||
max_retries: int | None = Field(2, ge=0, le=999, description="最大重试次数")
|
||||
proxy: ProxyConfig | None = Field(None, description="代理配置")
|
||||
# 超时配置(秒),为空时使用全局配置
|
||||
stream_first_byte_timeout: float | None = Field(
|
||||
@@ -341,7 +341,7 @@ class UpdateProviderRequest(BaseModel):
|
||||
is_active: bool | None = None
|
||||
concurrent_limit: int | None = Field(None, ge=0)
|
||||
# 请求配置(从 Endpoint 迁移)
|
||||
max_retries: int | None = Field(None, ge=0, le=10, description="最大重试次数")
|
||||
max_retries: int | None = Field(None, ge=0, le=999, description="最大重试次数")
|
||||
proxy: ProxyConfig | None = Field(None, description="代理配置")
|
||||
# 超时配置(秒),为空时使用全局配置
|
||||
stream_first_byte_timeout: float | None = Field(
|
||||
|
||||
@@ -274,7 +274,7 @@ class ProviderEndpointCreate(BaseModel):
|
||||
description="请求体规则列表,支持 set/drop/rename/append/insert/regex_replace 操作",
|
||||
)
|
||||
|
||||
max_retries: int = Field(default=2, ge=0, le=10, description="最大重试次数")
|
||||
max_retries: int = Field(default=2, ge=0, le=999, description="最大重试次数")
|
||||
|
||||
# 额外配置
|
||||
config: dict[str, Any] | None = Field(default=None, description="额外配置(JSON)")
|
||||
@@ -338,7 +338,7 @@ class ProviderEndpointUpdate(BaseModel):
|
||||
description="请求体规则列表,支持 set/drop/rename/append/insert/regex_replace 操作",
|
||||
)
|
||||
|
||||
max_retries: int | None = Field(default=None, ge=0, le=10, description="最大重试次数")
|
||||
max_retries: int | None = Field(default=None, ge=0, le=999, description="最大重试次数")
|
||||
is_active: bool | None = Field(default=None, description="是否启用")
|
||||
config: dict[str, Any] | None = Field(default=None, description="额外配置")
|
||||
proxy: ProxyConfig | None = Field(default=None, description="代理配置")
|
||||
|
||||
Reference in New Issue
Block a user