Files
furumi-ng/furumi-web-player/src/main.rs
AB-UK ff3ad15b95
All checks were successful
Publish Server Image / build-and-push-image (push) Successful in 2m7s
New player
2026-03-18 02:44:59 +00:00

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(())
}