use oa_server::{ServerConfig, app, store::Store}; use std::{net::SocketAddr, path::PathBuf, sync::Arc}; #[tokio::main] async fn main() { if let Err(error) = run().await { eprintln!("arcadia-server: {error}"); std::process::exit(1); } } async fn run() -> Result<(), Box> { let Some(config) = tokio::task::spawn_blocking(configuration).await?? else { return Ok(()); }; config.validate()?; let data_dir = config.data_dir.clone(); let store = Arc::new(tokio::task::spawn_blocking(move || Store::open(data_dir)).await??); let listener = tokio::net::TcpListener::bind(config.bind).await?; eprintln!( "arcadia-server {} listening on {}; data {}", env!("CARGO_PKG_VERSION"), listener.local_addr()?, config.data_dir.display() ); axum::serve(listener, app(store, config.bearer_token)) .with_graceful_shutdown(shutdown_signal()) .await?; Ok(()) } fn configuration() -> Result, Box> { let mut bind: SocketAddr = "127.0.0.1:8787".parse()?; let mut data_dir = default_data_dir(); let mut token_file = None; let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { match arg.as_str() { "--help" | "-h" => { println!( "Arcadia model REST service\n\nUsage: arcadia-server [--bind IP:PORT] [--data-dir PATH] [--token-file PATH]\n\nDefaults: 127.0.0.1:8787; platform-local ArcadiaStudio/server directory.\nToken required for non-loopback binds. ARCADIA_SERVER_TOKEN is an alternative.\nKeep the data directory on local storage. See docs/server.md for deployment and API limits." ); return Ok(None); } "--version" | "-V" => { println!("arcadia-server {}", env!("CARGO_PKG_VERSION")); return Ok(None); } "--bind" => bind = args.next().ok_or("--bind requires IP:PORT")?.parse()?, "--data-dir" => { data_dir = PathBuf::from(args.next().ok_or("--data-dir requires a path")?) } "--token-file" => { token_file = Some(PathBuf::from( args.next().ok_or("--token-file requires a path")?, )) } _ => return Err(format!("Unknown option {arg}; use --help.").into()), } } let bearer_token = if let Some(path) = token_file { let token = std::fs::read_to_string(path)?; Some(token.trim_end_matches(['\r', '\n']).to_owned()) } else { std::env::var("ARCADIA_SERVER_TOKEN").ok() }; Ok(Some(ServerConfig { bind, data_dir, bearer_token, })) } fn default_data_dir() -> PathBuf { #[cfg(windows)] if let Some(path) = std::env::var_os("LOCALAPPDATA") { return PathBuf::from(path).join("ArcadiaStudio").join("server"); } if let Some(path) = std::env::var_os("XDG_DATA_HOME") { return PathBuf::from(path).join("arcadia-studio").join("server"); } if let Some(path) = std::env::var_os("HOME") { return PathBuf::from(path).join(".local/share/arcadia-studio/server"); } std::env::temp_dir().join("arcadia-studio-server") } async fn shutdown_signal() { let ctrl_c = async { if let Err(error) = tokio::signal::ctrl_c().await { eprintln!("Could not listen for Ctrl+C: {error}"); } }; #[cfg(unix)] let terminate = async { match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) { Ok(mut signal) => { signal.recv().await; } Err(error) => { eprintln!("Could not listen for SIGTERM: {error}"); std::future::pending::<()>().await; } } }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => {}, _ = terminate => {} } eprintln!("arcadia-server stopping admission and draining requests"); }