Files
ARCADIA-Studio/crates/oa-server/src/main.rs
T

111 lines
4.1 KiB
Rust

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<dyn std::error::Error + Send + Sync>> {
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<Option<ServerConfig>, Box<dyn std::error::Error + Send + Sync>> {
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");
}