first commit
This commit is contained in:
commit
2eb8aac027
91 files changed
+20139
No files matched your search
+140
@@ -0,0 +1,140 @@
|
||||
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(())
|
||||
}
|
||||
Reference in new issue
Block a user