feat(directory): read admin credentials from ~/.gb-setup-credentials
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:
Rodrigo Rodriguez (Pragmatismo) 2026-03-01 10:06:30 -03:00
parent eb5c12c466
commit 2a6c599c75

View file

@ -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(&params)
.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
}
} }