diff --git a/warzone/crates/warzone-client/src/main.rs b/warzone/crates/warzone-client/src/main.rs index dda2d3b..fac2823 100644 --- a/warzone/crates/warzone-client/src/main.rs +++ b/warzone/crates/warzone-client/src/main.rs @@ -70,6 +70,8 @@ async fn main() -> anyhow::Result<()> { // All other commands need the seed — unlock once here 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 our_fp = identity.public_identity().fingerprint.to_string(); @@ -98,7 +100,7 @@ async fn main() -> anyhow::Result<()> { Commands::Chat { peer, server } => { let _ = cli::init::register_with_server_identity(&server, &identity).await; 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?; } } diff --git a/warzone/crates/warzone-client/src/storage.rs b/warzone/crates/warzone-client/src/storage.rs index 0aa8f95..614f22c 100644 --- a/warzone/crates/warzone-client/src/storage.rs +++ b/warzone/crates/warzone-client/src/storage.rs @@ -14,7 +14,25 @@ pub struct LocalDb { impl LocalDb { pub fn open() -> Result { 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 pre_keys = db.open_tree("pre_keys")?; Ok(LocalDb { diff --git a/warzone/crates/warzone-client/src/tui/app.rs b/warzone/crates/warzone-client/src/tui/app.rs index cc5afac..89a6706 100644 --- a/warzone/crates/warzone-client/src/tui/app.rs +++ b/warzone/crates/warzone-client/src/tui/app.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; -use anyhow::{Context, Result}; +use anyhow::Result; use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use ratatui::layout::{Constraint, Direction, Layout}; use ratatui::style::{Color, Modifier, Style}; @@ -741,6 +741,7 @@ pub async fn run_tui( peer_fp: Option, server_url: String, identity: IdentityKeyPair, + poll_seed: warzone_protocol::identity::Seed, db: LocalDb, ) -> Result<()> { 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); - // Spawn background poll task - // We need a second IdentityKeyPair for the poll loop — re-derive from seed - let poll_identity = crate::keystore::load_seed()?.derive_identity(); + // Derive a second identity for the poll loop (can't clone IdentityKeyPair) + let poll_identity = poll_seed.derive_identity(); let poll_messages = app.messages.clone(); let poll_client = client.clone(); let poll_db = db.clone(); diff --git a/warzone/crates/warzone-server/src/routes/auth.rs b/warzone/crates/warzone-server/src/routes/auth.rs index 299967f..ad44b2a 100644 --- a/warzone/crates/warzone-server/src/routes/auth.rs +++ b/warzone/crates/warzone-server/src/routes/auth.rs @@ -172,6 +172,8 @@ async fn verify_challenge( } /// 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 { let data = db.get(token.as_bytes()).ok()??; let val: serde_json::Value = serde_json::from_slice(&data).ok()?;