# macOS launchctl 本机部署模板

本目录用于把本机生产验证服务的启动方式仓库化。模板面向当前生产验证链路：

- `gb32960-ingest-app`: TCP `32960`，HTTP `20100`
- `jt808-ingest-app`: TCP `808`，HTTP `20400`
- `yutong-mqtt-app`: HTTP `20500`
- `vehicle-history-app`: HTTP `20200`
- `vehicle-analytics-app`: HTTP `20300`

`vehicle-history-app` 的热历史查询只依赖 Kafka 与 TDengine：RAW 帧的 `parsedJson`、`metadataJson`、`rawUri` 已进入 TDengine `raw_frames`。GB32960/JT808 接入服务可以继续保留 `SINK_ARCHIVE_PATH` 作为原始 `.bin` 冷备，history 本机模板不再要求共享 archive root。

`vehicle-history-app` 的生产高吞吐模板是 TDengine-only：

- 不暴露 DuckDB 或文件型明细库启动变量，避免本地文件索引进入实时消费热路径。
- 只写 `raw_frames` 和协议位置表，避免逐字段写放大。
- 通用 RAW 查询直接返回 TDengine `raw_frames` 中的 `parsedJson`/`parsedFields`；字段趋势宽表由独立字段解析服务消费 Kafka RAW/事件后维护。

## 生成 plist

在仓库根目录执行：

```bash
export PROJECT_ROOT="$(pwd)"
export JAVA_BIN="$(/usr/libexec/java_home 2>/dev/null)/bin/java"
export INGEST_ARCHIVE_PATH="$PROJECT_ROOT/data/archive-live"
export TDENGINE_JDBC_URL='jdbc:TAOS-WS://<tdengine-host>:6041/vehicle_ts'
export TDENGINE_USERNAME='root'
export TDENGINE_PASSWORD='<tdengine-password>'
export VEHICLE_IDENTITY_MYSQL_JDBC_URL='jdbc:mysql://<mysql-host>:3306/vehicle_ingest'
export VEHICLE_IDENTITY_MYSQL_USERNAME='<mysql-user>'
export VEHICLE_IDENTITY_MYSQL_PASSWORD='<mysql-password>'
export VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL='60s'
export MYSQL_JDBC_URL="$VEHICLE_IDENTITY_MYSQL_JDBC_URL"
export MYSQL_USERNAME="$VEHICLE_IDENTITY_MYSQL_USERNAME"
export MYSQL_PASSWORD="$VEHICLE_IDENTITY_MYSQL_PASSWORD"
export YUTONG_MQTT_ENABLED='false'
export YUTONG_MQTT_URI=''
export YUTONG_MQTT_TOPIC='#'
export YUTONG_MQTT_USERNAME=''
export YUTONG_MQTT_PASSWORD=''
export YUTONG_MQTT_KEEP_ALIVE_SECONDS='20'
export YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS='10'
export YUTONG_MQTT_TLS_CA_PEM=''
export YUTONG_MQTT_TLS_CLIENT_PEM=''
export YUTONG_MQTT_TLS_CLIENT_KEY=''
export YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED='true'

mkdir -p "$HOME/Library/LaunchAgents" "$PROJECT_ROOT/data"
mkdir -p /tmp/lingniu-gb32960-live /tmp/lingniu-jt808-live /tmp/lingniu-yutong-mqtt-live /tmp/lingniu-vehicle-history-live /tmp/lingniu-vehicle-analytics-live

python3 deploy/local/launchctl/render.py
```

渲染脚本固定生成 `gb32960 jt808 yutong-mqtt vehicle-history vehicle-analytics` 五个 plist，并会对密码、JDBC URL、MQTT topic、keepAlive、连接超时和 TLS PEM 参数中的 XML 特殊字符做转义，避免 `#`、`&` 等字符破坏 plist。

如果生产配置由 Nacos 下发，可以把模板里的 `NACOS_CONFIG_ENABLED` 改成 `true`，并补充 `NACOS_SERVER_ADDR`、`NACOS_NAMESPACE`、`NACOS_GROUP`、`NACOS_USERNAME`、`NACOS_PASSWORD`。端口、Kafka topic、接入服务冷备 archive 路径、TDengine 连接建议仍保留为启动环境变量，便于 Portainer、launchctl 和临时压测保持一致。

history 消费者会按协议和 raw/event 自动拆分 consumer group。设置 `KAFKA_GROUP_HISTORY=vehicle-history-live` 后，实际 group 为：

- `vehicle-history-live-gb32960-event`
- `vehicle-history-live-gb32960-raw`
- `vehicle-history-live-jt808-event`
- `vehicle-history-live-jt808-raw`
- `vehicle-history-live-yutong-mqtt-event`
- `vehicle-history-live-yutong-mqtt-raw`

本地模板与 ECS 生产口径一致，身份绑定只使用 MySQL。启动前必须提供 `VEHICLE_IDENTITY_MYSQL_*` 连接参数；服务会自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表：前者只保存 `plate`、`vin`，后者保存注册字段；服务按刷新周期用注册表里的车牌关联 VIN，后续 raw/event 会带真实 VIN。

## 启停

```bash
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.gb32960.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.jt808.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.yutong-mqtt.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-history.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-analytics.plist"

launchctl list | rg 'com.lingniu.(gb32960|jt808|yutong-mqtt|vehicle-history|vehicle-analytics)'
```

替换 jar 前先停止服务，避免 KeepAlive 在 jar 拷贝中途拉起半包：

```bash
launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.gb32960.plist" || true
launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.jt808.plist" || true
launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.yutong-mqtt.plist" || true
launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-history.plist" || true
launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-analytics.plist" || true
```

## 验证地址

```bash
curl -sS http://127.0.0.1:20100/actuator/health
curl -sS http://127.0.0.1:20100/actuator/health/liveness
curl -sS http://127.0.0.1:20100/actuator/health/readiness
curl -sS http://127.0.0.1:20400/actuator/health
curl -sS http://127.0.0.1:20400/actuator/health/liveness
curl -sS http://127.0.0.1:20400/actuator/health/readiness
curl -sS http://127.0.0.1:20500/actuator/health
curl -sS http://127.0.0.1:20500/actuator/health/liveness
curl -sS http://127.0.0.1:20500/actuator/health/readiness
curl -sS http://127.0.0.1:20200/actuator/health
curl -sS http://127.0.0.1:20200/actuator/health/liveness
curl -sS http://127.0.0.1:20200/actuator/health/readiness
curl -sS http://127.0.0.1:20300/actuator/health
curl -sS http://127.0.0.1:20300/actuator/health/liveness
curl -sS http://127.0.0.1:20300/actuator/health/readiness
```

API / Swagger：

- GB32960 ingest health: `http://127.0.0.1:20100/actuator/health`
- JT808 ingest health: `http://127.0.0.1:20400/actuator/health`
- Yutong MQTT ingest health: `http://127.0.0.1:20500/actuator/health`
- History query: `http://127.0.0.1:20200/swagger-ui/index.html`
- Analytics metrics: `http://127.0.0.1:20300/swagger-ui/index.html`
