Skip to content

Commit 761a319

Browse files
committed
Preserve manual config customizations
1 parent 943f63f commit 761a319

1 file changed

Lines changed: 123 additions & 48 deletions

File tree

src/config.rs

Lines changed: 123 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,31 @@ const DEFAULT_APP_CONFIG: &str = include_str!("../assets/defaults/linuxdo-accele
99

1010
#[derive(Debug, Clone, Serialize, Deserialize)]
1111
pub struct AppConfig {
12+
#[serde(default = "default_listen_host")]
1213
pub listen_host: String,
14+
#[serde(default = "default_hosts_ip")]
1315
pub hosts_ip: String,
16+
#[serde(default = "default_http_port")]
1417
pub http_port: u16,
18+
#[serde(default = "default_https_port")]
1519
pub https_port: u16,
20+
#[serde(default = "default_upstream")]
1621
pub upstream: String,
1722
#[serde(default = "default_fake_sni")]
1823
pub fake_sni: Option<String>,
24+
#[serde(default = "default_doh_endpoints")]
1925
pub doh_endpoints: Vec<String>,
2026
#[serde(default)]
2127
pub dns_hosts: BTreeMap<String, String>,
28+
#[serde(default = "default_proxy_domains")]
2229
pub proxy_domains: Vec<String>,
2330
#[serde(default)]
2431
pub hosts_domains: Vec<String>,
32+
#[serde(default = "default_certificate_domains")]
2533
pub certificate_domains: Vec<String>,
34+
#[serde(default = "default_ca_common_name")]
2635
pub ca_common_name: String,
36+
#[serde(default = "default_server_common_name")]
2737
pub server_common_name: String,
2838
}
2939

@@ -57,6 +67,46 @@ fn default_fake_sni() -> Option<String> {
5767
Some("www.cloudflare.com".to_string())
5868
}
5969

70+
fn default_listen_host() -> String {
71+
default_app_config().listen_host
72+
}
73+
74+
fn default_hosts_ip() -> String {
75+
default_app_config().hosts_ip
76+
}
77+
78+
fn default_http_port() -> u16 {
79+
default_app_config().http_port
80+
}
81+
82+
fn default_https_port() -> u16 {
83+
default_app_config().https_port
84+
}
85+
86+
fn default_upstream() -> String {
87+
default_app_config().upstream
88+
}
89+
90+
fn default_doh_endpoints() -> Vec<String> {
91+
default_app_config().doh_endpoints
92+
}
93+
94+
fn default_proxy_domains() -> Vec<String> {
95+
default_app_config().proxy_domains
96+
}
97+
98+
fn default_certificate_domains() -> Vec<String> {
99+
default_app_config().certificate_domains
100+
}
101+
102+
fn default_ca_common_name() -> String {
103+
default_app_config().ca_common_name
104+
}
105+
106+
fn default_server_common_name() -> String {
107+
default_app_config().server_common_name
108+
}
109+
60110
fn default_app_config() -> AppConfig {
61111
toml::from_str(DEFAULT_APP_CONFIG)
62112
.expect("assets/defaults/linuxdo-accelerator.toml must be valid TOML")
@@ -86,23 +136,7 @@ impl AppConfig {
86136
let content = fs::read_to_string(path)
87137
.with_context(|| format!("failed to read config {}", path.display()))?;
88138

89-
if let Ok(mut config) = toml::from_str::<AppConfig>(&content) {
90-
let defaults = default_app_config();
91-
let mut changed = false;
92-
changed |= merge_missing_values(&mut config.doh_endpoints, defaults.doh_endpoints);
93-
changed |= merge_missing_values(&mut config.proxy_domains, defaults.proxy_domains);
94-
changed |= merge_missing_values(&mut config.hosts_domains, defaults.hosts_domains);
95-
changed |= merge_missing_values(
96-
&mut config.certificate_domains,
97-
defaults.certificate_domains,
98-
);
99-
100-
let serialized =
101-
toml::to_string_pretty(&config).context("failed to serialize config")?;
102-
if changed || normalize_toml(&content) != normalize_toml(&serialized) {
103-
fs::write(path, serialized)
104-
.with_context(|| format!("failed to rewrite config {}", path.display()))?;
105-
}
139+
if let Ok(config) = toml::from_str::<AppConfig>(&content) {
106140
cleanup_legacy_network_profile(path)?;
107141
return Ok(config);
108142
}
@@ -111,7 +145,7 @@ impl AppConfig {
111145
.with_context(|| format!("failed to parse config {}", path.display()))?;
112146
let legacy_network = load_legacy_network_profile(path)?;
113147
let defaults = default_app_config();
114-
let mut config = AppConfig {
148+
let config = AppConfig {
115149
listen_host: legacy.listen_host,
116150
hosts_ip: legacy.hosts_ip,
117151
http_port: legacy.http_port,
@@ -121,27 +155,23 @@ impl AppConfig {
121155
doh_endpoints: legacy_network
122156
.as_ref()
123157
.map(|value| value.doh_endpoints.clone())
124-
.unwrap_or_default(),
158+
.filter(|value| !value.is_empty())
159+
.unwrap_or_else(default_doh_endpoints),
125160
dns_hosts: BTreeMap::new(),
126161
proxy_domains: legacy_network
127162
.as_ref()
128163
.map(|value| value.proxy_domains.clone())
129-
.unwrap_or_default(),
130-
hosts_domains: Vec::new(),
164+
.filter(|value| !value.is_empty())
165+
.unwrap_or_else(default_proxy_domains),
166+
hosts_domains: defaults.hosts_domains,
131167
certificate_domains: legacy_network
132168
.as_ref()
133169
.map(|value| value.certificate_domains.clone())
134-
.unwrap_or_default(),
170+
.filter(|value| !value.is_empty())
171+
.unwrap_or_else(default_certificate_domains),
135172
ca_common_name: legacy.ca_common_name,
136173
server_common_name: legacy.server_common_name,
137174
};
138-
merge_missing_values(&mut config.doh_endpoints, defaults.doh_endpoints);
139-
merge_missing_values(&mut config.proxy_domains, defaults.proxy_domains);
140-
merge_missing_values(&mut config.hosts_domains, defaults.hosts_domains);
141-
merge_missing_values(
142-
&mut config.certificate_domains,
143-
defaults.certificate_domains,
144-
);
145175

146176
let serialized =
147177
toml::to_string_pretty(&config).context("failed to serialize migrated config")?;
@@ -239,25 +269,6 @@ fn cleanup_legacy_network_profile(path: &Path) -> Result<()> {
239269
Ok(())
240270
}
241271

242-
fn merge_missing_values(target: &mut Vec<String>, defaults: Vec<String>) -> bool {
243-
let mut changed = false;
244-
for value in defaults {
245-
if !target.iter().any(|existing| existing == &value) {
246-
target.push(value);
247-
changed = true;
248-
}
249-
}
250-
changed
251-
}
252-
253-
fn normalize_toml(content: &str) -> String {
254-
content
255-
.lines()
256-
.map(str::trim_end)
257-
.collect::<Vec<_>>()
258-
.join("\n")
259-
}
260-
261272
#[cfg(test)]
262273
mod tests {
263274
use super::{AppConfig, DEFAULT_APP_CONFIG};
@@ -280,6 +291,70 @@ mod tests {
280291
cleanup_test_dir(&root);
281292
}
282293

294+
#[test]
295+
fn load_or_create_does_not_restore_removed_default_entries() {
296+
let root = create_test_dir("preserve-custom-lists");
297+
let config_path = root.join("linuxdo-accelerator.toml");
298+
let customized = r#"
299+
listen_host = "127.0.0.1"
300+
hosts_ip = "127.0.0.1"
301+
http_port = 8080
302+
https_port = 8443
303+
upstream = "https://linux.do"
304+
fake_sni = "www.cloudflare.com"
305+
doh_endpoints = ["https://1.1.1.1/dns-query"]
306+
managed_prefer_ipv6 = false
307+
dns_hosts = {}
308+
proxy_domains = ["linux.do"]
309+
hosts_domains = ["linux.do"]
310+
certificate_domains = ["linux.do"]
311+
ca_common_name = "Linux.do Accelerator Root CA"
312+
server_common_name = "linux.do"
313+
"#;
314+
std::fs::write(&config_path, customized).unwrap();
315+
316+
let config = AppConfig::load_or_create(&config_path).unwrap();
317+
let reloaded = std::fs::read_to_string(&config_path).unwrap();
318+
319+
assert_eq!(config.proxy_domains, vec!["linux.do"]);
320+
assert_eq!(config.hosts_domains, vec!["linux.do"]);
321+
assert_eq!(config.certificate_domains, vec!["linux.do"]);
322+
assert_eq!(config.doh_endpoints, vec!["https://1.1.1.1/dns-query"]);
323+
assert_eq!(reloaded, customized);
324+
325+
cleanup_test_dir(&root);
326+
}
327+
328+
#[test]
329+
fn load_or_create_uses_defaults_for_missing_fields_without_rewriting() {
330+
let root = create_test_dir("missing-fields");
331+
let config_path = root.join("linuxdo-accelerator.toml");
332+
let customized = r#"
333+
listen_host = "127.0.0.1"
334+
hosts_ip = "127.0.0.1"
335+
upstream = "https://linux.do"
336+
doh_endpoints = ["https://1.1.1.1/dns-query"]
337+
proxy_domains = ["linux.do"]
338+
hosts_domains = ["linux.do"]
339+
certificate_domains = ["linux.do"]
340+
ca_common_name = "Linux.do Accelerator Root CA"
341+
server_common_name = "linux.do"
342+
"#;
343+
std::fs::write(&config_path, customized).unwrap();
344+
345+
let config = AppConfig::load_or_create(&config_path).unwrap();
346+
let reloaded = std::fs::read_to_string(&config_path).unwrap();
347+
348+
assert_eq!(config.listen_host, "127.0.0.1");
349+
assert_eq!(config.hosts_ip, "127.0.0.1");
350+
assert_eq!(config.http_port, 80);
351+
assert_eq!(config.https_port, 443);
352+
assert_eq!(config.fake_sni, Some("www.cloudflare.com".to_string()));
353+
assert_eq!(reloaded, customized);
354+
355+
cleanup_test_dir(&root);
356+
}
357+
283358
fn create_test_dir(name: &str) -> std::path::PathBuf {
284359
let mut path = std::env::temp_dir();
285360
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);

0 commit comments

Comments
 (0)