mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 15:40:18 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a8d4e0104 | ||
|
|
e1ffd30d37 |
+6
-2
@@ -152,8 +152,12 @@ This is OS-level behavior we cannot prevent. To minimize pain:
|
|||||||
rather than triggering system dialogs.
|
rather than triggering system dialogs.
|
||||||
|
|
||||||
**Invariant:** After Step 2, `self.secrets_crypto` is `Some` if the user
|
**Invariant:** After Step 2, `self.secrets_crypto` is `Some` if the user
|
||||||
chose Keychain or generated a new key. It may be `None` if the user chose
|
chose Keychain or env-var mode (both generate a key and initialize crypto
|
||||||
env-var mode or skipped secrets.
|
immediately). It is `None` only if the user skipped secrets.
|
||||||
|
|
||||||
|
When env-var mode is chosen, the generated key is also stored in
|
||||||
|
`self.secrets_master_key_hex` so that `write_bootstrap_env()` can persist
|
||||||
|
it to `~/.ironclaw/.env` automatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+59
-6
@@ -90,6 +90,8 @@ pub struct SetupWizard {
|
|||||||
db_backend: Option<crate::db::libsql::LibSqlBackend>,
|
db_backend: Option<crate::db::libsql::LibSqlBackend>,
|
||||||
/// Secrets crypto (created during setup).
|
/// Secrets crypto (created during setup).
|
||||||
secrets_crypto: Option<Arc<SecretsCrypto>>,
|
secrets_crypto: Option<Arc<SecretsCrypto>>,
|
||||||
|
/// Generated master key hex (stored for writing to .env in env-var mode).
|
||||||
|
secrets_master_key_hex: Option<String>,
|
||||||
/// Cached API key from provider setup (used by model fetcher without env mutation).
|
/// Cached API key from provider setup (used by model fetcher without env mutation).
|
||||||
llm_api_key: Option<SecretString>,
|
llm_api_key: Option<SecretString>,
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,7 @@ impl SetupWizard {
|
|||||||
#[cfg(feature = "libsql")]
|
#[cfg(feature = "libsql")]
|
||||||
db_backend: None,
|
db_backend: None,
|
||||||
secrets_crypto: None,
|
secrets_crypto: None,
|
||||||
|
secrets_master_key_hex: None,
|
||||||
llm_api_key: None,
|
llm_api_key: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +124,7 @@ impl SetupWizard {
|
|||||||
#[cfg(feature = "libsql")]
|
#[cfg(feature = "libsql")]
|
||||||
db_backend: None,
|
db_backend: None,
|
||||||
secrets_crypto: None,
|
secrets_crypto: None,
|
||||||
|
secrets_master_key_hex: None,
|
||||||
llm_api_key: None,
|
llm_api_key: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -768,16 +772,25 @@ impl SetupWizard {
|
|||||||
print_success("Master key generated and stored in OS keychain");
|
print_success("Master key generated and stored in OS keychain");
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
// Env var mode
|
// Env var mode: generate key, initialize crypto, and persist to .env
|
||||||
print_info("Generate a key and add it to your environment:");
|
print_info("Generating master key...");
|
||||||
let key_hex = crate::secrets::keychain::generate_master_key_hex();
|
let key_hex = crate::secrets::keychain::generate_master_key_hex();
|
||||||
|
|
||||||
|
// Initialize crypto so subsequent steps (API key storage) work
|
||||||
|
self.secrets_crypto = Some(Arc::new(
|
||||||
|
SecretsCrypto::new(SecretString::from(key_hex.clone()))
|
||||||
|
.map_err(|e| SetupError::Config(e.to_string()))?,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Store for write_bootstrap_env to persist to ~/.ironclaw/.env
|
||||||
|
self.secrets_master_key_hex = Some(key_hex.clone());
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
println!(" export SECRETS_MASTER_KEY={}", key_hex);
|
print_info(&format!("Generated master key: {}", mask_api_key(&key_hex)));
|
||||||
println!();
|
print_info("This key will be saved to ~/.ironclaw/.env automatically.");
|
||||||
print_info("Add this to your shell profile or .env file.");
|
|
||||||
|
|
||||||
self.settings.secrets_master_key_source = KeySource::Env;
|
self.settings.secrets_master_key_source = KeySource::Env;
|
||||||
print_success("Configured for environment variable");
|
print_success("Master key generated and configured for environment variable");
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.settings.secrets_master_key_source = KeySource::None;
|
self.settings.secrets_master_key_source = KeySource::None;
|
||||||
@@ -2133,6 +2146,13 @@ impl SetupWizard {
|
|||||||
env_vars.push((base_url_env.clone(), base_url.clone()));
|
env_vars.push((base_url_env.clone(), base_url.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist SECRETS_MASTER_KEY when env-var mode was chosen in step 2
|
||||||
|
if self.settings.secrets_master_key_source == KeySource::Env
|
||||||
|
&& let Some(ref key_hex) = self.secrets_master_key_hex
|
||||||
|
{
|
||||||
|
env_vars.push(("SECRETS_MASTER_KEY".to_string(), key_hex.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
// Preserve NEARAI_API_KEY if present (set by API key auth flow)
|
// Preserve NEARAI_API_KEY if present (set by API key auth flow)
|
||||||
if let Ok(api_key) = std::env::var("NEARAI_API_KEY")
|
if let Ok(api_key) = std::env::var("NEARAI_API_KEY")
|
||||||
&& !api_key.is_empty()
|
&& !api_key.is_empty()
|
||||||
@@ -3293,6 +3313,39 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test for #666: env var mode in step_security must initialize
|
||||||
|
/// secrets_crypto (for immediate API key storage) and secrets_master_key_hex
|
||||||
|
/// (for persisting to ~/.ironclaw/.env via write_bootstrap_env).
|
||||||
|
#[test]
|
||||||
|
fn test_env_var_mode_initializes_crypto_and_stores_key() {
|
||||||
|
let mut wizard = SetupWizard::new();
|
||||||
|
assert!(wizard.secrets_crypto.is_none());
|
||||||
|
assert!(wizard.secrets_master_key_hex.is_none());
|
||||||
|
|
||||||
|
// Simulate the env-var branch of step_security
|
||||||
|
let key_hex = crate::secrets::keychain::generate_master_key_hex();
|
||||||
|
|
||||||
|
// Verify it's a valid 64-char hex string (32 bytes = AES-256)
|
||||||
|
assert_eq!(key_hex.len(), 64);
|
||||||
|
assert!(key_hex.chars().all(|c| c.is_ascii_hexdigit()));
|
||||||
|
|
||||||
|
let crypto = SecretsCrypto::new(SecretString::from(key_hex.clone()))
|
||||||
|
.expect("SecretsCrypto::new should succeed with generated hex key");
|
||||||
|
|
||||||
|
wizard.secrets_crypto = Some(Arc::new(crypto));
|
||||||
|
wizard.secrets_master_key_hex = Some(key_hex.clone());
|
||||||
|
wizard.settings.secrets_master_key_source = KeySource::Env;
|
||||||
|
|
||||||
|
// Verify crypto is usable for immediate secret encryption
|
||||||
|
assert!(wizard.secrets_crypto.is_some());
|
||||||
|
|
||||||
|
// Verify the hex key is stored for write_bootstrap_env to persist
|
||||||
|
assert_eq!(
|
||||||
|
wizard.secrets_master_key_hex.as_deref(),
|
||||||
|
Some(key_hex.as_str())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_run_provider_setup_no_setup_hint() {
|
async fn test_run_provider_setup_no_setup_hint() {
|
||||||
// A provider with setup: None should not error. It should set the
|
// A provider with setup: None should not error. It should set the
|
||||||
|
|||||||
Reference in New Issue
Block a user