Old fp contained non-hex chars (o,r) which got stripped by normFP, causing whois lookup failure and bot detection to miss. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
127 lines
4.5 KiB
Rust
127 lines
4.5 KiB
Rust
use clap::Parser;
|
|
|
|
mod botfather;
|
|
pub mod auth_middleware;
|
|
mod config;
|
|
mod db;
|
|
mod errors;
|
|
mod federation;
|
|
mod routes;
|
|
mod state;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "warzone-server", about = "Warzone messenger server")]
|
|
struct Cli {
|
|
/// Address to bind to
|
|
#[arg(short, long, default_value = "0.0.0.0:7700")]
|
|
bind: String,
|
|
|
|
/// Database directory
|
|
#[arg(short, long, default_value = "./warzone-data")]
|
|
data_dir: String,
|
|
|
|
/// Federation config file (JSON). Enables server-to-server message relay.
|
|
#[arg(short, long)]
|
|
federation: Option<String>,
|
|
|
|
/// Enable bot API (disabled by default)
|
|
#[arg(long, default_value = "false")]
|
|
enable_bots: bool,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "info,tower_http=debug".parse().unwrap()),
|
|
)
|
|
.init();
|
|
|
|
let cli = Cli::parse();
|
|
tracing::info!("Warzone server starting on {}", cli.bind);
|
|
|
|
let mut state = state::AppState::new(&cli.data_dir)?;
|
|
|
|
// Load federation config if provided
|
|
if let Some(ref fed_path) = cli.federation {
|
|
let fed_config = federation::load_config(fed_path)?;
|
|
tracing::info!(
|
|
"Federation enabled: server_id={}, peer={}@{}",
|
|
fed_config.server_id, fed_config.peer.id, fed_config.peer.url
|
|
);
|
|
let handle = federation::FederationHandle::new(fed_config);
|
|
state.federation = Some(handle);
|
|
}
|
|
|
|
// Enable bot API if requested
|
|
state.bots_enabled = cli.enable_bots;
|
|
if cli.enable_bots {
|
|
tracing::info!("Bot API enabled");
|
|
|
|
// Auto-create BotFather if it doesn't exist
|
|
let botfather_fp = "00000000000000000b0ffa00e000000f";
|
|
let botfather_key = format!("bot_fp:{}", botfather_fp);
|
|
if state.db.tokens.get(botfather_key.as_bytes()).ok().flatten().is_none() {
|
|
let token = format!("botfather:{}", hex::encode(rand::random::<[u8; 16]>()));
|
|
let bot_info = serde_json::json!({
|
|
"name": "BotFather",
|
|
"fingerprint": botfather_fp,
|
|
"token": token,
|
|
"owner": "system",
|
|
"e2e": false,
|
|
"created_at": chrono::Utc::now().timestamp(),
|
|
});
|
|
let key = format!("bot:{}", token);
|
|
let _ = state.db.tokens.insert(key.as_bytes(), serde_json::to_vec(&bot_info).unwrap_or_default());
|
|
let _ = state.db.tokens.insert(botfather_key.as_bytes(), token.as_bytes());
|
|
// Register alias
|
|
let _ = state.db.aliases.insert(b"a:botfather", botfather_fp.as_bytes());
|
|
let _ = state.db.aliases.insert(format!("fp:{}", botfather_fp).as_bytes(), b"botfather");
|
|
tracing::info!("BotFather created: @botfather (token: {}...)", &token[..20]);
|
|
} else {
|
|
tracing::info!("BotFather already exists");
|
|
}
|
|
// Always ensure alias exists (may have been lost on data wipe)
|
|
let _ = state.db.aliases.insert(b"a:botfather", botfather_fp.as_bytes());
|
|
let _ = state.db.aliases.insert(format!("fp:{}", botfather_fp).as_bytes(), b"botfather");
|
|
// Store proper AliasRecord so resolve_alias works
|
|
let bf_record = serde_json::json!({
|
|
"alias": "botfather",
|
|
"fingerprint": botfather_fp,
|
|
"recovery_key": "",
|
|
"registered_at": chrono::Utc::now().timestamp(),
|
|
"last_active": chrono::Utc::now().timestamp(),
|
|
});
|
|
let _ = state.db.aliases.insert(b"rec:botfather", serde_json::to_vec(&bf_record).unwrap_or_default());
|
|
}
|
|
|
|
// Spawn federation outgoing WS connection if enabled
|
|
if let Some(ref fed) = state.federation {
|
|
let handle = fed.clone();
|
|
let fed_state = state.clone();
|
|
tokio::spawn(async move {
|
|
federation::outgoing_ws_loop(handle, fed_state).await;
|
|
});
|
|
}
|
|
|
|
let cors = tower_http::cors::CorsLayer::new()
|
|
.allow_origin(tower_http::cors::Any)
|
|
.allow_methods(tower_http::cors::Any)
|
|
.allow_headers(tower_http::cors::Any);
|
|
|
|
let app = axum::Router::new()
|
|
.merge(routes::web_router())
|
|
.nest("/v1", routes::router())
|
|
.layer(cors)
|
|
.layer(tower::limit::ConcurrencyLimitLayer::new(200))
|
|
.layer(tower_http::trace::TraceLayer::new_for_http())
|
|
.with_state(state);
|
|
|
|
let listener = tokio::net::TcpListener::bind(&cli.bind).await?;
|
|
tracing::info!("Listening on {}", cli.bind);
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
}
|