Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions crates/sentinel-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct DatabaseConfig {
pub database_url: Option<String>,
pub enable_local_db: bool,
#[serde(default = "default_max_connections")]
pub max_connections: u32,
#[serde(default = "default_idle_timeout")]
pub idle_timeout_secs: u64,
}

fn default_max_connections() -> u32 { 10 }
fn default_idle_timeout() -> u64 { 300 }

#[derive(Debug, Deserialize, Serialize, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct OrchestratorConfig {
Expand Down Expand Up @@ -181,8 +194,7 @@ pub struct AgentConfig {
pub local_tsdb_max_disk_mb: u64,

// Persistence Layer
pub database_url: Option<String>,
pub enable_local_db: bool,
pub database: DatabaseConfig,

// Control Plane
pub orchestrator: Option<OrchestratorConfig>,
Expand Down Expand Up @@ -297,8 +309,7 @@ impl Default for AgentConfig {
local_tsdb_retention_hours: 24,
local_tsdb_max_disk_mb: 512,

database_url: None,
enable_local_db: false,
database: DatabaseConfig::default(),

orchestrator: None,

Expand Down Expand Up @@ -331,12 +342,12 @@ impl AgentConfig {
match self.mode {
RunMode::Dev => {
if overrides.log_level.is_none() { self.log_level = LogLevel::Debug; }
if overrides.enable_local_db.is_none() { self.enable_local_db = true; }
if overrides.enable_local_db.is_none() { self.database.enable_local_db = true; }
},
RunMode::Demo => {
// In Demo mode, we simulate GPU data if not explicitly enabled
if overrides.enable_gpu.is_none() { self.enable_gpu = false; }
if overrides.enable_local_db.is_none() { self.enable_local_db = true; }
if overrides.enable_local_db.is_none() { self.database.enable_local_db = true; }
},
RunMode::Prod => {
if overrides.log_level.is_none() { self.log_level = LogLevel::Info; }
Expand Down Expand Up @@ -367,8 +378,8 @@ impl AgentConfig {
if let Some(v) = overrides.local_tsdb_path { self.local_tsdb_path = v; }
if let Some(v) = overrides.local_tsdb_retention_hours { self.local_tsdb_retention_hours = v; }
if let Some(v) = overrides.local_tsdb_max_disk_mb { self.local_tsdb_max_disk_mb = v; }
if let Some(v) = overrides.database_url { self.database_url = Some(v); }
if let Some(v) = overrides.enable_local_db { self.enable_local_db = v; }
if let Some(v) = overrides.database_url { self.database.database_url = Some(v); }
if let Some(v) = overrides.enable_local_db { self.database.enable_local_db = v; }
if let Some(v) = overrides.log_level { self.log_level = v; }
if let Some(v) = overrides.orchestrator { self.orchestrator = Some(v); }
if let Some(v) = overrides.efficiency_profile_path { self.efficiency_profile_path = Some(v); }
Expand Down
40 changes: 40 additions & 0 deletions crates/sentinel-core/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct HttpState {
pub orchestrator_token: Option<String>,
pub security: crate::config::SecurityConfig,
pub storage: std::sync::Arc<crate::storage::Storage>,
pub database_config: Arc<parking_lot::RwLock<crate::config::DatabaseConfig>>,
}

pub fn build_router(state: HttpState) -> Router {
Expand All @@ -49,6 +50,10 @@ pub fn build_router(state: HttpState) -> Router {
let console_dir = std::env::var("SENTINEL_CONSOLE_DIR").unwrap_or_else(|_| "./public/console".to_string());
router = router.nest_service("/", tower_http::services::ServeDir::new(console_dir));

// Database Configuration Endpoints
router = router
.route("/api/config/database", get(get_database_config_handler).post(update_database_config_handler));

if let Some(orch_state) = &state.orchestrator {
if state.orchestrator_allow_public || state.listen_is_loopback {
router = router.nest_service(
Expand Down Expand Up @@ -238,3 +243,38 @@ async fn tsdb_export_handler(
}
}
}

async fn get_database_config_handler(
State(state): State<HttpState>,
user: crate::auth::AuthenticatedUser,
) -> impl IntoResponse {
if let Err(e) = crate::auth::require_permission(&user, crate::auth::Permission::ManageOrchestrator) {
return e;
}
let config = state.database_config.read();
Json((*config).clone()).into_response()
}

async fn update_database_config_handler(
State(state): State<HttpState>,
user: crate::auth::AuthenticatedUser,
Json(new_config): Json<crate::config::DatabaseConfig>,
) -> impl IntoResponse {
if let Err(e) = crate::auth::require_permission(&user, crate::auth::Permission::ManageOrchestrator) {
return e;
}

{
let mut config = state.database_config.write();
*config = new_config;
}

info!(
target: "audit",
action = "database_config_updated",
user = ?user.user_id,
role = ?user.role
);

(StatusCode::OK, "Database configuration updated (Effect on restart for connection parameters)").into_response()
}
1 change: 1 addition & 0 deletions crates/sentinel-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ impl Agent {
orchestrator_token: config.orchestrator.as_ref().and_then(|o| o.token.clone()),
security: config.security.clone(),
storage: storage.clone(),
database_config: Arc::new(parking_lot::RwLock::new(config.database.clone())),
};
let router = build_router(http_state);
let http_task = serve(&config.listen_address, router)
Expand Down
6 changes: 3 additions & 3 deletions crates/sentinel-core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ pub struct Storage {
impl Storage {
pub async fn new(config: &AgentConfig) -> Result<Self> {
// 1. Try PostgreSQL / TimescaleDB if URL provided
if let Some(url) = &config.database_url {
if let Some(url) = &config.database.database_url {
tracing::info!("Storage: Connecting to PostgreSQL/Timescale at configured URL...");
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(20)
.max_connections(config.database.max_connections)
.connect(url).await
.context("Failed to connect to PostgreSQL")?;

Expand Down Expand Up @@ -51,7 +51,7 @@ impl Storage {
}

// 2. Default: SQLite (Embedded)
if config.enable_local_db {
if config.database.enable_local_db {
let path = format!("sqlite://{}/sentinel.db?mode=rwc", config.local_tsdb_path);
tracing::info!("Storage: Initializing embedded SQLite DB at {}", path);

Expand Down
Loading
Loading