Improve config file editing and recovery fallback mechanisms (#3082)
This commit is contained in:
@@ -23,6 +23,8 @@ use utoipa::OpenApi;
|
||||
#[openapi(
|
||||
paths(
|
||||
super::routes::config_management::backup_config,
|
||||
super::routes::config_management::recover_config,
|
||||
super::routes::config_management::validate_config,
|
||||
super::routes::config_management::init_config,
|
||||
super::routes::config_management::upsert_config,
|
||||
super::routes::config_management::remove_config,
|
||||
|
||||
@@ -457,44 +457,15 @@ pub async fn init_config(
|
||||
return Ok(Json("Config already exists".to_string()));
|
||||
}
|
||||
|
||||
let workspace_root = match std::env::current_exe() {
|
||||
Ok(mut exe_path) => {
|
||||
while let Some(parent) = exe_path.parent() {
|
||||
let cargo_toml = parent.join("Cargo.toml");
|
||||
if cargo_toml.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
|
||||
if content.contains("[workspace]") {
|
||||
exe_path = parent.to_path_buf();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
exe_path = parent.to_path_buf();
|
||||
}
|
||||
exe_path
|
||||
}
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
let init_config_path = workspace_root.join("init-config.yaml");
|
||||
if !init_config_path.exists() {
|
||||
return Ok(Json(
|
||||
// Use the shared function to load init-config.yaml
|
||||
match goose::config::base::load_init_config_from_workspace() {
|
||||
Ok(init_values) => match config.save_values(init_values) {
|
||||
Ok(_) => Ok(Json("Config initialized successfully".to_string())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
},
|
||||
Err(_) => Ok(Json(
|
||||
"No init-config.yaml found, using default configuration".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let init_content = match std::fs::read_to_string(&init_config_path) {
|
||||
Ok(content) => content,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
let init_values: HashMap<String, Value> = match serde_yaml::from_str(&init_content) {
|
||||
Ok(values) => values,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
match config.save_values(init_values) {
|
||||
Ok(_) => Ok(Json("Config initialized successfully".to_string())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,8 +526,8 @@ pub async fn backup_config(
|
||||
backup_name.push(".bak");
|
||||
|
||||
let backup = config_path.with_file_name(backup_name);
|
||||
match std::fs::rename(&config_path, &backup) {
|
||||
Ok(_) => Ok(Json(format!("Moved {:?} to {:?}", config_path, backup))),
|
||||
match std::fs::copy(&config_path, &backup) {
|
||||
Ok(_) => Ok(Json(format!("Copied {:?} to {:?}", config_path, backup))),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
} else {
|
||||
@@ -564,6 +535,82 @@ pub async fn backup_config(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/config/recover",
|
||||
responses(
|
||||
(status = 200, description = "Config recovery attempted", body = String),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn recover_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let config = Config::global();
|
||||
|
||||
// Force a reload which will trigger recovery if needed
|
||||
match config.load_values() {
|
||||
Ok(values) => {
|
||||
let recovered_keys: Vec<String> = values.keys().cloned().collect();
|
||||
if recovered_keys.is_empty() {
|
||||
Ok(Json("Config recovery completed, but no data was recoverable. Starting with empty configuration.".to_string()))
|
||||
} else {
|
||||
Ok(Json(format!(
|
||||
"Config recovery completed. Recovered {} keys: {}",
|
||||
recovered_keys.len(),
|
||||
recovered_keys.join(", ")
|
||||
)))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Config recovery failed: {}", e);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/config/validate",
|
||||
responses(
|
||||
(status = 200, description = "Config validation result", body = String),
|
||||
(status = 422, description = "Config file is corrupted")
|
||||
)
|
||||
)]
|
||||
pub async fn validate_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let config_dir = choose_app_strategy(APP_STRATEGY.clone())
|
||||
.expect("goose requires a home dir")
|
||||
.config_dir();
|
||||
|
||||
let config_path = config_dir.join("config.yaml");
|
||||
|
||||
if !config_path.exists() {
|
||||
return Ok(Json("Config file does not exist".to_string()));
|
||||
}
|
||||
|
||||
match std::fs::read_to_string(&config_path) {
|
||||
Ok(content) => match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
||||
Ok(_) => Ok(Json("Config file is valid".to_string())),
|
||||
Err(e) => {
|
||||
tracing::warn!("Config validation failed: {}", e);
|
||||
Err(StatusCode::UNPROCESSABLE_ENTITY)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to read config file: {}", e);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/config/current-model",
|
||||
@@ -597,6 +644,8 @@ pub fn routes(state: Arc<AppState>) -> Router {
|
||||
.route("/config/pricing", post(get_pricing))
|
||||
.route("/config/init", post(init_config))
|
||||
.route("/config/backup", post(backup_config))
|
||||
.route("/config/recover", post(recover_config))
|
||||
.route("/config/validate", get(validate_config))
|
||||
.route("/config/permissions", post(upsert_permissions))
|
||||
.route("/config/current-model", get(get_current_model))
|
||||
.with_state(state)
|
||||
|
||||
Reference in New Issue
Block a user