Fix DB lock error: clear message + instructions, fix passphrase reprompt

Storage:
- Detects sled lock contention, shows actionable error:
  "Database locked by another warzone process"
  with ps command to find the process and rm command to force unlock

TUI:
- Poll loop no longer calls load_seed() (was re-prompting passphrase)
- Seed passed from main.rs to run_tui to poll_loop
- Single passphrase prompt per app launch

Warnings fixed:
- Removed unused `Context` import in tui/app.rs
- Added #[allow(dead_code)] on validate_token (used when auth middleware wired)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 08:24:53 +04:00
parent c8b51fa96b
commit d7b71efdbc
4 changed files with 28 additions and 6 deletions

View File

@@ -70,6 +70,8 @@ async fn main() -> anyhow::Result<()> {
// All other commands need the seed — unlock once here // All other commands need the seed — unlock once here
let seed = keystore::load_seed()?; let seed = keystore::load_seed()?;
// Create a copy for the poll thread (Seed doesn't impl Clone due to Zeroize)
let poll_seed = warzone_protocol::identity::Seed::from_bytes(seed.0);
let identity = seed.derive_identity(); let identity = seed.derive_identity();
let our_fp = identity.public_identity().fingerprint.to_string(); let our_fp = identity.public_identity().fingerprint.to_string();
@@ -98,7 +100,7 @@ async fn main() -> anyhow::Result<()> {
Commands::Chat { peer, server } => { Commands::Chat { peer, server } => {
let _ = cli::init::register_with_server_identity(&server, &identity).await; let _ = cli::init::register_with_server_identity(&server, &identity).await;
let db = storage::LocalDb::open()?; let db = storage::LocalDb::open()?;
tui::run_tui(our_fp, peer, server, identity, db).await?; tui::run_tui(our_fp, peer, server, identity, poll_seed, db).await?;
} }
} }

View File

@@ -14,7 +14,25 @@ pub struct LocalDb {
impl LocalDb { impl LocalDb {
pub fn open() -> Result<Self> { pub fn open() -> Result<Self> {
let path = crate::keystore::data_dir().join("db"); let path = crate::keystore::data_dir().join("db");
let db = sled::open(&path).context("failed to open local database")?; let db = match sled::open(&path) {
Ok(db) => db,
Err(e) => {
let err_str = e.to_string();
if err_str.contains("WouldBlock") || err_str.contains("lock") {
eprintln!("Error: Database is locked by another warzone process.");
eprintln!(" DB path: {}", path.display());
eprintln!();
eprintln!(" Check for running processes:");
eprintln!(" ps aux | grep warzone-client");
eprintln!();
eprintln!(" To force unlock (if no other process is running):");
eprintln!(" rm -rf {}", path.display());
eprintln!(" (This deletes sessions — you'll need to re-establish them)");
anyhow::bail!("database locked by another process");
}
return Err(e).context("failed to open local database");
}
};
let sessions = db.open_tree("sessions")?; let sessions = db.open_tree("sessions")?;
let pre_keys = db.open_tree("pre_keys")?; let pre_keys = db.open_tree("pre_keys")?;
Ok(LocalDb { Ok(LocalDb {

View File

@@ -1,7 +1,7 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use anyhow::{Context, Result}; use anyhow::Result;
use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use ratatui::layout::{Constraint, Direction, Layout}; use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::style::{Color, Modifier, Style}; use ratatui::style::{Color, Modifier, Style};
@@ -741,6 +741,7 @@ pub async fn run_tui(
peer_fp: Option<String>, peer_fp: Option<String>,
server_url: String, server_url: String,
identity: IdentityKeyPair, identity: IdentityKeyPair,
poll_seed: warzone_protocol::identity::Seed,
db: LocalDb, db: LocalDb,
) -> Result<()> { ) -> Result<()> {
let mut terminal = ratatui::init(); let mut terminal = ratatui::init();
@@ -749,9 +750,8 @@ pub async fn run_tui(
let mut app = App::new(our_fp.clone(), peer_fp, server_url); let mut app = App::new(our_fp.clone(), peer_fp, server_url);
// Spawn background poll task // Derive a second identity for the poll loop (can't clone IdentityKeyPair)
// We need a second IdentityKeyPair for the poll loop — re-derive from seed let poll_identity = poll_seed.derive_identity();
let poll_identity = crate::keystore::load_seed()?.derive_identity();
let poll_messages = app.messages.clone(); let poll_messages = app.messages.clone();
let poll_client = client.clone(); let poll_client = client.clone();
let poll_db = db.clone(); let poll_db = db.clone();

View File

@@ -172,6 +172,8 @@ async fn verify_challenge(
} }
/// Validate a bearer token. Returns the fingerprint if valid. /// Validate a bearer token. Returns the fingerprint if valid.
/// Used by protected endpoints (will be wired in when auth middleware is added).
#[allow(dead_code)]
pub fn validate_token(db: &sled::Tree, token: &str) -> Option<String> { pub fn validate_token(db: &sled::Tree, token: &str) -> Option<String> {
let data = db.get(token.as_bytes()).ok()??; let data = db.get(token.as_bytes()).ok()??;
let val: serde_json::Value = serde_json::from_slice(&data).ok()?; let val: serde_json::Value = serde_json::from_slice(&data).ok()?;