111 lines
4.1 KiB
Rust
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");
|
|
}
|