@@ -9,21 +9,31 @@ const DEFAULT_APP_CONFIG: &str = include_str!("../assets/defaults/linuxdo-accele
99
1010#[ derive( Debug , Clone , Serialize , Deserialize ) ]
1111pub 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+
60110fn 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) ]
262273mod 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