Initial public release v0.1.0
This commit is contained in:
commit
6c39e0bfbf
87 files changed
+10867
No files matched your search
+154
@@ -0,0 +1,154 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use clap::Parser;
|
||||
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
use nx9_auth::{
|
||||
api,
|
||||
cli::{self, Cli, Commands},
|
||||
config::Config,
|
||||
db,
|
||||
db::repository::sessions as session_repo,
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Parse CLI arguments first (before any logging so --help works cleanly)
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Initialize logging based on the command and verbosity
|
||||
let is_serve = matches!(cli.command, Commands::Serve);
|
||||
if is_serve {
|
||||
// Structured JSON logging for production server deployment
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "nx9_auth=info,tower_http=info".parse().unwrap()),
|
||||
)
|
||||
.with(fmt::layer().json())
|
||||
.init();
|
||||
} else if cli.verbose {
|
||||
// Human-readable compact logging for verbosity in subcommands
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "nx9_auth=debug".parse().unwrap()),
|
||||
)
|
||||
.with(fmt::layer().compact())
|
||||
.init();
|
||||
} else {
|
||||
// Silence info/debug logging for clean operator CLI commands
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "nx9_auth=warn".parse().unwrap()),
|
||||
)
|
||||
.with(fmt::layer().compact())
|
||||
.init();
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
let config_opt = if matches!(
|
||||
cli.command,
|
||||
Commands::Init { .. } | Commands::ConfigPath { .. }
|
||||
) {
|
||||
// For init/config-path commands, a missing override config is fine
|
||||
if let Some(ref path) = cli.config {
|
||||
if path.exists() {
|
||||
Some(Config::load(path)?)
|
||||
} else {
|
||||
let mut cfg = Config {
|
||||
config_path: Some(path.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
cfg.resolve_paths();
|
||||
Some(cfg)
|
||||
}
|
||||
} else {
|
||||
Config::find_and_load(None)?
|
||||
}
|
||||
} else {
|
||||
Config::find_and_load(cli.config.as_deref())?
|
||||
};
|
||||
|
||||
let config = match config_opt {
|
||||
Some(cfg) => cfg,
|
||||
None => {
|
||||
// init and config-path are allowed to run without an existing config file.
|
||||
// We use default Config structure for them.
|
||||
if matches!(
|
||||
cli.command,
|
||||
Commands::Init { .. } | Commands::ConfigPath { .. }
|
||||
) {
|
||||
let mut cfg = Config::default();
|
||||
cfg.resolve_paths();
|
||||
cfg
|
||||
} else {
|
||||
eprintln!(
|
||||
"\nError: No configuration found.\n\nRun:\n\n nx9-auth init\n\nOr if running in Docker:\n\n docker exec -it nx9-auth nx9-auth init\n"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
git_commit = env!("GIT_COMMIT"),
|
||||
"nx9-auth starting"
|
||||
);
|
||||
|
||||
// Dispatch to serve or CLI command
|
||||
match cli.command {
|
||||
Commands::Serve => run_server(config).await,
|
||||
cmd => cli::run(cmd, config).await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the HTTP server (Milestone B+).
|
||||
async fn run_server(config: Config) -> anyhow::Result<()> {
|
||||
// Open DB pool and run migrations
|
||||
let pool = db::create_pool(&config.database.path).await?;
|
||||
db::run_migrations(&pool).await?;
|
||||
|
||||
// Cleanup expired sessions at startup (one-shot, fire-and-forget)
|
||||
let pool_clone = pool.clone();
|
||||
tokio::spawn(async move {
|
||||
match session_repo::cleanup_expired(&pool_clone).await {
|
||||
Ok(n) => tracing::info!(removed = n, "expired sessions cleaned up"),
|
||||
Err(e) => tracing::warn!(error = %e, "session cleanup failed"),
|
||||
}
|
||||
});
|
||||
|
||||
// Build application state
|
||||
let state = AppState::new(pool, config.clone());
|
||||
|
||||
// Build router
|
||||
let app = api::router::build(state);
|
||||
|
||||
// Bind and serve
|
||||
let addr: SocketAddr = format!("{}:{}", config.server.host, config.server.port)
|
||||
.parse()
|
||||
.map_err(|e| anyhow::anyhow!("invalid bind address: {}", e))?;
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
|
||||
tracing::info!(
|
||||
address = %addr,
|
||||
"server listening"
|
||||
);
|
||||
|
||||
println!(
|
||||
"\nServer listening on:\n\n http://{}\n\nHealth:\n\n http://{}/health\n",
|
||||
addr, addr
|
||||
);
|
||||
|
||||
axum::serve(
|
||||
listener,
|
||||
app.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in new issue
Block a user