futu_opend/main.rs
1// v1.4.110 P1-2: 拆 1267-LoC `async fn main()` body 到 startup/ + hints.rs.
2// 类型 (cli) + 配置 (config) + 凭据 (credentials) + crash log (crash_log) 已 pre-stage 抽出.
3
4use anyhow::Result;
5use clap::Parser;
6
7mod cli;
8mod config;
9mod crash_log;
10mod credentials;
11mod hints;
12mod startup;
13
14use cli::Args;
15use config::merge_config;
16use crash_log::{warn_if_previous_crash, write_crash_log_file};
17
18fn main() -> Result<()> {
19 // v1.4.109 broker-auth WebTCP: rustls 0.23 needs an explicit global
20 // CryptoProvider before the first direct TLS config is built.
21 futu_backend::auth::install_default_rustls_crypto_provider();
22
23 // v1.4.41 (external reviewer v1.4.40 报告 P3.1 修): 装全局 panic hook,让 daemon
24 // silent crash 时在 log 里留 "PANIC: ..." + 位置 + backtrace。
25 //
26 // 之前 v1.4.39 external reviewer 测期权 place-order 时 daemon 一次性 silent crash
27 // (log 末尾无 panic / SIGABRT / shutdown trace),无法定位。
28 //
29 // 做法:
30 // 1. 尽量早装(main 第一行)—— 在 tracing init 之前装的 hook 只能
31 // eprintln(还没 subscriber),但至少 stderr 有记录
32 // 2. tracing init 之后**重新装一次**(覆盖)—— 走 tracing::error! 进
33 // audit log + JSON log,便于 grep
34 std::panic::set_hook(Box::new(|info| {
35 let location = info
36 .location()
37 .map(|l| format!("{}:{}", l.file(), l.line()))
38 .unwrap_or_else(|| "<unknown>".to_string());
39 let payload = info
40 .payload()
41 .downcast_ref::<&str>()
42 .copied()
43 .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.as_str()))
44 .unwrap_or("<non-string panic payload>");
45 // 双 write:stderr + process exit。即使 tracing subscriber 还没起来,
46 // 用户都能在 stderr 看到。
47 eprintln!("╔═══ PANIC at {location} ═══");
48 eprintln!("║ {payload}");
49 eprintln!("║ thread={:?}", std::thread::current().name());
50 eprintln!("╚═══ process will abort ═══");
51 // v1.4.97 P1-D-D: also write dated crash log to disk for forensics.
52 write_crash_log_file(info);
53 }));
54
55 let args = Args::parse();
56
57 // v1.4.97 P1-D-D: warn if previous run crashed (scans
58 // ~/.futu-opend-rs/crashes/crash-*.log). Keep this after clap parsing so
59 // `--help`, `--version`, and parse errors remain clean fast paths.
60 warn_if_previous_crash();
61
62 // codex 0547 F6 (P3): dev-only feature flag 在 merge_config consume args
63 // 前 capture (这是 CLI-only 字段, 不进 RuntimeConfig).
64 #[cfg(feature = "dev-flags")]
65 let inject_auth_failure_every = args.inject_auth_failure_every;
66 // v1.4.110 P1-2: 没启用 dev-flags feature 时也要绑 None, 让 run_daemon
67 // signature 统一 (`Option<u64>`).
68 #[cfg(not(feature = "dev-flags"))]
69 let inject_auth_failure_every: Option<u64> = None;
70
71 // codex 0547 F6 (P3) fix: merge_config 提前到 args 仍可用阶段, 让 TZ /
72 // 安全 keys / audit_log 走 RuntimeConfig 统一来源 (CLI 优先 + TOML
73 // fallback). 之前 args.rest_keys_file / args.tz 直接 read, TOML 配置
74 // 无法影响这些字段 — 与 `--config` help 文 "字段与 CLI 一致" 不符.
75 let config = merge_config(args)?;
76 startup::apply_pre_runtime_tz(&config);
77
78 let runtime = tokio::runtime::Builder::new_multi_thread()
79 .enable_all()
80 .build()?;
81 runtime.block_on(startup::run_daemon(config, inject_auth_failure_every))
82}
83
84#[cfg(test)]
85mod tests;