1
This commit is contained in:
@@ -80,14 +80,16 @@ mod work_author;
|
||||
mod work_play_tracking;
|
||||
|
||||
use shared_logging::init_tracing;
|
||||
use std::{collections::HashSet, env, fs, io, panic, thread};
|
||||
use std::{collections::HashSet, env, fs, io, panic, thread, time::Duration};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::runtime::Builder as TokioRuntimeBuilder;
|
||||
use tracing::info;
|
||||
use tokio::time::timeout;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{app::build_router, config::AppConfig, state::AppState};
|
||||
|
||||
const API_SERVER_STARTUP_STACK_SIZE_BYTES: usize = 32 * 1024 * 1024;
|
||||
const AUTH_STORE_STARTUP_RESTORE_TIMEOUT: Duration = Duration::from_secs(8);
|
||||
|
||||
fn main() -> Result<(), io::Error> {
|
||||
// Windows 本地调试下 Axum 路由树和启动恢复链较重,显式放大启动线程栈,避免 debug 构建在进入监听前栈溢出。
|
||||
@@ -121,7 +123,7 @@ async fn run_server() -> Result<(), io::Error> {
|
||||
let bind_address = config.bind_socket_addr();
|
||||
let listener = TcpListener::bind(bind_address).await?;
|
||||
|
||||
let state = AppState::try_restore_auth_store_from_spacetime(config)
|
||||
let state = restore_app_state_for_startup(config)
|
||||
.await
|
||||
.map_err(|error| std::io::Error::other(format!("初始化应用状态失败:{error}")))?;
|
||||
let router = build_router(state);
|
||||
@@ -131,14 +133,47 @@ async fn run_server() -> Result<(), io::Error> {
|
||||
axum::serve(listener, router).await
|
||||
}
|
||||
|
||||
async fn restore_app_state_for_startup(
|
||||
config: AppConfig,
|
||||
) -> Result<AppState, state::AppStateInitError> {
|
||||
let fallback_config = config.clone();
|
||||
match timeout(
|
||||
AUTH_STORE_STARTUP_RESTORE_TIMEOUT,
|
||||
AppState::try_restore_auth_store_from_spacetime(config),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
warn!(
|
||||
timeout_seconds = AUTH_STORE_STARTUP_RESTORE_TIMEOUT.as_secs(),
|
||||
"启动恢复认证快照超时,跳过远端恢复并继续启动 api-server"
|
||||
);
|
||||
AppState::new(fallback_config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_local_env_files() {
|
||||
let shell_env_keys = env::vars().map(|(key, _)| key).collect::<HashSet<_>>();
|
||||
let shell_env_keys = protected_env_keys_from(env::vars());
|
||||
|
||||
for path in [".env", ".env.local", ".env.secrets.local"] {
|
||||
load_env_file(path, &shell_env_keys);
|
||||
}
|
||||
}
|
||||
|
||||
fn protected_env_keys_from(vars: impl IntoIterator<Item = (String, String)>) -> HashSet<String> {
|
||||
vars.into_iter()
|
||||
.filter_map(|(key, value)| {
|
||||
if value.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(key)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn load_env_file(path: &str, shell_env_keys: &HashSet<String>) {
|
||||
let Ok(raw_text) = fs::read_to_string(path) else {
|
||||
return;
|
||||
@@ -193,7 +228,7 @@ fn is_valid_env_key(key: &str) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_valid_env_key, strip_env_value};
|
||||
use super::{is_valid_env_key, protected_env_keys_from, strip_env_value};
|
||||
|
||||
#[test]
|
||||
fn strip_env_value_removes_wrapping_quotes() {
|
||||
@@ -218,4 +253,20 @@ mod tests {
|
||||
assert!(!is_valid_env_key("1_BAD"));
|
||||
assert!(!is_valid_env_key("BAD-KEY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_shell_env_does_not_protect_dotenv_value() {
|
||||
let protected = protected_env_keys_from([
|
||||
("ALIYUN_OSS_BUCKET".to_string(), "".to_string()),
|
||||
("ALIYUN_OSS_ENDPOINT".to_string(), " ".to_string()),
|
||||
(
|
||||
"ALIYUN_OSS_ACCESS_KEY_ID".to_string(),
|
||||
"configured".to_string(),
|
||||
),
|
||||
]);
|
||||
|
||||
assert!(!protected.contains("ALIYUN_OSS_BUCKET"));
|
||||
assert!(!protected.contains("ALIYUN_OSS_ENDPOINT"));
|
||||
assert!(protected.contains("ALIYUN_OSS_ACCESS_KEY_ID"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user