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
18#[tokio::main]
19async fn main() -> Result<()> {
20 // v1.4.109 broker-auth WebTCP: rustls 0.23 needs an explicit global
21 // CryptoProvider before the first direct TLS config is built.
22 futu_backend::auth::install_default_rustls_crypto_provider();
23
24 // v1.4.41 (eli v1.4.40 报告 P3.1 修): 装全局 panic hook,让 daemon
25 // silent crash 时在 log 里留 "PANIC: ..." + 位置 + backtrace。
26 //
27 // 之前 v1.4.39 eli 测期权 place-order 时 daemon 一次性 silent crash
28 // (log 末尾无 panic / SIGABRT / shutdown trace),无法定位。
29 //
30 // 做法:
31 // 1. 尽量早装(main 第一行)—— 在 tracing init 之前装的 hook 只能
32 // eprintln(还没 subscriber),但至少 stderr 有记录
33 // 2. tracing init 之后**重新装一次**(覆盖)—— 走 tracing::error! 进
34 // audit log + JSON log,便于 grep
35 std::panic::set_hook(Box::new(|info| {
36 let location = info
37 .location()
38 .map(|l| format!("{}:{}", l.file(), l.line()))
39 .unwrap_or_else(|| "<unknown>".to_string());
40 let payload = info
41 .payload()
42 .downcast_ref::<&str>()
43 .copied()
44 .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.as_str()))
45 .unwrap_or("<non-string panic payload>");
46 // 双 write:stderr + process exit。即使 tracing subscriber 还没起来,
47 // 用户都能在 stderr 看到。
48 eprintln!("╔═══ PANIC at {location} ═══");
49 eprintln!("║ {payload}");
50 eprintln!("║ thread={:?}", std::thread::current().name());
51 eprintln!("╚═══ process will abort ═══");
52 // v1.4.97 P1-D-D: also write dated crash log to disk for forensics.
53 write_crash_log_file(info);
54 }));
55
56 // v1.4.97 P1-D-D: warn if previous run crashed (scans
57 // ~/.futu-opend-rs/crashes/crash-*.log).
58 warn_if_previous_crash();
59
60 let args = Args::parse();
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::run_daemon(config, inject_auth_failure_every).await
77}
78
79#[cfg(test)]
80mod tests;