mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-31 16:49:34 +00:00
Move setup wizard credentials to database storage
Changes setup wizard to save channel secrets (Telegram bot token, HTTP webhook secret) to PostgreSQL via SecretsStore instead of files. This enables the WASM channel credential injector to find and inject the secrets properly, since it reads from the database. Requires: - DATABASE_URL to be set - SECRETS_MASTER_KEY (will generate and display if not set) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
c7f0e8014d
commit
4ab20ff939
+105
-6
@@ -7,9 +7,14 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use deadpool_postgres::{Config as PoolConfig, Runtime};
|
||||
use secrecy::SecretString;
|
||||
use tokio_postgres::NoTls;
|
||||
|
||||
use crate::llm::{SessionConfig, SessionManager};
|
||||
use crate::secrets::SecretsCrypto;
|
||||
use crate::settings::Settings;
|
||||
use crate::setup::channels::{setup_http, setup_telegram};
|
||||
use crate::setup::channels::{SecretsContext, setup_http, setup_telegram};
|
||||
use crate::setup::prompts::{
|
||||
input, print_header, print_info, print_step, print_success, select_many, select_one,
|
||||
};
|
||||
@@ -23,6 +28,15 @@ pub enum SetupError {
|
||||
#[error("Authentication error: {0}")]
|
||||
Auth(String),
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
Database(String),
|
||||
|
||||
#[error("Configuration error: {0}")]
|
||||
Config(String),
|
||||
|
||||
#[error("Channel setup error: {0}")]
|
||||
Channel(String),
|
||||
|
||||
#[error("User cancelled")]
|
||||
Cancelled,
|
||||
}
|
||||
@@ -242,6 +256,64 @@ impl SetupWizard {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize secrets context for channel setup.
|
||||
async fn init_secrets_context(&self) -> Result<SecretsContext, SetupError> {
|
||||
// Get DATABASE_URL
|
||||
let database_url = std::env::var("DATABASE_URL").map_err(|_| {
|
||||
SetupError::Config(
|
||||
"DATABASE_URL not set. Please set it in .env or environment.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Get or generate SECRETS_MASTER_KEY
|
||||
let master_key = match std::env::var("SECRETS_MASTER_KEY") {
|
||||
Ok(key) => {
|
||||
if key.len() < 32 {
|
||||
return Err(SetupError::Config(
|
||||
"SECRETS_MASTER_KEY must be at least 32 characters".to_string(),
|
||||
));
|
||||
}
|
||||
key
|
||||
}
|
||||
Err(_) => {
|
||||
// Generate a new master key
|
||||
print_info("SECRETS_MASTER_KEY not set. Generating a new one...");
|
||||
let key = generate_master_key();
|
||||
print_info(&format!(
|
||||
"Generated master key. Add to your .env file:\nSECRETS_MASTER_KEY={}",
|
||||
key
|
||||
));
|
||||
key
|
||||
}
|
||||
};
|
||||
|
||||
// Create database pool
|
||||
let mut cfg = PoolConfig::new();
|
||||
cfg.url = Some(database_url);
|
||||
cfg.pool = Some(deadpool_postgres::PoolConfig {
|
||||
max_size: 5,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let pool = cfg
|
||||
.create_pool(Some(Runtime::Tokio1), NoTls)
|
||||
.map_err(|e| SetupError::Database(format!("Failed to create pool: {}", e)))?;
|
||||
|
||||
// Test connection
|
||||
let _ = pool
|
||||
.get()
|
||||
.await
|
||||
.map_err(|e| SetupError::Database(format!("Failed to connect to database: {}", e)))?;
|
||||
|
||||
print_success("Connected to database");
|
||||
|
||||
// Create crypto
|
||||
let crypto = SecretsCrypto::new(SecretString::from(master_key))
|
||||
.map_err(|e| SetupError::Config(format!("Invalid master key: {}", e)))?;
|
||||
|
||||
Ok(SecretsContext::new(pool, Arc::new(crypto), "default"))
|
||||
}
|
||||
|
||||
/// Step 3: Channel configuration.
|
||||
async fn step_channels(&mut self) -> Result<(), SetupError> {
|
||||
let options = [
|
||||
@@ -252,12 +324,22 @@ impl SetupWizard {
|
||||
|
||||
let selected = select_many("Which channels do you want to enable?", &options)?;
|
||||
|
||||
// Only initialize secrets context if we need it (HTTP or Telegram selected)
|
||||
let needs_secrets = selected.contains(&1) || selected.contains(&2);
|
||||
let secrets = if needs_secrets {
|
||||
Some(self.init_secrets_context().await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// HTTP is index 1
|
||||
if selected.contains(&1) {
|
||||
println!();
|
||||
let result = setup_http()?;
|
||||
self.settings.channels.http_enabled = result.enabled;
|
||||
self.settings.channels.http_port = Some(result.port);
|
||||
if let Some(ref ctx) = secrets {
|
||||
let result = setup_http(ctx).await.map_err(SetupError::Channel)?;
|
||||
self.settings.channels.http_enabled = result.enabled;
|
||||
self.settings.channels.http_port = Some(result.port);
|
||||
}
|
||||
} else {
|
||||
self.settings.channels.http_enabled = false;
|
||||
}
|
||||
@@ -265,8 +347,10 @@ impl SetupWizard {
|
||||
// Telegram is index 2
|
||||
if selected.contains(&2) {
|
||||
println!();
|
||||
let result = setup_telegram().await?;
|
||||
self.settings.channels.telegram_enabled = result.enabled;
|
||||
if let Some(ref ctx) = secrets {
|
||||
let result = setup_telegram(ctx).await.map_err(SetupError::Channel)?;
|
||||
self.settings.channels.telegram_enabled = result.enabled;
|
||||
}
|
||||
} else {
|
||||
self.settings.channels.telegram_enabled = false;
|
||||
}
|
||||
@@ -318,6 +402,15 @@ impl SetupWizard {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random 32-byte master key as hex string.
|
||||
fn generate_master_key() -> String {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
bytes.iter().map(|b| format!("{:02x}", b)).collect()
|
||||
}
|
||||
|
||||
impl Default for SetupWizard {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -344,4 +437,10 @@ mod tests {
|
||||
let wizard = SetupWizard::with_config(config);
|
||||
assert!(wizard.config.skip_auth);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_master_key() {
|
||||
let key = generate_master_key();
|
||||
assert_eq!(key.len(), 64); // 32 bytes = 64 hex chars
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user