110 lines
3.1 KiB
Rust
110 lines
3.1 KiB
Rust
mod api;
|
|
mod app_state;
|
|
mod audit_log;
|
|
mod auth;
|
|
mod config;
|
|
mod control_plane;
|
|
mod db;
|
|
mod models;
|
|
mod oidc;
|
|
mod permissions;
|
|
mod rbac;
|
|
|
|
use crate::api::auth::ensure_bootstrap_admin;
|
|
use crate::app_state::AppState;
|
|
use crate::config::Config;
|
|
use axum::routing::get;
|
|
use axum::Router;
|
|
use std::net::SocketAddr;
|
|
use tower_http::cors::{AllowOrigin, CorsLayer, Any};
|
|
use tower_http::services::{ServeDir, ServeFile};
|
|
use tower_http::trace::TraceLayer;
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(EnvFilter::from_default_env())
|
|
.init();
|
|
|
|
let config = Config::load()?;
|
|
let pool = db::connect(&config.database).await?;
|
|
|
|
let mut disable_migration_locking = config.database.disable_migration_locking;
|
|
if !disable_migration_locking {
|
|
match db::is_cockroach(&pool).await {
|
|
Ok(true) => {
|
|
tracing::info!("detected CockroachDB, disabling SQLx migration locking");
|
|
disable_migration_locking = true;
|
|
}
|
|
Ok(false) => {}
|
|
Err(err) => {
|
|
tracing::warn!(
|
|
error = ?err,
|
|
"failed to detect database engine, keeping migration locking enabled"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut migrator = sqlx::migrate!("./migrations");
|
|
if disable_migration_locking {
|
|
// CockroachDB does not implement pg_advisory_lock used by SQLx migration locking.
|
|
migrator.set_locking(false);
|
|
}
|
|
migrator.run(&pool).await?;
|
|
|
|
ensure_bootstrap_admin(&pool, &config).await?;
|
|
|
|
let oidc = oidc::load_providers(&config.oidc, &config.server.base_url).await?;
|
|
let state = AppState {
|
|
pool,
|
|
config: config.clone(),
|
|
oidc,
|
|
};
|
|
|
|
let mut app = Router::new()
|
|
.route("/healthz", get(|| async { "ok" }))
|
|
.nest("/admin/api", api::router())
|
|
.layer(TraceLayer::new_for_http());
|
|
|
|
if let Some(static_dir) = &config.server.static_dir {
|
|
let index_path = format!("{}/index.html", static_dir.trim_end_matches('/'));
|
|
let service = ServeDir::new(static_dir).fallback(ServeFile::new(index_path));
|
|
app = app.nest_service("/", service);
|
|
}
|
|
|
|
if let Some(cors_layer) = build_cors(&config.server.allowed_origins) {
|
|
app = app.layer(cors_layer);
|
|
}
|
|
|
|
let app = app.with_state(state);
|
|
|
|
let addr: SocketAddr = config.server.bind.parse()?;
|
|
tracing::info!("lightscale-admin listening on {}", addr);
|
|
|
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn build_cors(allowed_origins: &[String]) -> Option<CorsLayer> {
|
|
if allowed_origins.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let origins = allowed_origins
|
|
.iter()
|
|
.map(|origin| origin.parse())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.ok()?;
|
|
|
|
Some(
|
|
CorsLayer::new()
|
|
.allow_origin(AllowOrigin::list(origins))
|
|
.allow_methods(Any)
|
|
.allow_headers(Any)
|
|
.allow_credentials(true),
|
|
)
|
|
}
|