Skip to content

Commit 1ea61c4

Browse files
committed
Overwrite config on upgrade with old config backed up as .bak
Add a version marker file (linuxdo-accelerator.toml.version) written alongside the config. On startup, if the marker is missing or does not match the running version, the current config is saved as linuxdo-accelerator.toml.bak and replaced with the new default. Applies to both desktop (Rust) and Android (Kotlin) paths. Existing same-version runs preserve user customizations unchanged. Tests: load_or_create_resets_to_defaults_on_version_mismatch_and_keeps_backup load_or_create_resets_to_defaults_when_marker_is_missing load_or_create_writes_marker_on_fresh_install load_or_create_preserves_manual_loopback_override (no .bak) load_or_create_does_not_restore_removed_default_entries (no .bak) load_or_create_uses_defaults_for_missing_fields_without_rewriting
1 parent eb099fa commit 1ea61c4

2 files changed

Lines changed: 157 additions & 1 deletion

File tree

android/app/src/main/java/io/linuxdo/accelerator/android/LinuxdoBinary.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,42 @@ object LinuxdoBinary {
114114

115115
fun ensureConfigFile(context: Context): File {
116116
val configFile = configFile(context)
117+
val markerFile = File(configFile.parentFile, "$CONFIG_NAME.version")
118+
val backupFile = File(configFile.parentFile, "$CONFIG_NAME.bak")
119+
val currentVersion = BuildConfig.VERSION_NAME
120+
117121
if (configFile.exists()) {
122+
val markerVersion = readVersionMarker(markerFile)
123+
if (markerVersion != currentVersion) {
124+
configFile.copyTo(backupFile, overwrite = true)
125+
copyAsset(context, CONFIG_ASSET, configFile, overwrite = true)
126+
writeVersionMarker(markerFile, currentVersion)
127+
}
118128
return configFile
119129
}
120130

121131
val legacyFile = File(context.filesDir, CONFIG_NAME)
122132
if (legacyFile.exists()) {
123133
legacyFile.copyTo(configFile, overwrite = true)
134+
writeVersionMarker(markerFile, currentVersion)
124135
return configFile
125136
}
126137

127138
copyAsset(context, CONFIG_ASSET, configFile, overwrite = false)
139+
writeVersionMarker(markerFile, currentVersion)
128140
return configFile
129141
}
130142

143+
private fun readVersionMarker(markerFile: File): String? {
144+
if (!markerFile.exists()) return null
145+
return markerFile.readText().trim().ifBlank { null }
146+
}
147+
148+
private fun writeVersionMarker(markerFile: File, version: String) {
149+
markerFile.parentFile?.mkdirs()
150+
markerFile.writeText(version)
151+
}
152+
131153
fun readConfig(context: Context): LinuxdoConfig = LinuxdoConfig.fromTomlFile(configFile(context))
132154

133155
fun runRoot(context: Context, vararg args: String): CommandResult = run(context, *args)

src/config.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::collections::BTreeMap;
2+
use std::ffi::OsString;
23
use std::fs;
34
use std::path::{Path, PathBuf};
45

56
use anyhow::{Context, Result, anyhow};
67
use serde::{Deserialize, Serialize};
78

89
const DEFAULT_APP_CONFIG: &str = include_str!("../assets/defaults/linuxdo-accelerator.toml");
10+
const CURRENT_CONFIG_VERSION: &str = env!("CARGO_PKG_VERSION");
911

1012
#[derive(Debug, Clone, Serialize, Deserialize)]
1113
pub struct AppConfig {
@@ -129,16 +131,70 @@ fn legacy_network_profile_path(config_path: &Path) -> PathBuf {
129131
.unwrap_or_else(|| PathBuf::from("linuxdo-network.toml"))
130132
}
131133

134+
fn sibling_path(config_path: &Path, suffix: &str) -> PathBuf {
135+
let mut name = config_path
136+
.file_name()
137+
.map(ToOwned::to_owned)
138+
.unwrap_or_else(|| OsString::from("linuxdo-accelerator.toml"));
139+
name.push(suffix);
140+
config_path
141+
.parent()
142+
.map(|parent| parent.join(&name))
143+
.unwrap_or_else(|| PathBuf::from(&name))
144+
}
145+
146+
fn version_marker_path(config_path: &Path) -> PathBuf {
147+
sibling_path(config_path, ".version")
148+
}
149+
150+
fn backup_config_path(config_path: &Path) -> PathBuf {
151+
sibling_path(config_path, ".bak")
152+
}
153+
154+
fn read_version_marker(marker_path: &Path) -> Option<String> {
155+
fs::read_to_string(marker_path)
156+
.ok()
157+
.map(|value| value.trim().to_string())
158+
}
159+
160+
fn write_version_marker(marker_path: &Path) -> Result<()> {
161+
fs::write(marker_path, CURRENT_CONFIG_VERSION)
162+
.with_context(|| format!("failed to write {}", marker_path.display()))
163+
}
164+
165+
fn marker_matches_current_version(marker_path: &Path) -> bool {
166+
matches!(read_version_marker(marker_path), Some(value) if value == CURRENT_CONFIG_VERSION)
167+
}
168+
132169
impl AppConfig {
133170
pub fn load_or_create(path: &Path) -> Result<Self> {
134171
if let Some(parent) = path.parent() {
135172
fs::create_dir_all(parent)
136173
.with_context(|| format!("failed to create {}", parent.display()))?;
137174
}
138175

176+
let marker_path = version_marker_path(path);
177+
178+
if path.exists() && !marker_matches_current_version(&marker_path) {
179+
let backup_path = backup_config_path(path);
180+
fs::copy(path, &backup_path).with_context(|| {
181+
format!(
182+
"failed to back up {} to {}",
183+
path.display(),
184+
backup_path.display()
185+
)
186+
})?;
187+
fs::write(path, DEFAULT_APP_CONFIG)
188+
.with_context(|| format!("failed to write config {}", path.display()))?;
189+
write_version_marker(&marker_path)?;
190+
cleanup_legacy_network_profile(path)?;
191+
return Ok(default_app_config());
192+
}
193+
139194
if !path.exists() {
140195
fs::write(path, DEFAULT_APP_CONFIG)
141196
.with_context(|| format!("failed to write config {}", path.display()))?;
197+
write_version_marker(&marker_path)?;
142198
cleanup_legacy_network_profile(path)?;
143199
return Ok(default_app_config());
144200
}
@@ -202,6 +258,7 @@ impl AppConfig {
202258
if !path.exists() {
203259
fs::write(path, DEFAULT_APP_CONFIG)
204260
.with_context(|| format!("failed to write config {}", path.display()))?;
261+
write_version_marker(&version_marker_path(path))?;
205262
}
206263
cleanup_legacy_network_profile(path)?;
207264
Ok(())
@@ -291,7 +348,11 @@ fn cleanup_legacy_network_profile(path: &Path) -> Result<()> {
291348

292349
#[cfg(test)]
293350
mod tests {
294-
use super::{AppConfig, DEFAULT_APP_CONFIG};
351+
use super::{
352+
AppConfig, CURRENT_CONFIG_VERSION, DEFAULT_APP_CONFIG, backup_config_path,
353+
version_marker_path,
354+
};
355+
use std::path::Path;
295356
use std::sync::atomic::{AtomicU64, Ordering};
296357

297358
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
@@ -302,11 +363,13 @@ mod tests {
302363
let config_path = root.join("linuxdo-accelerator.toml");
303364
let customized = DEFAULT_APP_CONFIG.replace("127.211.73.84", "127.0.0.1");
304365
std::fs::write(&config_path, customized).unwrap();
366+
write_current_version_marker(&config_path);
305367

306368
let config = AppConfig::load_or_create(&config_path).unwrap();
307369

308370
assert_eq!(config.listen_host, "127.0.0.1");
309371
assert_eq!(config.hosts_ip, "127.0.0.1");
372+
assert!(!backup_config_path(&config_path).exists());
310373

311374
cleanup_test_dir(&root);
312375
}
@@ -332,6 +395,7 @@ ca_common_name = "Linux.do Accelerator Root CA"
332395
server_common_name = "linux.do"
333396
"#;
334397
std::fs::write(&config_path, customized).unwrap();
398+
write_current_version_marker(&config_path);
335399

336400
let config = AppConfig::load_or_create(&config_path).unwrap();
337401
let reloaded = std::fs::read_to_string(&config_path).unwrap();
@@ -341,6 +405,7 @@ server_common_name = "linux.do"
341405
assert_eq!(config.certificate_domains, vec!["linux.do"]);
342406
assert_eq!(config.doh_endpoints, vec!["https://1.1.1.1/dns-query"]);
343407
assert_eq!(reloaded, customized);
408+
assert!(!backup_config_path(&config_path).exists());
344409

345410
cleanup_test_dir(&root);
346411
}
@@ -361,6 +426,7 @@ ca_common_name = "Linux.do Accelerator Root CA"
361426
server_common_name = "linux.do"
362427
"#;
363428
std::fs::write(&config_path, customized).unwrap();
429+
write_current_version_marker(&config_path);
364430

365431
let config = AppConfig::load_or_create(&config_path).unwrap();
366432
let reloaded = std::fs::read_to_string(&config_path).unwrap();
@@ -375,6 +441,74 @@ server_common_name = "linux.do"
375441
cleanup_test_dir(&root);
376442
}
377443

444+
#[test]
445+
fn load_or_create_resets_to_defaults_on_version_mismatch_and_keeps_backup() {
446+
let root = create_test_dir("upgrade-overwrite");
447+
let config_path = root.join("linuxdo-accelerator.toml");
448+
let legacy = r#"
449+
listen_host = "127.0.0.1"
450+
hosts_ip = "127.0.0.1"
451+
upstream = "https://linux.do"
452+
doh_endpoints = ["https://example.test/dns-query"]
453+
proxy_domains = ["linux.do"]
454+
hosts_domains = ["linux.do"]
455+
certificate_domains = ["linux.do"]
456+
ca_common_name = "Linux.do Accelerator Root CA"
457+
server_common_name = "linux.do"
458+
"#;
459+
std::fs::write(&config_path, legacy).unwrap();
460+
std::fs::write(version_marker_path(&config_path), "0.0.0-old").unwrap();
461+
462+
let config = AppConfig::load_or_create(&config_path).unwrap();
463+
let on_disk = std::fs::read_to_string(&config_path).unwrap();
464+
let backup = std::fs::read_to_string(backup_config_path(&config_path)).unwrap();
465+
let marker = std::fs::read_to_string(version_marker_path(&config_path)).unwrap();
466+
467+
assert_eq!(on_disk, DEFAULT_APP_CONFIG);
468+
assert_eq!(backup, legacy);
469+
assert_eq!(marker, CURRENT_CONFIG_VERSION);
470+
assert_eq!(config.listen_host, "127.211.73.84");
471+
472+
cleanup_test_dir(&root);
473+
}
474+
475+
#[test]
476+
fn load_or_create_resets_to_defaults_when_marker_is_missing() {
477+
let root = create_test_dir("upgrade-no-marker");
478+
let config_path = root.join("linuxdo-accelerator.toml");
479+
let legacy = "listen_host = \"127.0.0.1\"\n";
480+
std::fs::write(&config_path, legacy).unwrap();
481+
482+
let _ = AppConfig::load_or_create(&config_path).unwrap();
483+
484+
let on_disk = std::fs::read_to_string(&config_path).unwrap();
485+
let backup = std::fs::read_to_string(backup_config_path(&config_path)).unwrap();
486+
let marker = std::fs::read_to_string(version_marker_path(&config_path)).unwrap();
487+
assert_eq!(on_disk, DEFAULT_APP_CONFIG);
488+
assert_eq!(backup, legacy);
489+
assert_eq!(marker, CURRENT_CONFIG_VERSION);
490+
491+
cleanup_test_dir(&root);
492+
}
493+
494+
#[test]
495+
fn load_or_create_writes_marker_on_fresh_install() {
496+
let root = create_test_dir("fresh-install");
497+
let config_path = root.join("linuxdo-accelerator.toml");
498+
499+
let _ = AppConfig::load_or_create(&config_path).unwrap();
500+
501+
let marker = std::fs::read_to_string(version_marker_path(&config_path)).unwrap();
502+
assert_eq!(marker, CURRENT_CONFIG_VERSION);
503+
assert!(!backup_config_path(&config_path).exists());
504+
505+
cleanup_test_dir(&root);
506+
}
507+
508+
fn write_current_version_marker(config_path: &Path) {
509+
std::fs::write(version_marker_path(config_path), CURRENT_CONFIG_VERSION).unwrap();
510+
}
511+
378512
fn create_test_dir(name: &str) -> std::path::PathBuf {
379513
let mut path = std::env::temp_dir();
380514
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);

0 commit comments

Comments
 (0)