Part O
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
mod config;
|
||||
mod http;
|
||||
mod ratelimit;
|
||||
mod routes;
|
||||
mod security;
|
||||
mod storage;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use axum::routing::{delete, get, post};
|
||||
use axum::Router;
|
||||
|
||||
use config::Config;
|
||||
use ratelimit::RateLimiter;
|
||||
use storage::Storage;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub config: Config,
|
||||
pub storage: Arc<Storage>,
|
||||
pub secret: String,
|
||||
pub limiter: Arc<RateLimiter>,
|
||||
pub started_at: Instant,
|
||||
}
|
||||
|
||||
fn build_router(state: AppState) -> Router {
|
||||
Router::new()
|
||||
.route("/api/health", get(routes::health))
|
||||
.route("/api/upload", post(routes::upload::upload))
|
||||
.route("/api/auth/{key}", post(routes::auth::auth))
|
||||
.route("/api/files/{id}", delete(routes::delete::delete))
|
||||
.fallback(routes::download::fallback)
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "info".into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
let config = match config::load() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::error!("configuration error: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
security::set_key_length(config.key_length);
|
||||
|
||||
let storage = Arc::new(Storage::new(config.storage_dir.clone()));
|
||||
if let Err(e) = storage.init().await {
|
||||
tracing::error!("failed to initialize storage: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let secret = match storage.load_secret().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("failed to load server secret: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let sweep = storage.sweep().await;
|
||||
if sweep.0 > 0 || sweep.1 > 0 {
|
||||
tracing::info!(
|
||||
"startup sweep removed {} orphaned file(s), {} orphaned metadata record(s)",
|
||||
sweep.0,
|
||||
sweep.1
|
||||
);
|
||||
}
|
||||
|
||||
let state = AppState {
|
||||
limiter: Arc::new(RateLimiter::new(config.rate_limit_max, config.rate_limit_window_ms)),
|
||||
started_at: Instant::now(),
|
||||
config: config.clone(),
|
||||
storage,
|
||||
secret,
|
||||
};
|
||||
|
||||
// Hourly expiration sweep.
|
||||
let expiry_state = state.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(3600));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
let removed = expiry_state
|
||||
.storage
|
||||
.expire_expired(chrono::Utc::now().timestamp_millis())
|
||||
.await;
|
||||
if removed > 0 {
|
||||
tracing::info!("expiry sweep removed {removed} file(s)");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let app = build_router(state);
|
||||
|
||||
let addr = format!("{}:{}", config.host, config.port);
|
||||
let listener = match tokio::net::TcpListener::bind(&addr).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
tracing::error!("failed to bind {addr}: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"listening on http://{} storage={:?} maxUpload={}",
|
||||
addr,
|
||||
config.storage_dir,
|
||||
config.max_file_size
|
||||
);
|
||||
|
||||
axum::serve(
|
||||
listener,
|
||||
app.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.expect("server error");
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
let ctrl_c = async {
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
let _ = 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 => {},
|
||||
_ = terminate => {},
|
||||
}
|
||||
tracing::info!("shutdown signal received");
|
||||
}
|
||||
Reference in New Issue
Block a user