141 lines
4.2 KiB
Rust
141 lines
4.2 KiB
Rust
mod auth;
|
|
mod config;
|
|
mod db;
|
|
mod error;
|
|
mod hooks;
|
|
mod mailer;
|
|
mod models;
|
|
mod passwords;
|
|
mod routes;
|
|
mod sieve;
|
|
mod state;
|
|
mod ui;
|
|
mod validation;
|
|
|
|
use std::{net::SocketAddr, sync::Arc, time::Duration};
|
|
|
|
use axum::{
|
|
Router,
|
|
http::{HeaderName, HeaderValue, StatusCode},
|
|
routing::get,
|
|
};
|
|
use tower_http::{set_header::SetResponseHeaderLayer, timeout::TimeoutLayer, trace::TraceLayer};
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
use crate::{
|
|
auth::AuthStore,
|
|
config::AppConfig,
|
|
db::Db,
|
|
error::{AppError, AppResult},
|
|
state::AppState,
|
|
};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
if let Err(error) = run().await {
|
|
eprintln!("postfixadmin-rs: {error}");
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn run() -> AppResult<()> {
|
|
if std::env::args().any(|arg| arg == "--version" || arg == "-V") {
|
|
println!("postfixadmin-rs {}", env!("CARGO_PKG_VERSION"));
|
|
return Ok(());
|
|
}
|
|
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| EnvFilter::new("postfixadmin_rs=info,tower_http=info")),
|
|
)
|
|
.init();
|
|
|
|
let config = Arc::new(AppConfig::load()?);
|
|
let db = Db::connect(&config).await?;
|
|
if config.database.dev_mock {
|
|
db.initialize_dev_mock(&config).await?;
|
|
}
|
|
db.ping().await?;
|
|
let warnings = db.check_schema(&config).await?;
|
|
for warning in &warnings {
|
|
tracing::warn!(%warning, "schema compatibility warning");
|
|
}
|
|
|
|
if std::env::args().any(|arg| arg == "--check-config") {
|
|
println!("configuration: ok");
|
|
println!("database: ok");
|
|
if config.database.dev_mock {
|
|
println!("database mode: development mock");
|
|
}
|
|
if config.database.read_only {
|
|
println!("database access: read-only");
|
|
}
|
|
for warning in warnings {
|
|
println!("warning: {warning}");
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
let state = Arc::new(AppState {
|
|
auth: AuthStore::new(&config),
|
|
config: config.clone(),
|
|
db,
|
|
});
|
|
|
|
let api = routes::router(&state);
|
|
let inner = Router::<Arc<AppState>>::new()
|
|
.nest("/api", api)
|
|
.route("/assets/{*path}", get(ui::asset))
|
|
.fallback(ui::index)
|
|
.layer(TimeoutLayer::with_status_code(
|
|
StatusCode::REQUEST_TIMEOUT,
|
|
Duration::from_secs(60),
|
|
))
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
HeaderName::from_static("content-security-policy"),
|
|
HeaderValue::from_static("default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'"),
|
|
))
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
HeaderName::from_static("x-content-type-options"),
|
|
HeaderValue::from_static("nosniff"),
|
|
))
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
HeaderName::from_static("x-frame-options"),
|
|
HeaderValue::from_static("DENY"),
|
|
))
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
HeaderName::from_static("referrer-policy"),
|
|
HeaderValue::from_static("no-referrer"),
|
|
))
|
|
.layer(SetResponseHeaderLayer::overriding(
|
|
HeaderName::from_static("permissions-policy"),
|
|
HeaderValue::from_static("camera=(), microphone=(), geolocation=()"),
|
|
))
|
|
.layer(TraceLayer::new_for_http());
|
|
|
|
let base = config.normalized_base_path();
|
|
let app: Router = if base == "/" {
|
|
inner.with_state(state.clone())
|
|
} else {
|
|
Router::<Arc<AppState>>::new()
|
|
.nest(&base, inner)
|
|
.with_state(state.clone())
|
|
};
|
|
|
|
let addr: SocketAddr = format!("{}:{}", config.host, config.port)
|
|
.parse()
|
|
.map_err(|error| AppError::config(format!("invalid listen address: {error}")))?;
|
|
tracing::info!(%addr, base_path = %base, "PostfixAdmin-RS listening");
|
|
|
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
|
axum::serve(
|
|
listener,
|
|
app.into_make_service_with_connect_info::<SocketAddr>(),
|
|
)
|
|
.await
|
|
.map_err(|error| AppError::Io(std::io::Error::other(error)))?;
|
|
Ok(())
|
|
}
|