feat(build): add unlock rescue script and build command

- Introduced a new PowerShell script `build-unlock-rescue.ps1` to facilitate the unlocking process for NyaTerm.
- Added a corresponding build command `build:unlock-rescue` in package.json to streamline the execution of the unlock rescue script.
- Implemented functionality in the script to create a backup of the database and disable screen lock settings.
This commit is contained in:
Kang committed 2026-07-13 10:58:22 +08:00
1 parent 0eed179446
commit 1412f314ab
3 files changed
+131

No files matched your search

+1
View File
@@ -20,6 +20,7 @@
"dev": "vite",
"build": "tsc && vite build",
"release": "node scripts/sync-version.mjs --commit && tsc && vite build && tauri build",
"build:unlock-rescue": "powershell -ExecutionPolicy Bypass -File scripts/build-unlock-rescue.ps1",
"preview": "vite preview",
"tauri": "tauri",
"lint": "node scripts/check-no-console.mjs && biome check src/",
+18
View File
@@ -0,0 +1,18 @@
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
$manifestPath = Join-Path $repoRoot "src-tauri/Cargo.toml"
$artifactDir = Join-Path $repoRoot "dist/rescue"
$sourceExe = Join-Path $repoRoot "src-tauri/target/release/nyaterm-unlock-rescue.exe"
$targetExe = Join-Path $artifactDir "nyaterm-unlock-rescue.exe"
$targetZip = Join-Path $artifactDir "nyaterm-unlock-rescue-windows-x64.zip"
cargo build --release --manifest-path $manifestPath --bin nyaterm-unlock-rescue
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
Copy-Item -Force -Path $sourceExe -Destination $targetExe
Compress-Archive -Force -Path $targetExe -DestinationPath $targetZip
Write-Host "Built rescue tool:"
Write-Host $targetExe
Write-Host $targetZip
+112
View File
@@ -0,0 +1,112 @@
use redb::{Database, ReadableTable, TableDefinition};
use serde_json::Value;
use std::path::PathBuf;
const SETTINGS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("settings");
const SETTINGS_KEY: &str = "settings/default";
fn main() {
if let Err(error) = run() {
eprintln!("NyaTerm unlock rescue failed: {error}");
wait_for_enter();
std::process::exit(1);
}
wait_for_enter();
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let db_path = std::env::args()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(default_db_path);
if !db_path.exists() {
return Err(format!("database not found: {}", db_path.display()).into());
}
let backup_path = db_path.with_extension(format!(
"redb.screen-lock-rescue-{}.bak",
unix_timestamp_seconds()
));
std::fs::copy(&db_path, &backup_path)?;
let db = Database::open(&db_path)?;
let txn = db.begin_write()?;
let changed = {
let mut table = txn.open_table(SETTINGS_TABLE)?;
let (mut settings, was_enabled, old_idle): (Value, bool, u64) = {
let Some(raw) = table.get(SETTINGS_KEY)? else {
return Err(format!("settings document not found: {SETTINGS_KEY}").into());
};
let mut settings: Value = serde_json::from_slice(raw.value())?;
let security = settings
.as_object_mut()
.and_then(|root| root.get_mut("security"))
.and_then(Value::as_object_mut)
.ok_or("settings.security is missing or not an object")?;
let was_enabled = security
.get("enable_screen_lock")
.and_then(Value::as_bool)
.unwrap_or(false);
let old_idle = security
.get("idle_lock_minutes")
.and_then(Value::as_u64)
.unwrap_or(0);
(settings, was_enabled, old_idle)
};
let security = settings
.as_object_mut()
.and_then(|root| root.get_mut("security"))
.and_then(Value::as_object_mut)
.ok_or("settings.security is missing or not an object")?;
security.insert("enable_screen_lock".to_string(), Value::Bool(false));
security.insert(
"idle_lock_minutes".to_string(),
Value::Number(serde_json::Number::from(0)),
);
let content = serde_json::to_vec(&settings)?;
table.insert(SETTINGS_KEY, content.as_slice())?;
was_enabled || old_idle != 0
};
txn.commit()?;
println!("NyaTerm unlock rescue completed.");
println!("Database: {}", db_path.display());
println!("Backup: {}", backup_path.display());
println!("Screen lock disabled: {changed}");
println!("Master password was left unchanged.");
println!("You can start NyaTerm again now.");
Ok(())
}
fn default_db_path() -> PathBuf {
std::env::var_os("USERPROFILE")
.or_else(|| std::env::var_os("HOME"))
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."))
.join(".nyaterm")
.join("nyaterm.redb")
}
fn unix_timestamp_seconds() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_secs())
.unwrap_or(0)
}
fn wait_for_enter() {
println!();
println!("Press Enter to close this window.");
let mut input = String::new();
let _ = std::io::stdin().read_line(&mut input);
}