| #![allow(internal_features)] |
| #![feature( |
| addr_parse_ascii, |
| cold_path, |
| hasher_prefixfree_extras, |
| const_trait_impl, |
| const_default, |
| core_intrinsics, |
| associated_type_defaults, |
| sized_type_properties |
| )] |
| #![allow(clippy::redundant_static_lifetimes)] |
|
|
| #[macro_use] |
| extern crate cursor_api; |
|
|
| mod app; |
| mod common; |
| mod core; |
| mod leak; |
| mod natural_args; |
|
|
| use ::axum::{ |
| Router, middleware, |
| routing::{get, post}, |
| }; |
| use ::tokio::signal; |
| use ::tower_http::{cors::CorsLayer, limit::RequestBodyLimitLayer}; |
|
|
| use app::{ |
| config::handle_config_update, |
| constant::{ |
| EMPTY_STRING, EXE_NAME, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BUILD_KEY_PATH, |
| ROUTE_CONFIG_PATH, ROUTE_CONFIG_VERSION_GET_PATH, ROUTE_CPP_CONFIG_PATH, |
| ROUTE_CPP_MODELS_PATH, ROUTE_CPP_STREAM_PATH, ROUTE_ENV_EXAMPLE_PATH, ROUTE_FILE_SYNC_PATH, |
| ROUTE_FILE_UPLOAD_PATH, ROUTE_GEN_CHECKSUM, ROUTE_GEN_HASH, ROUTE_GEN_UUID, |
| ROUTE_GET_TIMESTAMP_HEADER, ROUTE_HEALTH_PATH, ROUTE_LOGS_GET_PATH, ROUTE_LOGS_PATH, |
| ROUTE_LOGS_TOKENS_GET_PATH, ROUTE_PROXIES_ADD_PATH, ROUTE_PROXIES_DELETE_PATH, |
| ROUTE_PROXIES_GET_PATH, ROUTE_PROXIES_PATH, ROUTE_PROXIES_SET_GENERAL_PATH, |
| ROUTE_PROXIES_SET_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, |
| ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_ALIAS_SET_PATH, |
| ROUTE_TOKENS_CONFIG_VERSION_UPDATE_PATH, ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH, |
| ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH, ROUTE_TOKENS_PROXY_SET_PATH, |
| ROUTE_TOKENS_REFRESH_PATH, ROUTE_TOKENS_SET_PATH, ROUTE_TOKENS_STATUS_SET_PATH, |
| ROUTE_TOKENS_TIMEZONE_SET_PATH, VERSION, |
| }, |
| lazy::AUTH_TOKEN, |
| model::{AppConfig, AppState}, |
| }; |
| use common::utils::parse_from_env; |
| use core::{ |
| middleware::{admin_auth_middleware, cpp_auth_middleware, v1_auth_middleware}, |
| route::{ |
| handle_about, handle_add_proxy, handle_add_tokens, handle_api_page, handle_build_key, |
| handle_build_key_page, handle_config_page, handle_delete_proxies, handle_delete_tokens, |
| handle_env_example, handle_gen_checksum, handle_gen_hash, handle_gen_uuid, |
| handle_get_config_version, handle_get_logs, handle_get_logs_tokens, handle_get_proxies, |
| handle_get_timestamp_header, handle_get_tokens, handle_health, handle_logs, handle_options, |
| handle_proxies_page, handle_readme, handle_refresh_tokens, handle_root, |
| handle_set_general_proxy, handle_set_proxies, handle_set_tokens, handle_set_tokens_alias, |
| handle_set_tokens_proxy, handle_set_tokens_status, handle_set_tokens_timezone, |
| handle_static, handle_tokens_page, handle_update_tokens_config_version, |
| handle_update_tokens_profile, |
| }, |
| service::{ |
| cpp::{ |
| handle_cpp_config, handle_cpp_models, handle_stream_cpp, handle_sync_file, |
| handle_upload_file, |
| }, |
| handle_chat_completions, handle_messages, handle_models, handle_raw_models, |
| }, |
| }; |
| use natural_args::{DEFAULT_LISTEN_HOST, ENV_HOST, ENV_PORT}; |
|
|
| #[tokio::main] |
| async fn main() { |
| |
| #[cfg(not(debug_assertions))] |
| ::std::panic::set_hook(Box::new(|info| { |
| __cold_path!(); |
| |
| if let Some(msg) = info.payload().downcast_ref::<String>() { |
| __eprintln!(msg); |
| } else if let Some(msg) = info.payload().downcast_ref::<&str>() { |
| __eprintln!(msg); |
| } |
| })); |
|
|
| |
| { |
| let current_exe = __unwrap_panic!(std::env::current_exe()); |
| let file_name = __unwrap_panic!( |
| current_exe |
| .file_name() |
| .and_then(|s| s.to_str()) |
| .ok_or("filename") |
| ); |
| let expect = __unwrap_panic!(current_exe.parent().ok_or("parent")) |
| .join(EXE_NAME) |
| .is_file(); |
|
|
| if file_name != EXE_NAME { |
| if expect { |
| println!( |
| "Oh, I see you already have a {EXE_NAME} sitting there. Multiple versions? How adventurous of you!" |
| ) |
| } else { |
| println!("{file_name}? Really? *{EXE_NAME}* was literally right there!"); |
| }; |
| } |
|
|
| |
| natural_args::process_args(file_name); |
| } |
|
|
| |
|
|
| if AUTH_TOKEN.is_empty() { |
| __cold_path!(); |
| __eprintln!("AUTH_TOKEN must be set\n"); |
| std::process::exit(1); |
| }; |
|
|
| |
| AppConfig::init(); |
|
|
| |
| let state = std::sync::Arc::new(__unwrap_panic!(AppState::load().await)); |
|
|
| |
| if let Err(e) = AppConfig::load() { |
| __cold_path!(); |
| eprintln!("加载保存的配置失败: {e}"); |
| } |
|
|
| |
| let state_for_reload = state.clone(); |
|
|
| |
| tokio::spawn(async move { |
| use crate::app::model::TimestampHeader; |
| let state = state_for_reload; |
| let mut counter = 29u8; |
|
|
| loop { |
| let now = common::utils::now_secs(); |
| let current_kilo = now / 1000; |
|
|
| |
| TimestampHeader::update_global_with(current_kilo); |
|
|
| |
| let wait_duration = (current_kilo + 1) * 1000 - now; |
| ::tokio::time::sleep(::core::time::Duration::from_secs(wait_duration)).await; |
|
|
| |
| counter += 1; |
| if counter >= 30 { |
| state.update_client_key().await; |
| counter = 0; |
| } |
| } |
| }); |
|
|
| |
| let state_for_shutdown = state.clone(); |
|
|
| |
| let shutdown_signal = async move { |
| let ctrl_c = async { |
| signal::ctrl_c() |
| .await |
| .expect("failed to install Ctrl+C handler"); |
| }; |
|
|
| #[cfg(unix)] |
| { |
| let terminate = async { |
| signal::unix::signal(signal::unix::SignalKind::terminate()) |
| .expect("failed to install signal handler") |
| .recv() |
| .await; |
| }; |
|
|
| tokio::select! { |
| _ = ctrl_c => {}, |
| _ = terminate => {}, |
| } |
| } |
|
|
| #[cfg(not(unix))] |
| { |
| ctrl_c.await; |
| } |
|
|
| __println!("正在关闭服务器..."); |
|
|
| |
| if let Err(e) = AppConfig::save() { |
| __cold_path!(); |
| eprintln!("保存配置失败: {e}"); |
| } else { |
| __println!("配置已保存"); |
| } |
|
|
| |
| if let Err(e) = state_for_shutdown.save().await { |
| __cold_path!(); |
| eprintln!("保存状态失败: {e}"); |
| } else { |
| __println!("状态已保存"); |
| } |
|
|
| app::lazy::log::flush_all_debug_logs().await; |
| }; |
|
|
| |
| let app = { |
| let ( |
| route_raw_models_path, |
| route_models_path, |
| route_chat_completions_path, |
| route_messages_path, |
| ) = { |
| define_typed_constants! { |
| &'static str => { |
| RAW_MODELS_PATH = "/raw/models", |
| MODELS_PATH = "/v1/models", |
| CHAT_COMPLETIONS_PATH = "/v1/chat/completions", |
| MESSAGES_PATH = "/v1/messages", |
| } |
| } |
| use ::std::borrow::Cow; |
|
|
| let route_prefix = parse_from_env("ROUTE_PREFIX", EMPTY_STRING); |
| if route_prefix.is_empty() { |
| ( |
| Cow::Borrowed(RAW_MODELS_PATH), |
| Cow::Borrowed(MODELS_PATH), |
| Cow::Borrowed(CHAT_COMPLETIONS_PATH), |
| Cow::Borrowed(MESSAGES_PATH), |
| ) |
| } else { |
| #[inline] |
| fn make_route(route_prefix: &str, path: &'static str) -> Cow<'static, str> { |
| let mut route_path = String::with_capacity(path.len() + route_prefix.len()); |
| route_path.push_str(route_prefix); |
| route_path.push_str(path); |
| Cow::Owned(route_path) |
| } |
|
|
| ( |
| make_route(&route_prefix, RAW_MODELS_PATH), |
| make_route(&route_prefix, MODELS_PATH), |
| make_route(&route_prefix, CHAT_COMPLETIONS_PATH), |
| make_route(&route_prefix, MESSAGES_PATH), |
| ) |
| } |
| }; |
| Router::new() |
| .without_v07_checks() |
| .route(ROUTE_ROOT_PATH, get(handle_root)) |
| .route(ROUTE_HEALTH_PATH, get(handle_health)) |
| .route(ROUTE_TOKENS_PATH, get(handle_tokens_page)) |
| .route(ROUTE_PROXIES_PATH, get(handle_proxies_page)) |
| .merge( |
| Router::new() |
| .without_v07_checks() |
| .route(ROUTE_TOKENS_GET_PATH, post(handle_get_tokens)) |
| .route(ROUTE_TOKENS_SET_PATH, post(handle_set_tokens)) |
| .route(ROUTE_TOKENS_ADD_PATH, post(handle_add_tokens)) |
| .route(ROUTE_TOKENS_DELETE_PATH, post(handle_delete_tokens)) |
| .route(ROUTE_TOKENS_ALIAS_SET_PATH, post(handle_set_tokens_alias)) |
| .route( |
| ROUTE_TOKENS_PROFILE_UPDATE_PATH, |
| post(handle_update_tokens_profile), |
| ) |
| .route( |
| ROUTE_TOKENS_CONFIG_VERSION_UPDATE_PATH, |
| post(handle_update_tokens_config_version), |
| ) |
| .route(ROUTE_TOKENS_REFRESH_PATH, post(handle_refresh_tokens)) |
| .route(ROUTE_TOKENS_STATUS_SET_PATH, post(handle_set_tokens_status)) |
| .route(ROUTE_TOKENS_PROXY_SET_PATH, post(handle_set_tokens_proxy)) |
| .route( |
| ROUTE_TOKENS_TIMEZONE_SET_PATH, |
| post(handle_set_tokens_timezone), |
| ) |
| .route(ROUTE_PROXIES_GET_PATH, post(handle_get_proxies)) |
| .route(ROUTE_PROXIES_SET_PATH, post(handle_set_proxies)) |
| .route(ROUTE_PROXIES_ADD_PATH, post(handle_add_proxy)) |
| .route(ROUTE_PROXIES_DELETE_PATH, post(handle_delete_proxies)) |
| .route( |
| ROUTE_PROXIES_SET_GENERAL_PATH, |
| post(handle_set_general_proxy), |
| ) |
| .route_layer(middleware::from_fn(admin_auth_middleware)), |
| ) |
| .merge( |
| Router::new() |
| .without_v07_checks() |
| .route(ROUTE_CPP_CONFIG_PATH, post(handle_cpp_config)) |
| .route(ROUTE_CPP_MODELS_PATH, post(handle_cpp_models)) |
| .route(ROUTE_FILE_UPLOAD_PATH, post(handle_upload_file)) |
| .route(ROUTE_FILE_SYNC_PATH, post(handle_sync_file)) |
| .route(ROUTE_CPP_STREAM_PATH, post(handle_stream_cpp)) |
| .route_layer(middleware::from_fn_with_state( |
| state.clone(), |
| cpp_auth_middleware, |
| )), |
| ) |
| .route(&route_raw_models_path, get(handle_raw_models)) |
| .route( |
| &route_models_path, |
| get(handle_models).options(handle_options), |
| ) |
| .route( |
| &route_messages_path, |
| post(handle_messages) |
| .route_layer(middleware::from_fn_with_state( |
| state.clone(), |
| v1_auth_middleware, |
| )) |
| .options(handle_options), |
| ) |
| .route( |
| &route_chat_completions_path, |
| post(handle_chat_completions) |
| .route_layer(middleware::from_fn_with_state( |
| state.clone(), |
| v1_auth_middleware, |
| )) |
| .options(handle_options), |
| ) |
| .route(ROUTE_LOGS_PATH, get(handle_logs)) |
| .route(ROUTE_LOGS_GET_PATH, post(handle_get_logs)) |
| .route(ROUTE_LOGS_TOKENS_GET_PATH, post(handle_get_logs_tokens)) |
| .route(ROUTE_ENV_EXAMPLE_PATH, get(handle_env_example)) |
| .route( |
| ROUTE_CONFIG_PATH, |
| get(handle_config_page).post(handle_config_update), |
| ) |
| .route( |
| ROUTE_STATIC_PATH, |
| get(handle_static).options(handle_options), |
| ) |
| .route(ROUTE_ABOUT_PATH, get(handle_about)) |
| .route(ROUTE_README_PATH, get(handle_readme)) |
| .route(ROUTE_API_PATH, get(handle_api_page)) |
| .route(ROUTE_GEN_UUID, get(handle_gen_uuid)) |
| .route(ROUTE_GEN_HASH, get(handle_gen_hash)) |
| .route(ROUTE_GEN_CHECKSUM, get(handle_gen_checksum)) |
| .route(ROUTE_GET_TIMESTAMP_HEADER, get(handle_get_timestamp_header)) |
| |
| |
| .route( |
| ROUTE_BUILD_KEY_PATH, |
| get(handle_build_key_page).post(handle_build_key), |
| ) |
| .route( |
| ROUTE_CONFIG_VERSION_GET_PATH, |
| post(handle_get_config_version), |
| ) |
| |
| .layer(RequestBodyLimitLayer::new(parse_from_env( |
| "REQUEST_BODY_LIMIT", |
| 2_000_000, |
| ))) |
| .layer(CorsLayer::permissive()) |
| .with_state(state) |
| }; |
|
|
| |
| let listener = { |
| use std::net::{IpAddr, Ipv4Addr, SocketAddr}; |
| let port = { |
| std::env::var(ENV_PORT) |
| .ok() |
| .and_then(|v| v.trim().parse().ok()) |
| .unwrap_or(3000) |
| }; |
| let addr = SocketAddr::new( |
| IpAddr::parse_ascii(parse_from_env(ENV_HOST, DEFAULT_LISTEN_HOST).as_bytes()) |
| .unwrap_or_else(|e| { |
| __cold_path!(); |
| eprintln!("无法解析IP: {e}"); |
| IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) |
| }), |
| port, |
| ); |
| println!("服务器运行在 {addr}"); |
| tokio::net::TcpListener::bind(addr) |
| .await |
| .unwrap_or_else(|e| { |
| __cold_path!(); |
| eprintln!("无法绑定到地址 {addr}: {e}"); |
| std::process::exit(1); |
| }) |
| }; |
| println!("当前版本: v{VERSION}"); |
| #[cfg(feature = "__preview")] |
| { |
| __println!("当前是测试版,有问题及时反馈哦~"); |
| } |
| common::time::print_project_age(); |
| common::time::print_build_age(); |
|
|
| let start_time = app::lazy::get_start_time(); |
| let server = axum::serve(listener, app); |
| tokio::select! { |
| result = server => { |
| if let Err(e) = result { |
| __cold_path!(); |
| eprintln!("服务器错误: {e}"); |
| } |
| } |
| _ = shutdown_signal => { |
| println!( |
| "运行时间: {}", |
| common::utils::duration_fmt::human(__unwrap!( |
| app::model::DateTime::naive_now() |
| .signed_duration_since(*start_time) |
| .to_std() |
| )) |
| .format(common::utils::duration_fmt::DurationFormat::Random) |
| .language(common::utils::duration_fmt::Language::Random) |
| ); |
| common::time::print_project_age(); |
| common::time::print_build_age(); |
| __println!("服务器已关闭"); |
| } |
| } |
| } |
|
|