Auto-enable noconsole mode when console feature is disabled
This commit is contained in:
parent
5568ef5802
commit
e7fa5bf72c
1 changed files with 237 additions and 184 deletions
277
src/main.rs
277
src/main.rs
|
|
@ -9,47 +9,53 @@ use tikv_jemallocator::Jemalloc;
|
||||||
static GLOBAL: Jemalloc = Jemalloc;
|
static GLOBAL: Jemalloc = Jemalloc;
|
||||||
|
|
||||||
// Module declarations
|
// Module declarations
|
||||||
|
#[cfg(feature = "analytics")]
|
||||||
|
pub mod analytics;
|
||||||
|
#[cfg(feature = "attendant")]
|
||||||
|
pub mod attendant;
|
||||||
#[cfg(feature = "automation")]
|
#[cfg(feature = "automation")]
|
||||||
pub mod auto_task;
|
pub mod auto_task;
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting")]
|
||||||
pub mod basic;
|
pub mod basic;
|
||||||
#[cfg(feature = "billing")]
|
#[cfg(feature = "billing")]
|
||||||
pub mod billing;
|
pub mod billing;
|
||||||
|
pub mod botmodels;
|
||||||
#[cfg(feature = "canvas")]
|
#[cfg(feature = "canvas")]
|
||||||
pub mod canvas;
|
pub mod canvas;
|
||||||
pub mod channels;
|
pub mod channels;
|
||||||
#[cfg(feature = "people")]
|
#[cfg(feature = "people")]
|
||||||
pub mod contacts;
|
pub mod contacts;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
#[cfg(feature = "dashboards")]
|
|
||||||
pub mod shared;
|
|
||||||
pub mod embedded_ui;
|
|
||||||
pub mod maintenance;
|
|
||||||
pub mod multimodal;
|
|
||||||
#[cfg(feature = "player")]
|
|
||||||
pub mod player;
|
|
||||||
#[cfg(feature = "people")]
|
|
||||||
pub mod people;
|
|
||||||
#[cfg(feature = "billing")]
|
|
||||||
pub mod products;
|
|
||||||
pub mod search;
|
|
||||||
pub mod security;
|
|
||||||
#[cfg(feature = "tickets")]
|
|
||||||
pub mod tickets;
|
|
||||||
#[cfg(feature = "attendant")]
|
|
||||||
pub mod attendant;
|
|
||||||
#[cfg(feature = "analytics")]
|
|
||||||
pub mod analytics;
|
|
||||||
#[cfg(feature = "designer")]
|
#[cfg(feature = "designer")]
|
||||||
pub mod designer;
|
pub mod designer;
|
||||||
#[cfg(feature = "docs")]
|
#[cfg(feature = "docs")]
|
||||||
pub mod docs;
|
pub mod docs;
|
||||||
|
pub mod embedded_ui;
|
||||||
#[cfg(feature = "learn")]
|
#[cfg(feature = "learn")]
|
||||||
pub mod learn;
|
pub mod learn;
|
||||||
|
#[cfg(feature = "compliance")]
|
||||||
|
pub mod legal;
|
||||||
|
pub mod maintenance;
|
||||||
|
#[cfg(feature = "monitoring")]
|
||||||
|
pub mod monitoring;
|
||||||
|
pub mod multimodal;
|
||||||
#[cfg(feature = "paper")]
|
#[cfg(feature = "paper")]
|
||||||
pub mod paper;
|
pub mod paper;
|
||||||
|
#[cfg(feature = "people")]
|
||||||
|
pub mod people;
|
||||||
|
#[cfg(feature = "player")]
|
||||||
|
pub mod player;
|
||||||
|
#[cfg(feature = "billing")]
|
||||||
|
pub mod products;
|
||||||
|
#[cfg(feature = "project")]
|
||||||
|
pub mod project;
|
||||||
#[cfg(feature = "research")]
|
#[cfg(feature = "research")]
|
||||||
pub mod research;
|
pub mod research;
|
||||||
|
pub mod search;
|
||||||
|
pub mod security;
|
||||||
|
pub mod settings;
|
||||||
|
#[cfg(feature = "dashboards")]
|
||||||
|
pub mod shared;
|
||||||
#[cfg(feature = "sheet")]
|
#[cfg(feature = "sheet")]
|
||||||
pub mod sheet;
|
pub mod sheet;
|
||||||
#[cfg(feature = "slides")]
|
#[cfg(feature = "slides")]
|
||||||
|
|
@ -58,18 +64,12 @@ pub mod slides;
|
||||||
pub mod social;
|
pub mod social;
|
||||||
#[cfg(feature = "sources")]
|
#[cfg(feature = "sources")]
|
||||||
pub mod sources;
|
pub mod sources;
|
||||||
|
#[cfg(feature = "tickets")]
|
||||||
|
pub mod tickets;
|
||||||
#[cfg(feature = "video")]
|
#[cfg(feature = "video")]
|
||||||
pub mod video;
|
pub mod video;
|
||||||
#[cfg(feature = "monitoring")]
|
|
||||||
pub mod monitoring;
|
|
||||||
#[cfg(feature = "project")]
|
|
||||||
pub mod project;
|
|
||||||
#[cfg(feature = "workspaces")]
|
#[cfg(feature = "workspaces")]
|
||||||
pub mod workspaces;
|
pub mod workspaces;
|
||||||
pub mod botmodels;
|
|
||||||
#[cfg(feature = "compliance")]
|
|
||||||
pub mod legal;
|
|
||||||
pub mod settings;
|
|
||||||
|
|
||||||
#[cfg(feature = "attendant")]
|
#[cfg(feature = "attendant")]
|
||||||
pub mod attendance;
|
pub mod attendance;
|
||||||
|
|
@ -174,9 +174,7 @@ async fn ensure_vendor_files_in_minio(drive: &aws_sdk_s3::Client) {
|
||||||
"../botui/ui/suite/js/vendor/htmx.min.js",
|
"../botui/ui/suite/js/vendor/htmx.min.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
let htmx_content = htmx_paths
|
let htmx_content = htmx_paths.iter().find_map(|path| std::fs::read(path).ok());
|
||||||
.iter()
|
|
||||||
.find_map(|path| std::fs::read(path).ok());
|
|
||||||
|
|
||||||
let Some(content) = htmx_content else {
|
let Some(content) = htmx_content else {
|
||||||
warn!("Could not find htmx.min.js in botui, skipping MinIO upload");
|
warn!("Could not find htmx.min.js in botui, skipping MinIO upload");
|
||||||
|
|
@ -201,18 +199,16 @@ async fn ensure_vendor_files_in_minio(drive: &aws_sdk_s3::Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::security::{
|
use crate::security::{
|
||||||
create_cors_layer, create_rate_limit_layer, create_security_headers_layer,
|
build_default_route_permissions, create_cors_layer, create_rate_limit_layer,
|
||||||
request_id_middleware, security_headers_middleware, set_cors_allowed_origins,
|
create_security_headers_layer, request_id_middleware, security_headers_middleware,
|
||||||
set_global_panic_hook, AuthConfig, HttpRateLimitConfig, PanicHandlerConfig,
|
set_cors_allowed_origins, set_global_panic_hook, ApiKeyAuthProvider, AuthConfig,
|
||||||
SecurityHeadersConfig, AuthProviderBuilder, ApiKeyAuthProvider, JwtConfig, JwtKey,
|
AuthMiddlewareState, AuthProviderBuilder, HttpRateLimitConfig, JwtConfig, JwtKey, JwtManager,
|
||||||
JwtManager, RbacManager, RbacConfig, AuthMiddlewareState,
|
PanicHandlerConfig, RbacConfig, RbacManager, SecurityHeadersConfig,
|
||||||
build_default_route_permissions,
|
|
||||||
};
|
};
|
||||||
use botlib::SystemLimits;
|
use botlib::SystemLimits;
|
||||||
|
|
||||||
use crate::core::shared::memory_monitor::{
|
use crate::core::shared::memory_monitor::{
|
||||||
start_memory_monitor, log_process_memory, MemoryStats,
|
log_process_memory, record_thread_activity, register_thread, start_memory_monitor, MemoryStats,
|
||||||
register_thread, record_thread_activity
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "automation")]
|
#[cfg(feature = "automation")]
|
||||||
|
|
@ -222,22 +218,21 @@ use crate::core::bot;
|
||||||
use crate::core::package_manager;
|
use crate::core::package_manager;
|
||||||
use crate::core::session;
|
use crate::core::session;
|
||||||
|
|
||||||
#[cfg(feature = "automation")]
|
|
||||||
use automation::AutomationService;
|
|
||||||
use bootstrap::BootstrapManager;
|
|
||||||
use crate::core::bot::channels::{VoiceAdapter, WebChannelAdapter};
|
use crate::core::bot::channels::{VoiceAdapter, WebChannelAdapter};
|
||||||
use crate::core::bot::websocket_handler;
|
use crate::core::bot::websocket_handler;
|
||||||
use crate::core::bot::BotOrchestrator;
|
use crate::core::bot::BotOrchestrator;
|
||||||
use crate::core::bot_database::BotDatabaseManager;
|
use crate::core::bot_database::BotDatabaseManager;
|
||||||
use crate::core::config::AppConfig;
|
use crate::core::config::AppConfig;
|
||||||
|
#[cfg(feature = "automation")]
|
||||||
|
use automation::AutomationService;
|
||||||
|
use bootstrap::BootstrapManager;
|
||||||
|
|
||||||
use package_manager::InstallMode;
|
|
||||||
use session::{create_session, get_session_history, get_sessions, start_session};
|
|
||||||
use crate::shared::state::AppState;
|
use crate::shared::state::AppState;
|
||||||
use crate::shared::utils::create_conn;
|
use crate::shared::utils::create_conn;
|
||||||
#[cfg(feature = "drive")]
|
#[cfg(feature = "drive")]
|
||||||
use crate::shared::utils::create_s3_operator;
|
use crate::shared::utils::create_s3_operator;
|
||||||
|
use package_manager::InstallMode;
|
||||||
|
use session::{create_session, get_session_history, get_sessions, start_session};
|
||||||
|
|
||||||
async fn health_check(State(state): State<Arc<AppState>>) -> (StatusCode, Json<serde_json::Value>) {
|
async fn health_check(State(state): State<Arc<AppState>>) -> (StatusCode, Json<serde_json::Value>) {
|
||||||
let db_ok = state.conn.get().is_ok();
|
let db_ok = state.conn.get().is_ok();
|
||||||
|
|
@ -341,7 +336,8 @@ async fn run_axum_server(
|
||||||
|
|
||||||
let cors = create_cors_layer();
|
let cors = create_cors_layer();
|
||||||
|
|
||||||
let auth_config = Arc::new(AuthConfig::from_env()
|
let auth_config = Arc::new(
|
||||||
|
AuthConfig::from_env()
|
||||||
.add_anonymous_path("/health")
|
.add_anonymous_path("/health")
|
||||||
.add_anonymous_path("/healthz")
|
.add_anonymous_path("/healthz")
|
||||||
.add_anonymous_path("/api/health")
|
.add_anonymous_path("/api/health")
|
||||||
|
|
@ -356,10 +352,10 @@ async fn run_axum_server(
|
||||||
.add_public_path("/static")
|
.add_public_path("/static")
|
||||||
.add_public_path("/favicon.ico")
|
.add_public_path("/favicon.ico")
|
||||||
.add_public_path("/suite")
|
.add_public_path("/suite")
|
||||||
.add_public_path("/themes"));
|
.add_public_path("/themes"),
|
||||||
|
);
|
||||||
|
|
||||||
let jwt_secret = std::env::var("JWT_SECRET")
|
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
warn!("JWT_SECRET not set, using default development secret - DO NOT USE IN PRODUCTION");
|
warn!("JWT_SECRET not set, using default development secret - DO NOT USE IN PRODUCTION");
|
||||||
"dev-secret-key-change-in-production-minimum-32-chars".to_string()
|
"dev-secret-key-change-in-production-minimum-32-chars".to_string()
|
||||||
});
|
});
|
||||||
|
|
@ -382,8 +378,10 @@ async fn run_axum_server(
|
||||||
|
|
||||||
let default_permissions = build_default_route_permissions();
|
let default_permissions = build_default_route_permissions();
|
||||||
rbac_manager.register_routes(default_permissions).await;
|
rbac_manager.register_routes(default_permissions).await;
|
||||||
info!("RBAC Manager initialized with {} default route permissions",
|
info!(
|
||||||
rbac_manager.config().cache_ttl_seconds);
|
"RBAC Manager initialized with {} default route permissions",
|
||||||
|
rbac_manager.config().cache_ttl_seconds
|
||||||
|
);
|
||||||
|
|
||||||
let auth_provider_registry = {
|
let auth_provider_registry = {
|
||||||
let mut builder = AuthProviderBuilder::new()
|
let mut builder = AuthProviderBuilder::new()
|
||||||
|
|
@ -401,25 +399,32 @@ async fn run_axum_server(
|
||||||
info!("Zitadel environment variables detected - external IdP authentication available");
|
info!("Zitadel environment variables detected - external IdP authentication available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Arc::new(builder.build().await)
|
Arc::new(builder.build().await)
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Auth provider registry initialized with {} providers",
|
info!(
|
||||||
auth_provider_registry.provider_count().await);
|
"Auth provider registry initialized with {} providers",
|
||||||
|
auth_provider_registry.provider_count().await
|
||||||
|
);
|
||||||
|
|
||||||
let auth_middleware_state = AuthMiddlewareState::new(
|
let auth_middleware_state = AuthMiddlewareState::new(
|
||||||
Arc::clone(&auth_config),
|
Arc::clone(&auth_config),
|
||||||
Arc::clone(&auth_provider_registry),
|
Arc::clone(&auth_provider_registry),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
use crate::core::product::{get_product_config_json, PRODUCT_CONFIG};
|
||||||
use crate::core::urls::ApiUrls;
|
use crate::core::urls::ApiUrls;
|
||||||
use crate::core::product::{PRODUCT_CONFIG, get_product_config_json};
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let config = PRODUCT_CONFIG.read().expect("Failed to read product config");
|
let config = PRODUCT_CONFIG
|
||||||
info!("Product: {} | Theme: {} | Apps: {:?}",
|
.read()
|
||||||
config.name, config.theme, config.get_enabled_apps());
|
.expect("Failed to read product config");
|
||||||
|
info!(
|
||||||
|
"Product: {} | Theme: {} | Apps: {:?}",
|
||||||
|
config.name,
|
||||||
|
config.theme,
|
||||||
|
config.get_enabled_apps()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_product_config() -> Json<serde_json::Value> {
|
async fn get_product_config() -> Json<serde_json::Value> {
|
||||||
|
|
@ -467,8 +472,9 @@ async fn run_axum_server(
|
||||||
|
|
||||||
#[cfg(all(feature = "calendar", feature = "scripting"))]
|
#[cfg(all(feature = "calendar", feature = "scripting"))]
|
||||||
{
|
{
|
||||||
let calendar_engine =
|
let calendar_engine = Arc::new(crate::basic::keywords::book::CalendarEngine::new(
|
||||||
Arc::new(crate::basic::keywords::book::CalendarEngine::new(app_state.conn.clone()));
|
app_state.conn.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
api_router = api_router.merge(crate::calendar::caldav::create_caldav_router(
|
api_router = api_router.merge(crate::calendar::caldav::create_caldav_router(
|
||||||
calendar_engine,
|
calendar_engine,
|
||||||
|
|
@ -654,7 +660,8 @@ api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes());
|
||||||
// Create rate limiter integrating with botlib's RateLimiter
|
// Create rate limiter integrating with botlib's RateLimiter
|
||||||
let http_rate_config = HttpRateLimitConfig::api();
|
let http_rate_config = HttpRateLimitConfig::api();
|
||||||
let system_limits = SystemLimits::default();
|
let system_limits = SystemLimits::default();
|
||||||
let (rate_limit_extension, _rate_limiter) = create_rate_limit_layer(http_rate_config, system_limits);
|
let (rate_limit_extension, _rate_limiter) =
|
||||||
|
create_rate_limit_layer(http_rate_config, system_limits);
|
||||||
|
|
||||||
// Create security headers layer
|
// Create security headers layer
|
||||||
let security_headers_config = SecurityHeadersConfig::default();
|
let security_headers_config = SecurityHeadersConfig::default();
|
||||||
|
|
@ -680,11 +687,17 @@ api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes());
|
||||||
if ui_path_exists {
|
if ui_path_exists {
|
||||||
info!("Serving UI from external folder: {}", ui_path);
|
info!("Serving UI from external folder: {}", ui_path);
|
||||||
} else if use_embedded_ui {
|
} else if use_embedded_ui {
|
||||||
info!("External UI folder not found at '{}', using embedded UI", ui_path);
|
info!(
|
||||||
|
"External UI folder not found at '{}', using embedded UI",
|
||||||
|
ui_path
|
||||||
|
);
|
||||||
let file_count = embedded_ui::list_embedded_files().len();
|
let file_count = embedded_ui::list_embedded_files().len();
|
||||||
info!("Embedded UI contains {} files", file_count);
|
info!("Embedded UI contains {} files", file_count);
|
||||||
} else {
|
} else {
|
||||||
warn!("No UI available: folder '{}' not found and no embedded UI", ui_path);
|
warn!(
|
||||||
|
"No UI available: folder '{}' not found and no embedded UI",
|
||||||
|
ui_path
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update app_state with auth components
|
// Update app_state with auth components
|
||||||
|
|
@ -707,8 +720,7 @@ api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes());
|
||||||
.nest_service("/themes", ServeDir::new(format!("{}/../themes", ui_path)))
|
.nest_service("/themes", ServeDir::new(format!("{}/../themes", ui_path)))
|
||||||
.fallback_service(ServeDir::new(&ui_path))
|
.fallback_service(ServeDir::new(&ui_path))
|
||||||
} else if use_embedded_ui {
|
} else if use_embedded_ui {
|
||||||
base_router
|
base_router.merge(embedded_ui::embedded_ui_router())
|
||||||
.merge(embedded_ui::embedded_ui_router())
|
|
||||||
} else {
|
} else {
|
||||||
base_router
|
base_router
|
||||||
};
|
};
|
||||||
|
|
@ -716,7 +728,8 @@ api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes());
|
||||||
// Clone rbac_manager for use in middleware
|
// Clone rbac_manager for use in middleware
|
||||||
let rbac_manager_for_middleware = Arc::clone(&rbac_manager);
|
let rbac_manager_for_middleware = Arc::clone(&rbac_manager);
|
||||||
|
|
||||||
let app = app_with_ui
|
let app =
|
||||||
|
app_with_ui
|
||||||
// Security middleware stack (order matters - last added is outermost/runs first)
|
// Security middleware stack (order matters - last added is outermost/runs first)
|
||||||
.layer(middleware::from_fn(security_headers_middleware))
|
.layer(middleware::from_fn(security_headers_middleware))
|
||||||
.layer(security_headers_extension)
|
.layer(security_headers_extension)
|
||||||
|
|
@ -726,19 +739,21 @@ api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes());
|
||||||
// RBAC middleware - checks permissions AFTER authentication
|
// RBAC middleware - checks permissions AFTER authentication
|
||||||
// NOTE: In Axum, layers run in reverse order (last added = first to run)
|
// NOTE: In Axum, layers run in reverse order (last added = first to run)
|
||||||
// So RBAC is added BEFORE auth, meaning auth runs first, then RBAC
|
// So RBAC is added BEFORE auth, meaning auth runs first, then RBAC
|
||||||
.layer(middleware::from_fn(move |req: axum::http::Request<axum::body::Body>, next: axum::middleware::Next| {
|
.layer(middleware::from_fn(
|
||||||
|
move |req: axum::http::Request<axum::body::Body>, next: axum::middleware::Next| {
|
||||||
let rbac = Arc::clone(&rbac_manager_for_middleware);
|
let rbac = Arc::clone(&rbac_manager_for_middleware);
|
||||||
async move {
|
async move { crate::security::rbac_middleware_fn(req, next, rbac).await }
|
||||||
crate::security::rbac_middleware_fn(req, next, rbac).await
|
},
|
||||||
}
|
))
|
||||||
}))
|
|
||||||
// Authentication middleware - MUST run before RBAC (so added after)
|
// Authentication middleware - MUST run before RBAC (so added after)
|
||||||
.layer(middleware::from_fn(move |req: axum::http::Request<axum::body::Body>, next: axum::middleware::Next| {
|
.layer(middleware::from_fn(
|
||||||
|
move |req: axum::http::Request<axum::body::Body>, next: axum::middleware::Next| {
|
||||||
let state = auth_middleware_state.clone();
|
let state = auth_middleware_state.clone();
|
||||||
async move {
|
async move {
|
||||||
crate::security::auth_middleware_with_providers(req, next, state).await
|
crate::security::auth_middleware_with_providers(req, next, state).await
|
||||||
}
|
}
|
||||||
}))
|
},
|
||||||
|
))
|
||||||
// Panic handler catches panics and returns safe 500 responses
|
// Panic handler catches panics and returns safe 500 responses
|
||||||
.layer(middleware::from_fn(move |req, next| {
|
.layer(middleware::from_fn(move |req, next| {
|
||||||
let config = panic_config.clone();
|
let config = panic_config.clone();
|
||||||
|
|
@ -794,7 +809,10 @@ api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes());
|
||||||
let listener = match tokio::net::TcpListener::bind(addr).await {
|
let listener = match tokio::net::TcpListener::bind(addr).await {
|
||||||
Ok(l) => l,
|
Ok(l) => l,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to bind to {}: {} - is another instance running?", addr, e);
|
error!(
|
||||||
|
"Failed to bind to {}: {} - is another instance running?",
|
||||||
|
addr, e
|
||||||
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -813,8 +831,13 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let no_ui = args.contains(&"--noui".to_string());
|
let no_ui = args.contains(&"--noui".to_string());
|
||||||
|
|
||||||
|
#[cfg(feature = "console")]
|
||||||
let no_console = args.contains(&"--noconsole".to_string());
|
let no_console = args.contains(&"--noconsole".to_string());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "console"))]
|
||||||
|
let no_console = true;
|
||||||
|
|
||||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
|
|
@ -840,8 +863,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
trace!("Bootstrap not complete - skipping early SecretsManager init");
|
trace!("Bootstrap not complete - skipping early SecretsManager init");
|
||||||
}
|
}
|
||||||
|
|
||||||
let noise_filters =
|
let noise_filters = "vaultrs=off,rustify=off,rustify_derive=off,\
|
||||||
"vaultrs=off,rustify=off,rustify_derive=off,\
|
|
||||||
aws_sigv4=off,aws_smithy_checksums=off,aws_runtime=off,aws_smithy_http_client=off,\
|
aws_sigv4=off,aws_smithy_checksums=off,aws_runtime=off,aws_smithy_http_client=off,\
|
||||||
aws_smithy_runtime=off,aws_smithy_runtime_api=off,aws_sdk_s3=off,aws_config=off,\
|
aws_smithy_runtime=off,aws_smithy_runtime_api=off,aws_sdk_s3=off,aws_config=off,\
|
||||||
aws_credential_types=off,aws_http=off,aws_sig_auth=off,aws_types=off,\
|
aws_credential_types=off,aws_http=off,aws_sig_auth=off,aws_types=off,\
|
||||||
|
|
@ -870,9 +892,9 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
std::env::set_var("RUST_LOG", &rust_log);
|
std::env::set_var("RUST_LOG", &rust_log);
|
||||||
|
|
||||||
|
use crate::core::config::ConfigManager;
|
||||||
#[cfg(feature = "llm")]
|
#[cfg(feature = "llm")]
|
||||||
use crate::llm::local::ensure_llama_servers_running;
|
use crate::llm::local::ensure_llama_servers_running;
|
||||||
use crate::core::config::ConfigManager;
|
|
||||||
|
|
||||||
if no_console || no_ui {
|
if no_console || no_ui {
|
||||||
botlib::logging::init_compact_logger_with_style("info");
|
botlib::logging::init_compact_logger_with_style("info");
|
||||||
|
|
@ -889,9 +911,16 @@ use crate::core::config::ConfigManager;
|
||||||
"./locales"
|
"./locales"
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::core::i18n::init_i18n(locales_path) {
|
if let Err(e) = crate::core::i18n::init_i18n(locales_path) {
|
||||||
warn!("Failed to initialize i18n from {}: {}. Translations will show keys.", locales_path, e);
|
warn!(
|
||||||
|
"Failed to initialize i18n from {}: {}. Translations will show keys.",
|
||||||
|
locales_path, e
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
info!("i18n initialized from {} with locales: {:?}", locales_path, crate::core::i18n::available_locales());
|
info!(
|
||||||
|
"i18n initialized from {} with locales: {:?}",
|
||||||
|
locales_path,
|
||||||
|
crate::core::i18n::available_locales()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (progress_tx, _progress_rx) = tokio::sync::mpsc::unbounded_channel::<BootstrapProgress>();
|
let (progress_tx, _progress_rx) = tokio::sync::mpsc::unbounded_channel::<BootstrapProgress>();
|
||||||
|
|
@ -930,7 +959,9 @@ use crate::core::config::ConfigManager;
|
||||||
eprintln!("UI error: {e}");
|
eprintln!("UI error: {e}");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map_err(|e| std::io::Error::other(format!("Failed to spawn UI thread: {}", e)))?,
|
.map_err(|e| {
|
||||||
|
std::io::Error::other(format!("Failed to spawn UI thread: {}", e))
|
||||||
|
})?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "console"))]
|
#[cfg(not(feature = "console"))]
|
||||||
|
|
@ -1169,7 +1200,9 @@ use crate::core::config::ConfigManager;
|
||||||
ensure_vendor_files_in_minio(&drive).await;
|
ensure_vendor_files_in_minio(&drive).await;
|
||||||
|
|
||||||
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
||||||
pool.get().map_err(|e| std::io::Error::other(format!("Failed to get database connection: {}", e)))?,
|
pool.get().map_err(|e| {
|
||||||
|
std::io::Error::other(format!("Failed to get database connection: {}", e))
|
||||||
|
})?,
|
||||||
#[cfg(feature = "cache")]
|
#[cfg(feature = "cache")]
|
||||||
redis_client.clone(),
|
redis_client.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
@ -1180,17 +1213,20 @@ use crate::core::config::ConfigManager;
|
||||||
let config_path = "./config/directory_config.json";
|
let config_path = "./config/directory_config.json";
|
||||||
if let Ok(content) = std::fs::read_to_string(config_path) {
|
if let Ok(content) = std::fs::read_to_string(config_path) {
|
||||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||||
let base_url = json.get("base_url")
|
let base_url = json
|
||||||
|
.get("base_url")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("http://localhost:8300");
|
.unwrap_or("http://localhost:8300");
|
||||||
let client_id = json.get("client_id")
|
let client_id = json.get("client_id").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
.and_then(|v| v.as_str())
|
let client_secret = json
|
||||||
.unwrap_or("");
|
.get("client_secret")
|
||||||
let client_secret = json.get("client_secret")
|
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
info!("Loaded Zitadel config from {}: url={}", config_path, base_url);
|
info!(
|
||||||
|
"Loaded Zitadel config from {}: url={}",
|
||||||
|
config_path, base_url
|
||||||
|
);
|
||||||
|
|
||||||
crate::directory::ZitadelConfig {
|
crate::directory::ZitadelConfig {
|
||||||
issuer_url: base_url.to_string(),
|
issuer_url: base_url.to_string(),
|
||||||
|
|
@ -1231,7 +1267,8 @@ use crate::core::config::ConfigManager;
|
||||||
};
|
};
|
||||||
#[cfg(feature = "directory")]
|
#[cfg(feature = "directory")]
|
||||||
let auth_service = Arc::new(tokio::sync::Mutex::new(
|
let auth_service = Arc::new(tokio::sync::Mutex::new(
|
||||||
crate::directory::AuthService::new(zitadel_config.clone()).map_err(|e| std::io::Error::other(format!("Failed to create auth service: {}", e)))?,
|
crate::directory::AuthService::new(zitadel_config.clone())
|
||||||
|
.map_err(|e| std::io::Error::other(format!("Failed to create auth service: {}", e)))?,
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(feature = "directory")]
|
#[cfg(feature = "directory")]
|
||||||
|
|
@ -1243,18 +1280,28 @@ use crate::core::config::ConfigManager;
|
||||||
let pat_token = pat_token.trim().to_string();
|
let pat_token = pat_token.trim().to_string();
|
||||||
info!("Using admin PAT token for bootstrap authentication");
|
info!("Using admin PAT token for bootstrap authentication");
|
||||||
crate::directory::ZitadelClient::with_pat_token(zitadel_config, pat_token)
|
crate::directory::ZitadelClient::with_pat_token(zitadel_config, pat_token)
|
||||||
.map_err(|e| std::io::Error::other(format!("Failed to create bootstrap client with PAT: {}", e)))?
|
.map_err(|e| {
|
||||||
|
std::io::Error::other(format!(
|
||||||
|
"Failed to create bootstrap client with PAT: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to read admin PAT token: {}, falling back to OAuth2", e);
|
warn!(
|
||||||
crate::directory::ZitadelClient::new(zitadel_config)
|
"Failed to read admin PAT token: {}, falling back to OAuth2",
|
||||||
.map_err(|e| std::io::Error::other(format!("Failed to create bootstrap client: {}", e)))?
|
e
|
||||||
|
);
|
||||||
|
crate::directory::ZitadelClient::new(zitadel_config).map_err(|e| {
|
||||||
|
std::io::Error::other(format!("Failed to create bootstrap client: {}", e))
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Admin PAT not found, using OAuth2 client credentials for bootstrap");
|
info!("Admin PAT not found, using OAuth2 client credentials for bootstrap");
|
||||||
crate::directory::ZitadelClient::new(zitadel_config)
|
crate::directory::ZitadelClient::new(zitadel_config).map_err(|e| {
|
||||||
.map_err(|e| std::io::Error::other(format!("Failed to create bootstrap client: {}", e)))?
|
std::io::Error::other(format!("Failed to create bootstrap client: {}", e))
|
||||||
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
match crate::directory::bootstrap::check_and_bootstrap_admin(&bootstrap_client).await {
|
match crate::directory::bootstrap::check_and_bootstrap_admin(&bootstrap_client).await {
|
||||||
|
|
@ -1271,7 +1318,9 @@ use crate::core::config::ConfigManager;
|
||||||
}
|
}
|
||||||
let config_manager = ConfigManager::new(pool.clone());
|
let config_manager = ConfigManager::new(pool.clone());
|
||||||
|
|
||||||
let mut bot_conn = pool.get().map_err(|e| std::io::Error::other(format!("Failed to get database connection: {}", e)))?;
|
let mut bot_conn = pool
|
||||||
|
.get()
|
||||||
|
.map_err(|e| std::io::Error::other(format!("Failed to get database connection: {}", e)))?;
|
||||||
let (default_bot_id, default_bot_name) = crate::bot::get_default_bot(&mut bot_conn);
|
let (default_bot_id, default_bot_name) = crate::bot::get_default_bot(&mut bot_conn);
|
||||||
info!(
|
info!(
|
||||||
"Using default bot: {} (id: {})",
|
"Using default bot: {} (id: {})",
|
||||||
|
|
@ -1297,7 +1346,11 @@ use crate::core::config::ConfigManager;
|
||||||
#[cfg(feature = "llm")]
|
#[cfg(feature = "llm")]
|
||||||
let base_llm_provider = crate::llm::create_llm_provider_from_url(
|
let base_llm_provider = crate::llm::create_llm_provider_from_url(
|
||||||
&llm_url,
|
&llm_url,
|
||||||
if llm_model.is_empty() { None } else { Some(llm_model.clone()) },
|
if llm_model.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(llm_model.clone())
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "llm")]
|
#[cfg(feature = "llm")]
|
||||||
|
|
@ -1321,8 +1374,7 @@ use crate::core::config::ConfigManager;
|
||||||
let embedding_service = Some(Arc::new(crate::llm::cache::LocalEmbeddingService::new(
|
let embedding_service = Some(Arc::new(crate::llm::cache::LocalEmbeddingService::new(
|
||||||
embedding_url,
|
embedding_url,
|
||||||
embedding_model,
|
embedding_model,
|
||||||
))
|
)) as Arc<dyn crate::llm::cache::EmbeddingService>);
|
||||||
as Arc<dyn crate::llm::cache::EmbeddingService>);
|
|
||||||
|
|
||||||
let cache_config = crate::llm::cache::CacheConfig {
|
let cache_config = crate::llm::cache::CacheConfig {
|
||||||
ttl: 3600,
|
ttl: 3600,
|
||||||
|
|
@ -1354,13 +1406,11 @@ use crate::core::config::ConfigManager;
|
||||||
#[cfg(feature = "tasks")]
|
#[cfg(feature = "tasks")]
|
||||||
let task_scheduler = None;
|
let task_scheduler = None;
|
||||||
|
|
||||||
let (attendant_tx, _attendant_rx) = tokio::sync::broadcast::channel::<
|
let (attendant_tx, _attendant_rx) =
|
||||||
crate::core::shared::state::AttendantNotification,
|
tokio::sync::broadcast::channel::<crate::core::shared::state::AttendantNotification>(1000);
|
||||||
>(1000);
|
|
||||||
|
|
||||||
let (task_progress_tx, _task_progress_rx) = tokio::sync::broadcast::channel::<
|
let (task_progress_tx, _task_progress_rx) =
|
||||||
crate::core::shared::state::TaskProgressEvent,
|
tokio::sync::broadcast::channel::<crate::core::shared::state::TaskProgressEvent>(1000);
|
||||||
>(1000);
|
|
||||||
|
|
||||||
// Initialize BotDatabaseManager for per-bot database support
|
// Initialize BotDatabaseManager for per-bot database support
|
||||||
let database_url = crate::shared::utils::get_database_url_sync().unwrap_or_default();
|
let database_url = crate::shared::utils::get_database_url_sync().unwrap_or_default();
|
||||||
|
|
@ -1431,7 +1481,9 @@ use crate::core::config::ConfigManager;
|
||||||
billing_alert_broadcast: None,
|
billing_alert_broadcast: None,
|
||||||
task_manifests: Arc::new(std::sync::RwLock::new(HashMap::new())),
|
task_manifests: Arc::new(std::sync::RwLock::new(HashMap::new())),
|
||||||
#[cfg(feature = "project")]
|
#[cfg(feature = "project")]
|
||||||
project_service: Arc::new(tokio::sync::RwLock::new(crate::project::ProjectService::new())),
|
project_service: Arc::new(tokio::sync::RwLock::new(
|
||||||
|
crate::project::ProjectService::new(),
|
||||||
|
)),
|
||||||
#[cfg(feature = "compliance")]
|
#[cfg(feature = "compliance")]
|
||||||
legal_service: Arc::new(tokio::sync::RwLock::new(crate::legal::LegalService::new())),
|
legal_service: Arc::new(tokio::sync::RwLock::new(crate::legal::LegalService::new())),
|
||||||
jwt_manager: None,
|
jwt_manager: None,
|
||||||
|
|
@ -1440,7 +1492,9 @@ use crate::core::config::ConfigManager;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resume workflows after server restart
|
// Resume workflows after server restart
|
||||||
if let Err(e) = crate::basic::keywords::orchestration::resume_workflows_on_startup(app_state.clone()).await {
|
if let Err(e) =
|
||||||
|
crate::basic::keywords::orchestration::resume_workflows_on_startup(app_state.clone()).await
|
||||||
|
{
|
||||||
log::warn!("Failed to resume workflows on startup: {}", e);
|
log::warn!("Failed to resume workflows on startup: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1487,11 +1541,8 @@ use crate::core::config::ConfigManager;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
register_thread("drive-monitor", "drive");
|
register_thread("drive-monitor", "drive");
|
||||||
trace!("DriveMonitor::new starting...");
|
trace!("DriveMonitor::new starting...");
|
||||||
let monitor =crate::DriveMonitor::new(
|
let monitor =
|
||||||
drive_monitor_state,
|
crate::DriveMonitor::new(drive_monitor_state, bucket_name.clone(), monitor_bot_id);
|
||||||
bucket_name.clone(),
|
|
||||||
monitor_bot_id,
|
|
||||||
);
|
|
||||||
trace!("DriveMonitor::new done, calling start_monitoring...");
|
trace!("DriveMonitor::new done, calling start_monitoring...");
|
||||||
info!("Starting DriveMonitor for bucket: {}", bucket_name);
|
info!("Starting DriveMonitor for bucket: {}", bucket_name);
|
||||||
if let Err(e) = monitor.start_monitoring().await {
|
if let Err(e) = monitor.start_monitoring().await {
|
||||||
|
|
@ -1507,8 +1558,10 @@ use crate::core::config::ConfigManager;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
register_thread("automation-service", "automation");
|
register_thread("automation-service", "automation");
|
||||||
let automation = AutomationService::new(automation_state);
|
let automation = AutomationService::new(automation_state);
|
||||||
trace!("[TASK] AutomationService starting, RSS={}",
|
trace!(
|
||||||
MemoryStats::format_bytes(MemoryStats::current().rss_bytes));
|
"[TASK] AutomationService starting, RSS={}",
|
||||||
|
MemoryStats::format_bytes(MemoryStats::current().rss_bytes)
|
||||||
|
);
|
||||||
loop {
|
loop {
|
||||||
record_thread_activity("automation-service");
|
record_thread_activity("automation-service");
|
||||||
if let Err(e) = automation.check_scheduled_tasks().await {
|
if let Err(e) = automation.check_scheduled_tasks().await {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue