mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-27 08:00:17 +00:00
* refactor: split large files and consolidate test stubs for contributor velocity - Extract 7 Database sub-traits (ConversationStore, JobStore, SandboxStore, RoutineStore, ToolFailureStore, SettingsStore, WorkspaceStore) with Database as a supertrait combining them all - Split libsql_backend.rs (2769 lines) into src/db/libsql/ directory with one file per sub-trait implementation - Split config.rs (1753 lines) into src/config/ directory with 16 domain files - Consolidate 3 duplicate test LLM stubs into shared StubLlm in src/testing.rs - Split server.rs handlers into src/channels/web/handlers/ directory - Extract main.rs init phases into AppBuilder (src/app.rs) - Add developer setup script (scripts/dev-setup.sh) Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: move heartbeat test from examples/ to tests/ Convert standalone example binary into a proper #[ignore] integration test, matching the convention of the other integration tests. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix rustfmt formatting for CI Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review comments from Copilot - tunnel.rs: replace .ok().flatten() with ? to propagate env var errors - secrets.rs: remove misleading "process-wide cache" comment - database.rs: use uppercase "DATABASE_URL" in error key - testing.rs: gate harness tests with #[cfg(feature = "libsql")] Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]>
71 lines
2.3 KiB
Rust
71 lines
2.3 KiB
Rust
use secrecy::{ExposeSecret, SecretString};
|
|
|
|
use crate::config::helpers::optional_env;
|
|
use crate::error::ConfigError;
|
|
|
|
/// Secrets management configuration.
|
|
#[derive(Clone, Default)]
|
|
pub struct SecretsConfig {
|
|
/// Master key for encrypting secrets.
|
|
pub master_key: Option<SecretString>,
|
|
/// Whether secrets management is enabled.
|
|
pub enabled: bool,
|
|
/// Source of the master key.
|
|
pub source: crate::settings::KeySource,
|
|
}
|
|
|
|
impl std::fmt::Debug for SecretsConfig {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("SecretsConfig")
|
|
.field("master_key", &self.master_key.is_some())
|
|
.field("enabled", &self.enabled)
|
|
.field("source", &self.source)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl SecretsConfig {
|
|
/// Auto-detect secrets master key from env var, then OS keychain.
|
|
///
|
|
/// Sequential probe: SECRETS_MASTER_KEY env var first, then OS keychain.
|
|
/// No saved "source" needed; just try each source in order.
|
|
pub(crate) async fn resolve() -> Result<Self, ConfigError> {
|
|
use crate::settings::KeySource;
|
|
|
|
let (master_key, source) = if let Some(env_key) = optional_env("SECRETS_MASTER_KEY")? {
|
|
(Some(SecretString::from(env_key)), KeySource::Env)
|
|
} else {
|
|
// Probe the OS keychain; if a key is stored, use it
|
|
match crate::secrets::keychain::get_master_key().await {
|
|
Ok(key_bytes) => {
|
|
let key_hex: String = key_bytes.iter().map(|b| format!("{:02x}", b)).collect();
|
|
(Some(SecretString::from(key_hex)), KeySource::Keychain)
|
|
}
|
|
Err(_) => (None, KeySource::None),
|
|
}
|
|
};
|
|
|
|
let enabled = master_key.is_some();
|
|
|
|
if let Some(ref key) = master_key
|
|
&& key.expose_secret().len() < 32
|
|
{
|
|
return Err(ConfigError::InvalidValue {
|
|
key: "SECRETS_MASTER_KEY".to_string(),
|
|
message: "must be at least 32 bytes for AES-256-GCM".to_string(),
|
|
});
|
|
}
|
|
|
|
Ok(Self {
|
|
master_key,
|
|
enabled,
|
|
source,
|
|
})
|
|
}
|
|
|
|
/// Get the master key if configured.
|
|
pub fn master_key(&self) -> Option<&SecretString> {
|
|
self.master_key.as_ref()
|
|
}
|
|
}
|