Redesign runtime w/ include-aware config + Module Split + Listener Lifecycle + Atomic Reload

This commit is contained in:
Alexey
2026-08-22 16:13:25 +03:00
parent f9910fb29e
commit 7f4b87bea4
72 changed files with 14433 additions and 12976 deletions
+118
View File
@@ -0,0 +1,118 @@
use super::*;
pub(in crate::api) async fn rotate_secret(
user: &str,
body: RotateSecretRequest,
expected_revision: Option<String>,
shared: &ApiShared,
) -> Result<(CreateUserResponse, String), ApiFailure> {
let secret = body.secret.unwrap_or_else(random_user_secret);
if !is_valid_user_secret(&secret) {
return Err(ApiFailure::bad_request(
"secret must be exactly 32 hex characters",
));
}
let _guard = shared.mutation_lock.lock().await;
let mut cfg = load_config_from_disk(&shared.config_path).await?;
ensure_expected_revision(&shared.config_path, expected_revision.as_deref()).await?;
if !cfg.access.users.contains_key(user) {
return Err(ApiFailure::new(
StatusCode::NOT_FOUND,
"not_found",
"User not found",
));
}
cfg.access.users.insert(user.to_string(), secret.clone());
cfg.validate()
.map_err(|e| ApiFailure::bad_request(format!("config validation failed: {}", e)))?;
let revision =
save_access_sections_to_disk(&shared.config_path, &cfg, &[AccessSection::Users]).await?;
drop(_guard);
let (detected_ip_v4, detected_ip_v6) = shared.detected_link_ips();
let users = users_from_config(
&cfg,
&shared.stats,
&shared.ip_tracker,
detected_ip_v4,
detected_ip_v6,
None,
)
.await;
let user_info = users
.into_iter()
.find(|entry| entry.username == user)
.ok_or_else(|| ApiFailure::internal("failed to build updated user view"))?;
Ok((
CreateUserResponse {
user: user_info,
secret,
},
revision,
))
}
pub(in crate::api) async fn delete_user(
user: &str,
expected_revision: Option<String>,
shared: &ApiShared,
) -> Result<(String, String), ApiFailure> {
let _guard = shared.mutation_lock.lock().await;
let mut cfg = load_config_from_disk(&shared.config_path).await?;
ensure_expected_revision(&shared.config_path, expected_revision.as_deref()).await?;
if !cfg.access.users.contains_key(user) {
return Err(ApiFailure::new(
StatusCode::NOT_FOUND,
"not_found",
"User not found",
));
}
if cfg.access.users.len() <= 1 {
return Err(ApiFailure::new(
StatusCode::CONFLICT,
"last_user_forbidden",
"Cannot delete the last configured user",
));
}
let mut touched_sections = vec![AccessSection::Users];
cfg.access.users.remove(user);
if cfg.access.user_enabled.remove(user).is_some() {
touched_sections.push(AccessSection::UserEnabled);
}
if cfg.access.user_ad_tags.remove(user).is_some() {
touched_sections.push(AccessSection::UserAdTags);
}
if cfg.access.user_max_tcp_conns.remove(user).is_some() {
touched_sections.push(AccessSection::UserMaxTcpConns);
}
if cfg.access.user_expirations.remove(user).is_some() {
touched_sections.push(AccessSection::UserExpirations);
}
if cfg.access.user_data_quota.remove(user).is_some() {
touched_sections.push(AccessSection::UserDataQuota);
}
if cfg.access.user_rate_limits.remove(user).is_some() {
touched_sections.push(AccessSection::UserRateLimits);
}
if cfg.access.user_max_unique_ips.remove(user).is_some() {
touched_sections.push(AccessSection::UserMaxUniqueIps);
}
cfg.validate()
.map_err(|e| ApiFailure::bad_request(format!("config validation failed: {}", e)))?;
let revision =
save_access_sections_to_disk(&shared.config_path, &cfg, &touched_sections).await?;
drop(_guard);
shared.ip_tracker.remove_user_limit(user).await;
shared.ip_tracker.clear_user_ips(user).await;
Ok((user.to_string(), revision))
}