mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-25 17:59:08 +00:00
177 lines
4.9 KiB
Rust
177 lines
4.9 KiB
Rust
use anyhow::Result;
|
|
use std::sync::Arc;
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
mod config;
|
|
mod database;
|
|
mod services;
|
|
mod web;
|
|
|
|
use config::{args::parse_args, AppConfig};
|
|
use database::DatabaseManager;
|
|
use services::{TaskScheduler, TelegramService, XrayService};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
// Initialize default crypto provider for rustls
|
|
rustls::crypto::aws_lc_rs::default_provider()
|
|
.install_default()
|
|
.expect("Failed to install rustls crypto provider");
|
|
|
|
// Parse command line arguments first
|
|
let args = parse_args();
|
|
|
|
// Initialize logging early with basic configuration
|
|
init_logging(&args.log_level.as_deref().unwrap_or("info"))?;
|
|
|
|
// Handle special flags
|
|
if args.print_default_config {
|
|
print_default_config()?;
|
|
return Ok(());
|
|
}
|
|
|
|
// Load configuration
|
|
let config = match AppConfig::load() {
|
|
Ok(config) => config,
|
|
Err(e) => {
|
|
tracing::error!("Failed to load configuration: {}", e);
|
|
if args.validate_config {
|
|
std::process::exit(1);
|
|
}
|
|
AppConfig::default()
|
|
}
|
|
};
|
|
|
|
// Validate configuration if requested
|
|
if args.validate_config {
|
|
return Ok(());
|
|
}
|
|
|
|
// Display configuration summary
|
|
config.display_summary();
|
|
|
|
// Print environment info in debug mode
|
|
if tracing::level_enabled!(tracing::Level::DEBUG) {
|
|
config::env::EnvVars::print_env_info();
|
|
}
|
|
|
|
// Initialize database connection
|
|
let db = match DatabaseManager::new(&config.database).await {
|
|
Ok(db) => db,
|
|
Err(e) => {
|
|
tracing::error!("Failed to initialize database: {}", e);
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
// Perform database health check
|
|
match db.health_check().await {
|
|
Ok(false) => tracing::warn!("Database health check failed"),
|
|
Err(e) => tracing::error!("Database health check error: {}", e),
|
|
_ => {}
|
|
}
|
|
|
|
// Initialize event bus first
|
|
let event_receiver = crate::services::events::init_event_bus();
|
|
|
|
// Initialize xray service
|
|
let xray_service = XrayService::new();
|
|
|
|
// Initialize and start task scheduler with dependencies
|
|
let mut task_scheduler = TaskScheduler::new().await?;
|
|
task_scheduler.start(db.clone(), xray_service).await?;
|
|
|
|
// Start event-driven sync handler with the receiver
|
|
TaskScheduler::start_event_handler(db.clone(), event_receiver).await;
|
|
|
|
// Initialize Telegram service if needed
|
|
let telegram_service = Arc::new(TelegramService::new(db.clone(), config.clone()));
|
|
if let Err(e) = telegram_service.initialize().await {
|
|
tracing::warn!("Failed to initialize Telegram service: {}", e);
|
|
}
|
|
|
|
// Start web server with task scheduler
|
|
|
|
tokio::select! {
|
|
result = web::start_server(db, config.clone(), Some(telegram_service.clone())) => {
|
|
match result {
|
|
Err(e) => tracing::error!("Web server error: {}", e),
|
|
_ => {}
|
|
}
|
|
}
|
|
_ = tokio::signal::ctrl_c() => {
|
|
if let Err(e) = task_scheduler.shutdown().await {
|
|
tracing::error!("Error shutting down task scheduler: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn init_logging(level: &str) -> Result<()> {
|
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(level));
|
|
|
|
tracing_subscriber::registry()
|
|
.with(filter)
|
|
.with(
|
|
tracing_subscriber::fmt::layer()
|
|
.with_target(true) // Show module names
|
|
.with_thread_ids(false)
|
|
.with_thread_names(false)
|
|
.with_file(false)
|
|
.with_line_number(false)
|
|
.compact(),
|
|
)
|
|
.try_init()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn print_default_config() -> Result<()> {
|
|
let default_config = AppConfig::default();
|
|
let toml_content = toml::to_string_pretty(&default_config)?;
|
|
|
|
println!("# Default configuration for Xray Admin Panel");
|
|
println!("# Save this to config.toml and modify as needed\n");
|
|
println!("{}", toml_content);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn mask_url(url: &str) -> String {
|
|
if let Ok(parsed) = url::Url::parse(url) {
|
|
if parsed.password().is_some() {
|
|
let mut masked = parsed.clone();
|
|
masked.set_password(Some("***")).unwrap();
|
|
masked.to_string()
|
|
} else {
|
|
url.to_string()
|
|
}
|
|
} else {
|
|
url.to_string()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_mask_url() {
|
|
let url = "postgresql://user:password@localhost/db";
|
|
let masked = mask_url(url);
|
|
assert!(masked.contains("***"));
|
|
assert!(!masked.contains("password"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mask_url_no_password() {
|
|
let url = "postgresql://user@localhost/db";
|
|
let masked = mask_url(url);
|
|
assert_eq!(masked, url);
|
|
}
|
|
}
|