Files
BoosAPI/src/config.rs
T

443 lines
13 KiB
Rust

use figment::{
providers::{Env, Format, Toml},
Figment,
};
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub db: DbConfig,
pub auth: AuthConfig,
pub proxy: ProxyConfig,
#[serde(default)]
pub comfyui: ComfyUIConfig,
#[serde(default)]
pub pixelle_video: PixelleVideoConfig,
#[serde(default)]
pub payment: PaymentConfig,
#[serde(default)]
pub jimeng: JimengConfig,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub log_level: String,
pub cors_origins: Vec<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct DbConfig {
pub url: String,
pub max_connections: u32,
pub ssl: bool,
pub runtime_db_url: Option<String>,
pub runtime_db_dialect: Option<String>,
pub runtime_db_ssl: Option<bool>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct AuthConfig {
pub jwt_secret: String,
pub jwt_expiry_hours: i64,
pub auth_token: String,
pub proxy_token: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ProxyConfig {
pub upstream_timeout_secs: u64,
pub first_byte_timeout_secs: u64,
pub max_retries: u32,
pub proxy_log_retention_days: u32,
#[serde(default)]
pub template_proxy_url: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ComfyUIConfig {
pub enabled: bool,
#[serde(default)]
pub backend: ComfyUIBackend,
#[serde(default)]
pub money_printer_video_cost: f64,
#[serde(default)]
pub digital_human_video_cost: f64,
#[serde(default = "default_workflow_points_cost")]
pub workflow_points_cost: f64,
#[serde(default = "default_comfyui_docker_image")]
pub docker_image: String,
#[serde(default = "default_comfyui_container_name")]
pub container_name: String,
pub install_dir: Option<PathBuf>,
pub python_bin: String,
pub port: u16,
pub extra_args: Vec<String>,
pub max_restarts: u32,
pub external_url: Option<String>,
pub base_url: Option<String>,
pub api_key: Option<String>,
pub agent_model: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct PixelleVideoConfig {
pub enabled: bool,
pub api_base_url: Option<String>,
pub api_key: Option<String>,
#[serde(default = "default_pixelle_backend_provider")]
pub backend_provider: String,
pub frame_template: String,
pub media_workflow: Option<String>,
pub tts_workflow: Option<String>,
pub runninghub_media_workflow: Option<String>,
pub runninghub_tts_workflow: Option<String>,
pub mode: String,
pub n_scenes: i64,
pub video_fps: i64,
pub poll_interval_secs: u64,
pub timeout_secs: u64,
#[serde(default = "default_pixelle_config_path")]
pub config_path: String,
#[serde(default = "default_pixelle_compose_dir")]
pub compose_dir: String,
}
/// Jimeng (Volcengine Visual / CV) async generation provider.
///
/// Credentials are read from config or the `BOOSAPI_JIMENG__*` environment
/// variables (e.g. `BOOSAPI_JIMENG__ACCESS_KEY`) and never hard-coded. The
/// provider is gated behind `enabled`; when false the routes short-circuit
/// with a "capability disabled" response and no upstream calls are made.
#[derive(Debug, Deserialize, Clone)]
pub struct JimengConfig {
/// Master switch. Defaults to false until AK/SK are configured.
#[serde(default)]
pub enabled: bool,
/// Volcengine access key id.
#[serde(default)]
pub access_key: String,
/// Volcengine secret access key.
#[serde(default)]
pub secret_key: String,
/// Optional STS session token (sent as `X-Security-Token` when present).
#[serde(default)]
pub session_token: Option<String>,
/// Signing region.
#[serde(default = "default_jimeng_region")]
pub region: String,
/// API host.
#[serde(default = "default_jimeng_host")]
pub host: String,
/// Poll interval for image tasks (seconds).
#[serde(default = "default_jimeng_image_poll_interval_secs")]
pub image_poll_interval_secs: u64,
/// Overall timeout for image tasks (seconds).
#[serde(default = "default_jimeng_image_timeout_secs")]
pub image_timeout_secs: u64,
/// Poll interval for video tasks (seconds).
#[serde(default = "default_jimeng_video_poll_interval_secs")]
pub video_poll_interval_secs: u64,
/// Overall timeout for video tasks (seconds).
#[serde(default = "default_jimeng_video_timeout_secs")]
pub video_timeout_secs: u64,
/// Cost charged per successful image generation.
#[serde(default)]
pub image_cost: f64,
/// Cost charged per successful video generation.
#[serde(default)]
pub video_cost: f64,
}
#[derive(Debug, Deserialize, Clone)]
pub struct PaymentConfig {
pub enabled: bool,
#[serde(default)]
pub provider: PaymentProvider,
pub public_base_url: Option<String>,
pub product_name_prefix: String,
pub currency: String,
pub min_amount_usd: f64,
pub max_amount_usd: Option<f64>,
pub daily_limit_usd: Option<f64>,
pub order_timeout_minutes: i64,
pub max_pending_orders: i64,
pub stripe_secret_key: Option<String>,
pub stripe_webhook_secret: Option<String>,
pub comfy_customer_token: Option<String>,
pub comfy_customer_token_endpoint: Option<String>,
pub comfy_firebase_api_key: Option<String>,
pub comfy_oauth_client_id: Option<String>,
pub comfy_oauth_client_secret: Option<String>,
pub comfy_oauth_refresh_token: Option<String>,
pub comfy_customer_api_base_url: Option<String>,
pub comfy_checkout_endpoint: Option<String>,
pub success_path: String,
pub cancel_path: String,
/// When true, confirming a comfy_cloud recharge grants workflow points
/// (amount_usd * usd_to_points_rate) instead of crediting the USD pool.
#[serde(default = "default_comfy_recharge_to_points")]
pub comfy_recharge_to_points: bool,
/// Points granted per 1 USD when comfy_recharge_to_points is enabled.
#[serde(default = "default_usd_to_points_rate")]
pub usd_to_points_rate: f64,
}
#[derive(Debug, Deserialize, Clone, Default, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PaymentProvider {
#[default]
ComfyCloud,
Stripe,
}
#[derive(Debug, Deserialize, Clone, Default)]
#[serde(rename_all = "snake_case")]
pub enum ComfyUIBackend {
#[default]
Boosapi,
External,
Cloud,
Runninghub,
Docker,
}
fn default_comfyui_docker_image() -> String {
"boosapi-comfyui:multi-tenant".to_string()
}
fn default_comfyui_container_name() -> String {
"boosapi-comfyui-workflow".to_string()
}
fn default_pixelle_backend_provider() -> String {
"comfy".to_string()
}
fn default_pixelle_config_path() -> String {
"/opt/Pixelle-Video/config.yaml".to_string()
}
fn default_pixelle_compose_dir() -> String {
"/opt/Pixelle-Video".to_string()
}
fn default_jimeng_region() -> String {
"cn-north-1".to_string()
}
fn default_jimeng_host() -> String {
"visual.volcengineapi.com".to_string()
}
fn default_jimeng_image_poll_interval_secs() -> u64 {
2
}
fn default_jimeng_image_timeout_secs() -> u64 {
120
}
fn default_jimeng_video_poll_interval_secs() -> u64 {
8
}
fn default_jimeng_video_timeout_secs() -> u64 {
600
}
fn default_workflow_points_cost() -> f64 {
10.0
}
fn default_comfy_recharge_to_points() -> bool {
true
}
fn default_usd_to_points_rate() -> f64 {
100.0
}
impl Default for ComfyUIConfig {
fn default() -> Self {
Self {
enabled: false,
backend: ComfyUIBackend::Boosapi,
money_printer_video_cost: 0.0,
digital_human_video_cost: 0.0,
workflow_points_cost: 10.0,
docker_image: default_comfyui_docker_image(),
container_name: default_comfyui_container_name(),
install_dir: None,
python_bin: "python3".to_string(),
port: 8188,
extra_args: Vec::new(),
max_restarts: 5,
external_url: None,
base_url: Some("http://127.0.0.1:8188".to_string()),
api_key: None,
agent_model: "gpt-4o".to_string(),
}
}
}
impl Default for PixelleVideoConfig {
fn default() -> Self {
Self {
enabled: false,
api_base_url: Some("http://127.0.0.1:8000".to_string()),
api_key: None,
backend_provider: default_pixelle_backend_provider(),
frame_template: "1080x1920/image_default.html".to_string(),
media_workflow: None,
tts_workflow: None,
runninghub_media_workflow: Some("runninghub/video_wan2.1_fusionx.json".to_string()),
runninghub_tts_workflow: Some("runninghub/tts_edge.json".to_string()),
mode: "fixed".to_string(),
n_scenes: 5,
video_fps: 30,
poll_interval_secs: 5,
timeout_secs: 900,
config_path: default_pixelle_config_path(),
compose_dir: default_pixelle_compose_dir(),
}
}
}
impl Default for PaymentConfig {
fn default() -> Self {
Self {
enabled: false,
provider: PaymentProvider::ComfyCloud,
public_base_url: None,
product_name_prefix: "BossAI 账户充值".to_string(),
currency: "usd".to_string(),
min_amount_usd: 1.0,
max_amount_usd: Some(100_000.0),
daily_limit_usd: None,
order_timeout_minutes: 30,
max_pending_orders: 3,
stripe_secret_key: None,
stripe_webhook_secret: None,
comfy_customer_token: None,
comfy_customer_token_endpoint: None,
comfy_firebase_api_key: None,
comfy_oauth_client_id: None,
comfy_oauth_client_secret: None,
comfy_oauth_refresh_token: None,
comfy_customer_api_base_url: None,
comfy_checkout_endpoint: None,
success_path: "/user/recharge?payment=success&order={ORDER_ID}".to_string(),
cancel_path: "/user/recharge?payment=cancel&order={ORDER_ID}".to_string(),
comfy_recharge_to_points: true,
usd_to_points_rate: 100.0,
}
}
}
impl Default for JimengConfig {
fn default() -> Self {
Self {
enabled: false,
access_key: String::new(),
secret_key: String::new(),
session_token: None,
region: default_jimeng_region(),
host: default_jimeng_host(),
image_poll_interval_secs: default_jimeng_image_poll_interval_secs(),
image_timeout_secs: default_jimeng_image_timeout_secs(),
video_poll_interval_secs: default_jimeng_video_poll_interval_secs(),
video_timeout_secs: default_jimeng_video_timeout_secs(),
image_cost: 0.0,
video_cost: 0.0,
}
}
}
impl Default for AppConfig {
fn default() -> Self {
Self {
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 4000,
log_level: "info".to_string(),
cors_origins: vec!["*".to_string()],
},
db: DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 1,
ssl: false,
runtime_db_url: None,
runtime_db_dialect: None,
runtime_db_ssl: None,
},
auth: AuthConfig {
jwt_secret: "test-secret".to_string(),
jwt_expiry_hours: 72,
auth_token: "test-admin-token".to_string(),
proxy_token: "sk-test-proxy".to_string(),
},
proxy: ProxyConfig {
upstream_timeout_secs: 300,
first_byte_timeout_secs: 60,
max_retries: 3,
proxy_log_retention_days: 30,
template_proxy_url: None,
},
comfyui: ComfyUIConfig::default(),
pixelle_video: PixelleVideoConfig::default(),
payment: PaymentConfig::default(),
jimeng: JimengConfig::default(),
}
}
}
impl ComfyUIConfig {
pub fn uses_native_backend(&self) -> bool {
matches!(
self.backend,
ComfyUIBackend::External | ComfyUIBackend::Cloud | ComfyUIBackend::Runninghub
)
}
pub fn uses_cloud_backend(&self) -> bool {
matches!(self.backend, ComfyUIBackend::Cloud)
}
pub fn uses_runninghub_backend(&self) -> bool {
matches!(self.backend, ComfyUIBackend::Runninghub)
}
pub fn uses_docker_backend(&self) -> bool {
matches!(self.backend, ComfyUIBackend::Docker)
}
pub fn target_base_url(&self) -> String {
self.external_url
.as_deref()
.or(self.base_url.as_deref())
.map(str::trim)
.filter(|url| !url.is_empty())
.unwrap_or("http://127.0.0.1:8188")
.trim_end_matches('/')
.to_string()
}
}
impl AppConfig {
pub fn load() -> anyhow::Result<Self> {
Self::load_from_file("config.toml")
}
pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
dotenvy::dotenv().ok();
Ok(Figment::new()
.merge(Toml::file(path))
.merge(Env::prefixed("BOOSAPI_").split("__"))
.extract()?)
}
}