fix: report all missing setup requirements

This commit is contained in:
anonsaber 2026-09-25 09:42:15 +08:00
parent e5ec3aef4c
commit 8a24ec7e9e
2 changed files with 36 additions and 11 deletions

View File

@ -32,27 +32,30 @@ async fn main() -> Result<(), error::BotError> {
.parse() .parse()
.map_err(|e| error::BotError::Config(format!("invalid PORT: {e}")))?; .map_err(|e| error::BotError::Config(format!("invalid PORT: {e}")))?;
let listener = tokio::net::TcpListener::bind(addr).await?; let listener = tokio::net::TcpListener::bind(addr).await?;
let mut missing = Vec::new();
let redis_value = match std::env::var("REDIS_URL") { let redis_value = match std::env::var("REDIS_URL") {
Ok(value) if !value.trim().is_empty() => value, Ok(value) if !value.trim().is_empty() => value,
_ => { _ => {
tracing::warn!("REDIS_URL is not configured; serving configuration setup mode"); missing.push("REDIS_URL".to_owned());
let app = router_configuration_setup(vec!["REDIS_URL".into()]).merge(web::router()); String::new()
axum::serve(listener, app).await?;
return Ok(());
} }
}; };
let encryption_key = match encryption_key_from_env() { let encryption_key = match encryption_key_from_env() {
Ok(key) => key, Ok(key) => key,
Err(_) => { Err(_) => {
missing.push("CONFIG_ENCRYPTION_KEY".to_owned());
[0_u8; 32]
}
};
if !missing.is_empty() {
tracing::warn!( tracing::warn!(
"CONFIG_ENCRYPTION_KEY is not configured or invalid; serving setup mode" ?missing,
"required bootstrap environment is incomplete; serving setup mode"
); );
let app = router_configuration_setup(vec!["CONFIG_ENCRYPTION_KEY".into()]) let app = router_configuration_setup(missing).merge(web::router());
.merge(web::router());
axum::serve(listener, app).await?; axum::serve(listener, app).await?;
return Ok(()); return Ok(());
} }
};
// The Redis URL is the only bootstrap credential in the migration path. Until // The Redis URL is the only bootstrap credential in the migration path. Until
// the bootstrap route is wired, retain the legacy parser solely to obtain it. // the bootstrap route is wired, retain the legacy parser solely to obtain it.
let bootstrap_config = match Config::from_env() { let bootstrap_config = match Config::from_env() {

View File

@ -866,6 +866,28 @@ mod tests {
assert!(!body.windows(4).any(|part| part == b"pass")); assert!(!body.windows(4).any(|part| part == b"pass"));
} }
#[tokio::test]
async fn configuration_setup_status_lists_both_bootstrap_requirements() {
let response =
router_configuration_setup(vec!["REDIS_URL".into(), "CONFIG_ENCRYPTION_KEY".into()])
.oneshot(
Request::builder()
.method("GET")
.uri("/api/status")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let value: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(value["missing"].as_array().unwrap().len(), 2);
assert_eq!(value["missing"][0], "REDIS_URL");
assert_eq!(value["missing"][1], "CONFIG_ENCRYPTION_KEY");
}
#[tokio::test] #[tokio::test]
async fn failed_bootstrap_does_not_consume_initialization_slot() { async fn failed_bootstrap_does_not_consume_initialization_slot() {
let app = router_with_worker_state_runtime_bootstrap( let app = router_with_worker_state_runtime_bootstrap(