feat(directory): read admin credentials from ~/.gb-setup-credentials
Some checks failed
BotServer CI / build (push) Failing after 5m3s
Some checks failed
BotServer CI / build (push) Failing after 5m3s
- Updated setup_directory() to read credentials from saved file - Added read_saved_credentials() to parse ~/.gb-setup-credentials - Added get_admin_credentials() to try multiple sources - Removed default credentials approach (doesn't work) - Improved error messages with solution steps This matches the working approach from commit 86cfccc2 where credentials were saved during first bootstrap and reused for OAuth client creation on subsequent runs.
This commit is contained in:
parent
eb5c12c466
commit
2a6c599c75
1 changed files with 84 additions and 82 deletions
|
|
@ -135,6 +135,13 @@ fn extract_initial_admin_from_log(log_path: &std::path::Path) -> Option<(String,
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Admin credentials structure
|
||||||
|
#[cfg(feature = "directory")]
|
||||||
|
struct AdminCredentials {
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize Directory (Zitadel) with default admin user and OAuth application
|
/// Initialize Directory (Zitadel) with default admin user and OAuth application
|
||||||
/// This should be called after Zitadel has started and is responding
|
/// This should be called after Zitadel has started and is responding
|
||||||
#[cfg(feature = "directory")]
|
#[cfg(feature = "directory")]
|
||||||
|
|
@ -159,96 +166,91 @@ pub async fn setup_directory() -> anyhow::Result<crate::core::package_manager::s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try multiple approaches to get initial admin credentials
|
// Try to get credentials from multiple sources
|
||||||
let log_path = PathBuf::from(&stack_path).join("logs/zitadel.log");
|
let credentials = get_admin_credentials(&stack_path).await?;
|
||||||
|
|
||||||
// Approach 1: Try to extract credentials from Zitadel log
|
let mut directory_setup = crate::core::package_manager::setup::DirectorySetup::with_admin_credentials(
|
||||||
let admin_credentials = extract_initial_admin_from_log(&log_path);
|
base_url,
|
||||||
|
config_path.clone(),
|
||||||
// Approach 2: Try well-known default credentials for initial Zitadel setup
|
credentials.email,
|
||||||
// Zitadel's default initial admin credentials (if any)
|
credentials.password,
|
||||||
let default_credentials = [
|
);
|
||||||
// Try common default patterns
|
|
||||||
("admin@localhost", "Password1!"),
|
|
||||||
("zitadel-admin@localhost", "Password1!"),
|
|
||||||
("admin", "admin"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Find working credentials
|
|
||||||
let working_credentials = if let Some((email, password)) = admin_credentials {
|
|
||||||
log::info!("Using credentials extracted from Zitadel log");
|
|
||||||
Some((email, password))
|
|
||||||
} else {
|
|
||||||
// Try default credentials
|
|
||||||
log::info!("Attempting to authenticate with default Zitadel credentials...");
|
|
||||||
let mut found = None;
|
|
||||||
for (email, password) in default_credentials.iter() {
|
|
||||||
if let Ok(true) = test_zitadel_credentials(&base_url, email, password).await {
|
|
||||||
log::info!("Successfully authenticated with default credentials: {}", email);
|
|
||||||
found = Some((email.to_string(), password.to_string()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
found
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut directory_setup = if let Some((email, password)) = working_credentials {
|
|
||||||
log::info!("Using admin credentials for Directory setup: {}", email);
|
|
||||||
crate::core::package_manager::setup::DirectorySetup::with_admin_credentials(
|
|
||||||
base_url,
|
|
||||||
config_path.clone(),
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// No credentials found - provide helpful error message
|
|
||||||
log::error!("═══════════════════════════════════════════════════════════════");
|
|
||||||
log::error!("❌ FAILED TO GET ZITADEL ADMIN CREDENTIALS");
|
|
||||||
log::error!("═══════════════════════════════════════════════════════════════");
|
|
||||||
log::error!("Could not extract credentials from Zitadel logs at:",);
|
|
||||||
log::error!(" {}", log_path.display());
|
|
||||||
log::error!("");
|
|
||||||
log::error!("Please check the Zitadel logs manually for initial admin credentials:");
|
|
||||||
log::error!(" tail -100 {}", log_path.display());
|
|
||||||
log::error!("");
|
|
||||||
log::error!("Then create the config file manually at:");
|
|
||||||
log::error!(" {}", config_path.display());
|
|
||||||
log::error!("═══════════════════════════════════════════════════════════════");
|
|
||||||
|
|
||||||
anyhow::bail!(
|
|
||||||
"Failed to obtain Zitadel admin credentials. Check logs at {}",
|
|
||||||
log_path.display()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
directory_setup.initialize().await
|
directory_setup.initialize().await
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to initialize directory: {}", e))
|
.map_err(|e| anyhow::anyhow!("Failed to initialize directory: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if Zitadel credentials are valid by attempting to get an access token
|
/// Get admin credentials from multiple sources
|
||||||
#[cfg(feature = "directory")]
|
#[cfg(feature = "directory")]
|
||||||
async fn test_zitadel_credentials(base_url: &str, username: &str, password: &str) -> anyhow::Result<bool> {
|
async fn get_admin_credentials(stack_path: &str) -> anyhow::Result<AdminCredentials> {
|
||||||
use reqwest::Client;
|
// Approach 1: Read from ~/.gb-setup-credentials (most reliable - from first bootstrap)
|
||||||
|
if let Some(creds) = read_saved_credentials() {
|
||||||
|
log::info!("Using credentials from ~/.gb-setup-credentials");
|
||||||
|
return Ok(creds);
|
||||||
|
}
|
||||||
|
|
||||||
let client = Client::builder()
|
// Approach 2: Try to extract from Zitadel logs (fallback)
|
||||||
.timeout(std::time::Duration::from_secs(5))
|
let log_path = std::path::PathBuf::from(stack_path).join("logs/directory/zitadel.log");
|
||||||
.build()
|
if let Some((email, password)) = extract_initial_admin_from_log(&log_path) {
|
||||||
.unwrap_or_else(|_| Client::new());
|
log::info!("Using credentials extracted from Zitadel log");
|
||||||
|
return Ok(AdminCredentials { email, password });
|
||||||
|
}
|
||||||
|
|
||||||
let token_url = format!("{}/oauth/v2/token", base_url);
|
// Approach 3: Error with helpful message
|
||||||
let params = [
|
log::error!("═══════════════════════════════════════════════════════════════");
|
||||||
("grant_type", "password".to_string()),
|
log::error!("❌ FAILED TO GET ZITADEL ADMIN CREDENTIALS");
|
||||||
("username", username.to_string()),
|
log::error!("═══════════════════════════════════════════════════════════════");
|
||||||
("password", password.to_string()),
|
log::error!("Could not find credentials in:");
|
||||||
("scope", "openid profile email".to_string()),
|
log::error!(" - ~/.gb-setup-credentials");
|
||||||
];
|
log::error!(" - {}/logs/directory/zitadel.log", stack_path);
|
||||||
|
log::error!("");
|
||||||
|
log::error!("SOLUTION: Run a fresh bootstrap to create initial admin user:");
|
||||||
|
log::error!(" 1. Delete .env and botserver-stack/conf/system/.bootstrap_completed");
|
||||||
|
log::error!(" 2. Run: ./reset.sh");
|
||||||
|
log::error!(" 3. Admin credentials will be displayed and saved");
|
||||||
|
log::error!("═══════════════════════════════════════════════════════════════");
|
||||||
|
|
||||||
let response = client
|
anyhow::bail!("No admin credentials found. Run fresh bootstrap to create them.")
|
||||||
.post(&token_url)
|
}
|
||||||
.form(¶ms)
|
|
||||||
.send()
|
/// Read credentials from ~/.gb-setup-credentials file
|
||||||
.await
|
#[cfg(feature = "directory")]
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to test credentials: {}", e))?;
|
fn read_saved_credentials() -> Option<AdminCredentials> {
|
||||||
|
let home = std::env::var("HOME").ok()?;
|
||||||
Ok(response.status().is_success())
|
let creds_path = std::path::PathBuf::from(&home).join(".gb-setup-credentials");
|
||||||
|
|
||||||
|
if !creds_path.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = std::fs::read_to_string(&creds_path).ok()?;
|
||||||
|
|
||||||
|
// Parse credentials from file
|
||||||
|
let mut username = None;
|
||||||
|
let mut password = None;
|
||||||
|
let mut email = None;
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
if line.contains("Username:") {
|
||||||
|
username = line.split("Username:")
|
||||||
|
.nth(1)
|
||||||
|
.map(|s| s.trim().to_string());
|
||||||
|
}
|
||||||
|
if line.contains("Password:") {
|
||||||
|
password = line.split("Password:")
|
||||||
|
.nth(1)
|
||||||
|
.map(|s| s.trim().to_string());
|
||||||
|
}
|
||||||
|
if line.contains("Email:") {
|
||||||
|
email = line.split("Email:")
|
||||||
|
.nth(1)
|
||||||
|
.map(|s| s.trim().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(_username), Some(password), Some(email)) = (username, password, email) {
|
||||||
|
Some(AdminCredentials { email, password })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue