Files
rustpad/src/main.rs
T
2026-07-22 11:15:01 +02:00

122 lines
4.0 KiB
Rust

mod api;
mod auth;
mod app;
mod config;
mod database;
mod db;
mod queries;
mod state;
mod websocket;
use std::{net::SocketAddr, sync::Arc};
use config::Config;
use database::{Database, DatabaseKind};
use state::AppState;
use tokio::net::TcpListener;
use tracing::{info, warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
init_tracing();
let config = Config::from_env()?;
info!(
host = %config.host,
port = config.port,
database_kind = %database_kind_label(&config.database_url),
database_max_connections = config.database_max_connections,
static_dir = %config.static_dir,
files_dir = %config.files_dir,
upload_max_size_bytes = config.upload_max_size_bytes,
registration_enabled = config.registration_enabled,
frontend_log_level = %config.frontend_log_level,
smtp_configured = config.smtp.is_some(),
asset_version = %config.asset_version,
"configuration loaded"
);
if let Some(path) = config.database_url.strip_prefix("sqlite://").and_then(|v| v.split('?').next()) {
if let Some(parent) = std::path::Path::new(path).parent() { std::fs::create_dir_all(parent)?; }
}
info!("connecting to database");
let db = Database::connect(&config.database_url, config.database_max_connections).await?;
info!(database_kind = ?db.kind(), "database connection established");
run_migrations(&db).await?;
info!(database_kind = ?db.kind(), "database migrations completed");
std::fs::create_dir_all(&config.files_dir)?;
info!(files_dir = %config.files_dir, "file storage ready");
let state = Arc::new(AppState::new(
db,
config.asset_version.clone(),
config.files_dir.clone(),
config.upload_max_size_bytes,
config.smtp.clone(),
config.registration_enabled,
config.frontend_log_level.clone(),
));
let app = app::router(
state,
&config.static_dir,
config.upload_max_size_bytes,
);
let address = SocketAddr::new(config.host, config.port);
let listener = TcpListener::bind(address).await?;
info!(%address, asset_version = %config.asset_version, "RustPad is running");
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
info!("RustPad stopped cleanly");
Ok(())
}
fn init_tracing() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "rustpad=debug,tower_http=info".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install SIGTERM handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => warn!("shutdown requested by Ctrl+C"),
() = terminate => warn!("shutdown requested by SIGTERM"),
}
}
async fn run_migrations(db: &Database) -> Result<(), sqlx::migrate::MigrateError> {
let path = match db.kind() {
DatabaseKind::Sqlite => std::path::Path::new("migrations/sqlite"),
DatabaseKind::Postgres => std::path::Path::new("migrations/postgres"),
DatabaseKind::MySql => std::path::Path::new("migrations/mysql"),
};
sqlx::migrate::Migrator::new(path).await?.run(db.pool()).await
}
fn database_kind_label(url: &str) -> &'static str {
if url.starts_with("sqlite:") { "sqlite" }
else if url.starts_with("postgres:") || url.starts_with("postgresql:") { "postgres" }
else if url.starts_with("mysql:") { "mysql" }
else { "unknown" }
}