refactor: drop the Alchementary Link data migration
Startup no longer looks for an AlchementaryLink data directory to copy into NexusTerminal, and the 'Imported your Alchementary Link data' toast and the migration docs are gone. Existing Alchementary Link installs are no longer picked up automatically.
This commit is contained in:
@@ -27,10 +27,6 @@ Prefer a one-off session? Use **Cmd+Shift+Q** / **Ctrl+Shift+Q** to quick connec
|
|||||||
|
|
||||||
If a session drops, use the **Reconnect** button in the disconnected overlay.
|
If a session drops, use the **Reconnect** button in the disconnected overlay.
|
||||||
|
|
||||||
### Coming from Alchementary Link?
|
|
||||||
|
|
||||||
On first run, Nexus Terminal copies your Alchementary Link profiles, encrypted vault and recordings into its own data directory, and picks up credentials saved in the OS keychain. The old data is left untouched. Older `.link` export files can still be imported.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- Product overview: [docs/overview.md](docs/overview.md)
|
- Product overview: [docs/overview.md](docs/overview.md)
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ The upload picker lists folders only and marks permissions:
|
|||||||
- Each password, passphrase or token can be saved, remembered for the session only, or asked for every time.
|
- Each password, passphrase or token can be saved, remembered for the session only, or asked for every time.
|
||||||
- SSH host key checks can be set per profile (Strict, Ask, Accept new, Insecure).
|
- SSH host key checks can be set per profile (Strict, Ask, Accept new, Insecure).
|
||||||
|
|
||||||
## Coming from Alchementary Link
|
|
||||||
|
|
||||||
Your profiles, vault and recordings are copied across automatically on first run, and keychain entries are picked up the first time they are needed. Your old Alchementary Link data is left untouched.
|
|
||||||
|
|
||||||
## Common shortcuts
|
## Common shortcuts
|
||||||
|
|
||||||
| macOS Shortcut | Windows/Linux Shortcut | Action |
|
| macOS Shortcut | Windows/Linux Shortcut | Action |
|
||||||
|
|||||||
+2
-11
@@ -16,7 +16,7 @@
|
|||||||
12. Security and credentials
|
12. Security and credentials
|
||||||
13. Import and export
|
13. Import and export
|
||||||
14. Settings and updates
|
14. Settings and updates
|
||||||
15. Data locations and migration from Alchementary Link
|
15. Data locations
|
||||||
16. Troubleshooting
|
16. Troubleshooting
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -404,7 +404,7 @@ Nexus Terminal checks for a new version once at launch. When one is available, t
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 15. Data locations and migration from Alchementary Link
|
## 15. Data locations
|
||||||
|
|
||||||
### Data locations
|
### Data locations
|
||||||
|
|
||||||
@@ -424,15 +424,6 @@ The directory holds:
|
|||||||
|
|
||||||
Credentials saved in the OS keychain are stored under the service name `NexusTerminal`.
|
Credentials saved in the OS keychain are stored under the service name `NexusTerminal`.
|
||||||
|
|
||||||
### Migrating from Alchementary Link
|
|
||||||
|
|
||||||
On first run, if Nexus Terminal has no database yet and an Alchementary Link data directory (`AlchementaryLink`, in the same parent folder) exists, Nexus Terminal **copies** `link.db` to `nexus.db`, along with the `vault/` and `recordings/` directories, and shows a toast saying where the data came from. The Alchementary Link directory is left untouched.
|
|
||||||
|
|
||||||
- If a `NexusTerminal/nexus.db` already exists (for example from a version before the Alchementary Link rebrand), it is used as-is and nothing is copied.
|
|
||||||
- Keychain entries saved under `AlchementaryLink` are found the first time they are needed and copied to `NexusTerminal`. The old entries are not deleted.
|
|
||||||
- Your vault master password is unchanged.
|
|
||||||
- Trigger rules and SFTP bookmarks from Alchementary Link are not migrated; recreate them in Nexus Terminal.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 16. Troubleshooting
|
## 16. Troubleshooting
|
||||||
|
|||||||
@@ -210,9 +210,6 @@ impl App {
|
|||||||
*sid = Some(id.clone());
|
*sid = Some(id.clone());
|
||||||
app.add_session(&id, true);
|
app.add_session(&id, true);
|
||||||
}
|
}
|
||||||
if let Some(from) = &svc.config.migrated_from {
|
|
||||||
app.s.success(format!("Imported your Alchementary Link data from {}", from.display()));
|
|
||||||
}
|
|
||||||
// Background update check, once per launch.
|
// Background update check, once per launch.
|
||||||
if app.screenshot.is_none() && app.s.prefs.check_for_updates {
|
if app.screenshot.is_none() && app.s.prefs.check_for_updates {
|
||||||
let svc2 = svc.clone();
|
let svc2 = svc.clone();
|
||||||
|
|||||||
+5
-83
@@ -1,9 +1,8 @@
|
|||||||
//! Data directories, and the one-time move of Alchementary Link data into Nexus Terminal's.
|
//! Data directories.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub const APP_DIR: &str = "NexusTerminal";
|
pub const APP_DIR: &str = "NexusTerminal";
|
||||||
pub const LEGACY_APP_DIR: &str = "AlchementaryLink";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
@@ -11,12 +10,10 @@ pub struct AppConfig {
|
|||||||
pub db_path: PathBuf,
|
pub db_path: PathBuf,
|
||||||
pub vault_dir: PathBuf,
|
pub vault_dir: PathBuf,
|
||||||
pub recordings_dir: PathBuf,
|
pub recordings_dir: PathBuf,
|
||||||
/// Set when this run copied data across from Alchementary Link.
|
|
||||||
pub migrated_from: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `~/Library/Application Support` on macOS, `%APPDATA%` on Windows, `$XDG_CONFIG_HOME` or
|
/// `~/Library/Application Support` on macOS, `%APPDATA%` on Windows, `$XDG_CONFIG_HOME` or
|
||||||
/// `~/.config` elsewhere — the same bases the Go app used.
|
/// `~/.config` elsewhere.
|
||||||
pub fn base_dir() -> PathBuf {
|
pub fn base_dir() -> PathBuf {
|
||||||
let home = || std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).map(PathBuf::from).unwrap_or_default();
|
let home = || std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).map(PathBuf::from).unwrap_or_default();
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
@@ -30,20 +27,12 @@ pub fn base_dir() -> PathBuf {
|
|||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub fn in_dir(data_dir: PathBuf) -> AppConfig {
|
pub fn in_dir(data_dir: PathBuf) -> AppConfig {
|
||||||
AppConfig {
|
AppConfig { db_path: data_dir.join("nexus.db"), vault_dir: data_dir.join("vault"), recordings_dir: data_dir.join("recordings"), data_dir }
|
||||||
db_path: data_dir.join("nexus.db"),
|
|
||||||
vault_dir: data_dir.join("vault"),
|
|
||||||
recordings_dir: data_dir.join("recordings"),
|
|
||||||
data_dir,
|
|
||||||
migrated_from: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the data directory, migrating legacy data first, and creates the directories.
|
/// Resolves the data directory and creates it.
|
||||||
pub fn load() -> std::io::Result<AppConfig> {
|
pub fn load() -> std::io::Result<AppConfig> {
|
||||||
let base = base_dir();
|
let cfg = AppConfig::in_dir(base_dir().join(APP_DIR));
|
||||||
let mut cfg = AppConfig::in_dir(base.join(APP_DIR));
|
|
||||||
cfg.migrated_from = migrate_legacy(&base.join(LEGACY_APP_DIR), &cfg)?;
|
|
||||||
for d in [&cfg.data_dir, &cfg.vault_dir] {
|
for d in [&cfg.data_dir, &cfg.vault_dir] {
|
||||||
create_private_dir(d)?;
|
create_private_dir(d)?;
|
||||||
}
|
}
|
||||||
@@ -58,70 +47,3 @@ pub fn create_private_dir(d: &Path) -> std::io::Result<()> {
|
|||||||
std::fs::set_permissions(d, std::os::unix::fs::PermissionsExt::from_mode(0o700))?;
|
std::fs::set_permissions(d, std::os::unix::fs::PermissionsExt::from_mode(0o700))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies (never moves) `AlchementaryLink/{link.db, vault/, recordings/}` into the Nexus
|
|
||||||
/// directory when Nexus has no database yet. A pre-rebrand `NexusTerminal/nexus.db` wins as-is.
|
|
||||||
pub fn migrate_legacy(legacy: &Path, cfg: &AppConfig) -> std::io::Result<Option<PathBuf>> {
|
|
||||||
let legacy_db = legacy.join("link.db");
|
|
||||||
if cfg.db_path.exists() || !legacy_db.exists() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
create_private_dir(&cfg.data_dir)?;
|
|
||||||
// Fold the WAL into the main file so the copy is complete.
|
|
||||||
if let Ok(c) = rusqlite::Connection::open(&legacy_db) {
|
|
||||||
let _ = c.pragma_update(None, "wal_checkpoint", "TRUNCATE");
|
|
||||||
}
|
|
||||||
std::fs::copy(&legacy_db, &cfg.db_path)?;
|
|
||||||
copy_dir(&legacy.join("vault"), &cfg.vault_dir)?;
|
|
||||||
copy_dir(&legacy.join("recordings"), &cfg.recordings_dir)?;
|
|
||||||
std::fs::write(cfg.data_dir.join("migrated-from"), legacy.to_string_lossy().as_bytes())?;
|
|
||||||
Ok(Some(legacy.to_path_buf()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_dir(from: &Path, to: &Path) -> std::io::Result<()> {
|
|
||||||
let Ok(rd) = std::fs::read_dir(from) else { return Ok(()) };
|
|
||||||
create_private_dir(to)?;
|
|
||||||
for e in rd.flatten() {
|
|
||||||
if e.file_type()?.is_file() && !to.join(e.file_name()).exists() {
|
|
||||||
std::fs::copy(e.path(), to.join(e.file_name()))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn tmp(name: &str) -> PathBuf {
|
|
||||||
let d = std::env::temp_dir().join(format!("nexus-cfg-{name}-{}", std::process::id()));
|
|
||||||
let _ = std::fs::remove_dir_all(&d);
|
|
||||||
d
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn migrates_legacy_once_and_keeps_source() {
|
|
||||||
let base = tmp("mig");
|
|
||||||
let legacy = base.join(LEGACY_APP_DIR);
|
|
||||||
std::fs::create_dir_all(legacy.join("vault")).unwrap();
|
|
||||||
std::fs::write(legacy.join("link.db"), b"db").unwrap();
|
|
||||||
std::fs::write(legacy.join("vault/vault.json"), b"{}").unwrap();
|
|
||||||
let cfg = AppConfig::in_dir(base.join(APP_DIR));
|
|
||||||
assert_eq!(migrate_legacy(&legacy, &cfg).unwrap(), Some(legacy.clone()));
|
|
||||||
assert_eq!(std::fs::read(&cfg.db_path).unwrap(), b"db");
|
|
||||||
assert!(cfg.vault_dir.join("vault.json").exists());
|
|
||||||
assert!(legacy.join("link.db").exists());
|
|
||||||
// Second run: Nexus already has a database, nothing happens.
|
|
||||||
std::fs::write(legacy.join("link.db"), b"newer").unwrap();
|
|
||||||
assert_eq!(migrate_legacy(&legacy, &cfg).unwrap(), None);
|
|
||||||
assert_eq!(std::fs::read(&cfg.db_path).unwrap(), b"db");
|
|
||||||
std::fs::remove_dir_all(base).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_legacy_no_migration() {
|
|
||||||
let base = tmp("none");
|
|
||||||
let cfg = AppConfig::in_dir(base.join(APP_DIR));
|
|
||||||
assert_eq!(migrate_legacy(&base.join(LEGACY_APP_DIR), &cfg).unwrap(), None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user