All checks were successful
Publish Server Image / build-and-push-image (push) Successful in 2m7s
107 lines
3.5 KiB
Rust
107 lines
3.5 KiB
Rust
mod db;
|
|
mod web;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use clap::Parser;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about = "Furumi Web Player: database-backed music player")]
|
|
struct Args {
|
|
/// IP address and port for the web player
|
|
#[arg(long, env = "FURUMI_PLAYER_BIND", default_value = "0.0.0.0:8080")]
|
|
bind: String,
|
|
|
|
/// PostgreSQL connection URL
|
|
#[arg(long, env = "FURUMI_PLAYER_DATABASE_URL")]
|
|
database_url: String,
|
|
|
|
/// Root directory where music files are stored (agent's storage_dir)
|
|
#[arg(long, env = "FURUMI_PLAYER_STORAGE_DIR")]
|
|
storage_dir: std::path::PathBuf,
|
|
|
|
/// OIDC Issuer URL (e.g. https://auth.example.com/application/o/furumi/)
|
|
#[arg(long, env = "FURUMI_PLAYER_OIDC_ISSUER_URL")]
|
|
oidc_issuer_url: Option<String>,
|
|
|
|
/// OIDC Client ID
|
|
#[arg(long, env = "FURUMI_PLAYER_OIDC_CLIENT_ID")]
|
|
oidc_client_id: Option<String>,
|
|
|
|
/// OIDC Client Secret
|
|
#[arg(long, env = "FURUMI_PLAYER_OIDC_CLIENT_SECRET")]
|
|
oidc_client_secret: Option<String>,
|
|
|
|
/// OIDC Redirect URL (e.g. https://music.example.com/auth/callback)
|
|
#[arg(long, env = "FURUMI_PLAYER_OIDC_REDIRECT_URL")]
|
|
oidc_redirect_url: Option<String>,
|
|
|
|
/// OIDC Session Secret (32+ chars, for HMAC). Random if not provided.
|
|
#[arg(long, env = "FURUMI_PLAYER_OIDC_SESSION_SECRET")]
|
|
oidc_session_secret: Option<String>,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Install ring as the default crypto provider for rustls
|
|
rustls::crypto::ring::default_provider()
|
|
.install_default()
|
|
.expect("Failed to install rustls crypto provider");
|
|
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let args = Args::parse();
|
|
|
|
let version = option_env!("FURUMI_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
|
|
tracing::info!("Furumi Web Player v{} starting", version);
|
|
tracing::info!("Storage directory: {:?}", args.storage_dir);
|
|
|
|
if !args.storage_dir.exists() || !args.storage_dir.is_dir() {
|
|
eprintln!("Error: Storage directory {:?} does not exist or is not a directory", args.storage_dir);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
tracing::info!("Connecting to database...");
|
|
let pool = db::connect(&args.database_url).await?;
|
|
tracing::info!("Database connected");
|
|
|
|
// Initialize OIDC if configured
|
|
let oidc_state = if let (Some(issuer), Some(client_id), Some(secret), Some(redirect)) = (
|
|
args.oidc_issuer_url,
|
|
args.oidc_client_id,
|
|
args.oidc_client_secret,
|
|
args.oidc_redirect_url,
|
|
) {
|
|
tracing::info!("OIDC (SSO): enabled (issuer: {})", issuer);
|
|
match web::auth::oidc_init(issuer, client_id, secret, redirect, args.oidc_session_secret).await {
|
|
Ok(state) => Some(Arc::new(state)),
|
|
Err(e) => {
|
|
eprintln!("Error initializing OIDC: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
} else {
|
|
tracing::info!("OIDC (SSO): disabled (no OIDC configuration provided)");
|
|
None
|
|
};
|
|
|
|
let bind_addr: std::net::SocketAddr = args.bind.parse().unwrap_or_else(|e| {
|
|
eprintln!("Error: Invalid bind address '{}': {}", args.bind, e);
|
|
std::process::exit(1);
|
|
});
|
|
|
|
let state = Arc::new(web::AppState {
|
|
pool,
|
|
storage_dir: Arc::new(args.storage_dir),
|
|
oidc: oidc_state,
|
|
});
|
|
|
|
tracing::info!("Web player: http://{}", bind_addr);
|
|
|
|
let app = web::build_router(state);
|
|
let listener = tokio::net::TcpListener::bind(bind_addr).await?;
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
}
|