长跑 daemon 部署指南 (v1.4.94+)¶
本指南覆盖 daemon 在生产环境长期运行 (24h+ / 7 天 / 30 天) 时特有的配置项、 故障自愈、与失败 fallback 路径。
适用场景: - VPS / 云服务器 7×24 跑 daemon - 容器化部署 (Docker / Kubernetes) - 多账号同 daemon 多 session - 期权 / 期货策略需要长期保持订阅与 push 不中断
如果只是本地短时间手工跑 (CLI 调用 / 手动测试), 本指南不必读, 默认行为已足够.
v1.4.94 长跑加固开关 (env opt-in, default OFF)¶
v1.4.94 加了 2 个 env opt-in 开关, 让长跑 daemon 在 client_sig 失效前主动续期 /
持续失败时主动重新登录, 减少需要人工 admin reload / 重启 daemon 的频率.
默认 OFF 是因为这两个路径依赖 backend 的具体响应行为, 真机 verify 后下版会切 default ON. 当前生产环境若想提前启用, 按本节配置.
FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1 — 提前 1h 主动 refresh client_sig¶
何时有效: daemon 启动时从 /authority/ 响应抽出 client_sig_invalid_local_time_s
(C++ auth_impl.cpp:3245-3247). 若这个时间戳 > 0 (服务端给了过期时间), daemon
spawn 一个 timer 在 (invalid_time - 3600s) 时主动调
AuthRefresher::refresh_qot_login() 续期, 不等 backend 返报错.
默认 OFF 原因: backend 是否接受 multi-hour 提前 refresh 还是 "too early" reject 未真机 verify (pitfall #42). 真机 verify path:
- 启动 daemon:
FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1 RUST_LOG=info futu-opend ... - daemon log 应有 "v1.4.94 G1: client_sig invalidate scheduled" + ttl_secs
- daemon log 应有 "v1.4.94 G1: spawning client_sig proactive timer"
- 等到 timer 触发 (生产环境通常 24-48h 后), log 应有 "v1.4.94 G1: refresh completed"
- timer 触发后 daemon 仍能正常 query / push, 不掉线 = backend 接受提前 refresh
FUTU_CLIENT_SIG_REACTIVE_REFRESH=1 — tcp_login 持续失败时反应式 refresh¶
何时有效: daemon TCP 心跳失败 → reconnect monitor 启动. 若 reconnect 时
tcp_login 连续失败 ≥ 3 次, daemon 认为 client_sig 可能失效, 自动调
AuthRefresher::refresh_qot_login() + auth::reauth_via_remember_login() 拿 fresh
AuthResult, 替换 reconnect monitor 的 client_sig 缓存, 下一轮 tcp_login 用新 sig.
默认 OFF 原因: tcp_login 失败的 ret_type 含义不止 client_sig 失效一种 —
还可能是服务端反刷限流 / 风控. 误 refresh 可能加重 limit (pitfall #42 + docs/protocol/auth.md
"error_code=15 三大成因"). 真机 verify path:
- 在测试环境改本地系统时钟 +30 天 (跨过 client_sig 自然过期), 或等长跑 30 天
- 启动 daemon:
FUTU_CLIENT_SIG_REACTIVE_REFRESH=1 ... - heartbeat 必断 → reconnect monitor 启动
- log 应见 "reconnect login failed" (3 次)
- log 应见 "v1.4.94 G4: persistent tcp_login failures → trying reactive client_sig refresh"
- log 应见 "v1.4.94 G4: refresh_qot_login OK → reauth_via_remember_login"
- 后续 tcp_login 应成功, daemon 恢复 push
推荐部署组合¶
| 环境 | G1 | G4 | 备注 |
|---|---|---|---|
| 本地短跑 (CLI / 测试) | OFF | OFF | 默认即可, 无 long-runner 需求 |
| 生产长跑 (24h+) early adopter | ON | OFF | 先验 G1 不报副作用; G4 默认仍由 admin reload 处理 |
| 生产长跑 (7 天+) full opt-in | ON | ON | 期望 zero admin reload; tester 已 verify pass |
| 多账号集中 daemon (5+ accounts) | ON | OFF | 启 G4 风险: 一账号反刷 → daemon 多账号都被影响 |
具体 verify 后真机抓包 (.githooks/pre-push 已含 redact 规则, 可安全 paste log
给开发者) → v1.4.95+ 评估切 default ON.
容器化部署示例¶
Docker¶
# docker-compose.yml 片段
services:
futu-opend:
image: ghcr.io/futuleaf/futu-opend-rs:v1.4.94
restart: unless-stopped
environment:
FUTU_ACCOUNT: <account>
FUTU_PWD: <password>
FUTU_CLIENT_SIG_PROACTIVE_REFRESH: "1" # 长跑加固
# FUTU_CLIENT_SIG_REACTIVE_REFRESH: "1" # 单账号场景才开
volumes:
- ./.futu-opend-rs:/root/.futu-opend-rs # device_id + credentials 持久化
ports:
- "11111:11111" # gRPC
- "11112:11112" # REST
- "11113:11113" # WebSocket
systemd 单元¶
[Unit]
Description=futu-opend long-running gateway
After=network-online.target
[Service]
Type=simple
User=futu
EnvironmentFile=/etc/futu-opend.env
Environment=FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1
ExecStart=/usr/local/bin/futu-opend --rest-port 11112 --grpc-port 11111
Restart=on-failure
RestartSec=30s
[Install]
WantedBy=multi-user.target
/etc/futu-opend.env 含 FUTU_ACCOUNT=... + FUTU_PWD=... (mode 0600 owner=futu).
自愈 vs 人工干预¶
| 场景 | v1.4.94 行为 | v1.4.94 行为 | env required |
|---|---|---|---|
client_sig 自然过期 (30 天) |
daemon 报错 → 需 admin reload | G1 timer 提前 1h 自动续 | FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1 |
| 网络抖动后 tcp_login 1-2 次 fail | 指数退避后续重试 | 同 | — |
| 网络抖动后 tcp_login ≥ 3 次 fail | 持续 fail / 人工干预 | G4 自动 refresh + retry | FUTU_CLIENT_SIG_REACTIVE_REFRESH=1 |
服务端反刷 (error_code=15) |
等 60s 重试 | 同 (G4 不 trigger 反刷场景) | — |
| 账号风控 / 密码改 / 设备锁 | 必须人工 SMS / --reset-device |
同 | — |
| broker auth_code 30 天过期 | v1.4.94 G2 RepullAuthCode 已自动 refresh | 同 | (default ON 自 v1.4.94) |
Troubleshooting¶
G1 启动后 timer 不 spawn¶
log 找 "v1.4.94 G1 skip" 看 reason:
- "client_sig_invalid_local_time_s=0": backend 没返此字段 → 老 backend / 旧 credentials shell.
--reset-device 重新 password auth 后 backend 会下发新字段.
- "env FUTU_CLIENT_SIG_PROACTIVE_REFRESH not set to 1": env var 没传到 daemon.
- "TooLate": invalid_time 已过期或不到 1h, 跳过. 重启 daemon 让首登拿新 invalid_time.
G4 启动后 refresh 不 trigger¶
log 找 "v1.4.94 G4 skip" 看 reason: - "consecutive failures below threshold": 失败次数还没到 3 - "auth_refresher not injected": 内部 wiring 问题 → 报 issue - "already attempted refresh this cycle": 防 refresh-fail-loop. 重启 daemon 重置.
G4 refresh 失败但 daemon 继续用 stale auth¶
设计如此. log 应有 "v1.4.94 G4: refresh_qot_login failed (continue with stale AuthResult)" 或 "v1.4.94 G4: reauth_via_remember_login failed (continue with stale AuthResult; user may need admin reload)". G4 是 best-effort, 失败不打断老 fallback 行为 (admin reload 路径完整保留).
与 v1.4.95+ 计划¶
- v1.4.95+: G1/G4 真机 verify 充分后切 default ON, 本指南顶部的 "default OFF" 描述更新.
- v1.5+: G6 实际 moomoo path broker channel routing (现 v1.4.94 只 lay infrastructure, routing 仍走主 client_sig 对齐 C++ 等价行为).
如有部署问题, 提 issue 或 contact via official site.