use std::convert::TryFrom; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; use warp::Filter; use structopt::StructOpt; use tokio::net::UnixListener; use geoffrey_models::logging::LogLevel; use geoffrey_models::models::parameters::add_token_params::AddTokenParams; use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::token::Permissions; use crate::commands::add_token::AddToken; use crate::commands::{command_filter, Command}; use crate::config::GeoffreyAPIConfig; use crate::context::Context; use crate::logging::init_logging; use crate::model::{local_model_filter, remote_model_filter}; use tokio_stream::wrappers::UnixListenerStream; mod api_endpoint; mod commands; mod config; mod context; mod helper; mod logging; mod model; pub type Result = std::result::Result; #[derive(Debug, StructOpt, Clone, Default)] #[structopt(name = "GeoffreyAPI", about = "Geoffrey Central API")] pub struct Args { #[structopt(env = "GEOFFREY_CONFIG", parse(from_os_str))] config: PathBuf, #[structopt( short, long, env = "GEOFFREY_LOG_LEVEL", parse(from_str), default_value = "Info" )] log_level: LogLevel, #[structopt(short, long)] force_migration: bool, #[structopt(subcommand)] command: GeoffreyApiCommand, } #[derive(Debug, PartialEq, StructOpt, Clone)] pub enum GeoffreyApiCommand { Run, CreateAdminToken(CreateTokenCommand), } impl Default for GeoffreyApiCommand { fn default() -> Self { Self::Run } } #[derive(Debug, StructOpt, PartialEq, Clone)] pub struct CreateTokenCommand { #[structopt(parse(try_from_str = Permissions::try_from))] pub permissions: Vec, } async fn run_local_server(ctx: &Arc) { let socket_path = ctx .cfg .server_config .local_socket .as_ref() .expect("Expected local_socket to be defined"); if socket_path.exists() { if let Err(e) = std::fs::remove_file(&socket_path) { log::error!("Unable to cleanup local socket: {}", e); return; } } let listener = match UnixListener::bind(socket_path) { Ok(l) => l, Err(e) => { log::error!("Unable to open local socket: {}", e); return; } }; let unix_listener_stream = UnixListenerStream::new(listener); let local_api = local_model_filter(ctx.clone()); warp::serve(local_api.clone()) .run_incoming(unix_listener_stream) .await; } async fn run_remote_server(ctx: &Arc) { let socket_addr = match SocketAddr::from_str(ctx.cfg.server_config.host.as_str()) { Ok(socket_addr) => socket_addr, Err(e) => { log::warn!( "Error parsing {} as address: {}", ctx.cfg.server_config.host, e ); return; } }; let api = command_filter(ctx.clone()).or(remote_model_filter(ctx.clone())); warp::serve(api).run(socket_addr).await; } async fn run_server(ctx: Arc) { tokio::select! { // Start local api _ = run_local_server(&ctx), if ctx.cfg.server_config.local_socket.is_some() => { log::warn!("Socket API exited") } // Start HTTP api _ = run_remote_server(&ctx) => { log::warn!("HTTP API exited") } } } fn create_token(ctx: Arc, perms: Vec) { match AddToken::run_command(ctx, &AddTokenParams { permissions: perms }, None) { Ok(token) => { // Don't log this to keep tokens out of the log println!("Added admin token with secret: {}", token.secret) } Err(e) => { log::warn!("Unable to create admin token: {}", e) } } } #[tokio::main] async fn main() { let args: Args = Args::from_args(); if let Err(e) = init_logging(args.log_level) { println!("Unable to initialize logger: {}", e); return; } let cfg = match GeoffreyAPIConfig::new(args.config.as_path()) { Ok(cfg) => cfg, Err(e) => { log::warn!("Error opening config: {}", e); return; } }; let ctx = Context::new(cfg, args.clone()).unwrap(); match args.command { GeoffreyApiCommand::Run => run_server(ctx).await, GeoffreyApiCommand::CreateAdminToken(perms) => create_token(ctx, perms.permissions), }; }