# Hawkbit DDI Client 轻量级纯 Python OTA 更新代理,零依赖(仅 Python 3 标准库),可在 **Android adb shell** 和 **Linux 主机**上运行。 ## 功能 | 功能 | 说明 | |------|------| | **轮询** | `GET /{tenant}/controller/v1/{controllerId}` 查询部署任务,自动适配服务端建议的轮询间隔 | | **断点下载** | HTTP `Range` 请求实现断点续传,临时 `.tmp` 文件保留至下载完成 | | **SHA256 校验** | 下载完成后自动与部署元数据中的哈希值比对 | | **状态上报** | 完整状态链:`download` → `downloaded` → `proceeding` → `closed(success/failure)` | | **设备 SN 检测** | 自动检测序列号:Android `ro.serialno` → DMI `product_serial` → `/etc/machine-id` → hostname | | **设备属性上报** | 首次连接自动上报设备属性(osType, platform, hwRevision, kernel…),供 Hawkbit target filter 匹配 | | **认证** | 支持 `GatewayToken` 和 `TargetToken`,也支持从文件读取:`--gateway-token @/etc/hawkbit/gateway.key` | | **取消/确认** | 处理 `cancelAction`(取消任务)和 `confirmationBase`(headless 自动确认) | ### 设备属性上报 脚本在**首次轮询**时自动调用 `PUT …/configData` 上报以下属性: | 属性 | 来源 | 示例 | |------|------|------| | `osType` | `/system/build.prop` 或 uname | `android` / `linux` | | `platform` | `platform.machine()` | `aarch64` / `x86_64` | | `kernel` | `os.uname().release` | `6.17.0-35-generic` | | `hwRevision` | DMI product_name + product_version | `VMware Virtual Platform` | | `manufacturer` | DMI sys_vendor 或 Android ro.product.manufacturer | `VMware, Inc.` | | `hostname` | `socket.gethostname()` | `device-001` | | `swVersion` | `/etc/hawkbit/current_version`(可选) | `V1.0.0.0` | **扩展属性**:创建 `/etc/hawkbit/device_attrs.json` 添加自定义属性: ```json {"dept": "production", "region": "east", "owner": "team-a"} ``` 这些属性在 Hawkbit 里就是 target 的 attributes,管理员创建 target filter 时可以直接用: ``` 属性 osType=android AND hwRevision=VMware → 自动分配 V2.0 更新 ## 快速开始 ```bash # 网关令牌,每 30 秒轮询 python3 ddi-client.py -u https://hawkbit.example.com -t DEFAULT --gateway-token s3cret -i 30 # 网关令牌从文件读取(生产推荐) echo "s3cret" > /etc/hawkbit/gateway.key chmod 600 /etc/hawkbit/gateway.key python3 ddi-client.py -u https://hawkbit.example.com -t DEFAULT \ --gateway-token @/etc/hawkbit/gateway.key -i 30 # 目标令牌,单次轮询 python3 ddi-client.py -u http://10.0.0.1:8080 -t DEFAULT --target-token tok --once # 自定义设备 ID + 自定义属性文件,调试模式 python3 ddi-client.py -u https://hawkbit:8443 -t prod \ --gateway-token @/etc/hawkbit/gateway.key \ --controller-id edge-042 -d /data/ota -v ``` ## 命令行参数 ``` -u, --base-url Hawkbit 服务端地址(必填) -t, --tenant 租户名称,如 DEFAULT(必填) --gateway-token Gateway 安全令牌 --target-token Target 安全令牌 --controller-id 控制器 ID,默认自动检测设备序列号 -d, --download-dir 下载目录,默认 /tmp/hawkbit -i, --polling-interval 轮询间隔(秒),默认 60,可被服务端覆盖 --no-verify 跳过 SHA256 校验 --no-resume 禁用断点续传 --no-ssl-verify 禁用 TLS 证书验证(不安全) --once 仅轮询一次后退出 -v, --verbose 调试级日志 -h, --help 帮助信息 ``` ## DDI API 流程 ``` ┌─────────┐ ① GET /{tenant}/controller/v1/{controllerId} ┌──────────┐ │ │ ────────────────────────────────────────────────────→ │ │ │ 设备 │ ←──── HAL links (deploymentBase / cancel / …) │ Hawkbit │ │ │ │ Server │ │ │ ② GET …/deploymentBase/{actionId} │ │ │ │ ────────────────────────────────────────────────────→ │ │ │ │ ←──── chunks[].artifacts[] (filename, SHA256, │ │ │ │ download URL) │ │ │ │ │ │ │ │ ③ GET …/softwaremodules/{id}/artifacts/{file} │ │ │ │ ────────────────────────────────────────────────────→ │ │ │ │ ←──── 二进制流(支持 Range 断点续传) │ │ │ │ │ │ │ │ ④ POST …/deploymentBase/{actionId}/feedback │ │ │ │ ───── {execution, result, details} ────────────────→ │ │ │ │ ←──── 200 OK │ │ └─────────┘ └──────────┘ ``` ### 状态上报 JSON 格式 ```json { "timestamp": 1750000000000, "status": { "execution": "download", "result": { "finished": "none" }, "details": ["Starting download"] } } ``` | execution 值 | 含义 | result.finished | |-------------|------|-----------------| | `download` | 开始下载 | `none` | | `downloaded` | 下载完成 | `none` | | `proceeding` | 安装中 | `none` | | `closed` | 最终状态 | `success` / `failure` | | `canceled` | 已取消 | `none` | | `rejected` | 已拒绝 | `none` | ## 认证配置 ### Target Token(推荐,每设备独立令牌) 在 Hawkbit Management API 中为目标设置 security token: ```bash curl -u admin:admin -X PUT \ -H "Content-Type: application/json" \ "http://:9090/rest/v1/targets/" \ -d '{"securityToken": "my-device-token"}' ``` 启用租户级 TargetToken 认证: ```bash curl -u admin:admin -X PUT \ -H "Content-Type: application/json" \ "http://:9090/rest/v1/system/configs/authentication.targettoken.enabled" \ -d '{"value": true}' ``` ### Gateway Token(所有设备共享同一令牌) ```bash # 设置 token key curl -u admin:admin -X PUT \ -H "Content-Type: application/json" \ "http://:9090/rest/v1/system/configs/authentication.gatewaytoken.key" \ -d '{"value": "my-gateway-key"}' # 启用 curl -u admin:admin -X PUT \ -H "Content-Type: application/json" \ "http://:9090/rest/v1/system/configs/authentication.gatewaytoken.enabled" \ -d '{"value": true}' ``` ## 设备 SN 检测优先级 | 优先级 | 来源 | 适用平台 | |--------|------|---------| | 1 | `getprop ro.serialno` | Android | | 2 | `/sys/class/dmi/id/product_serial` | Linux (x86) | | 3 | `/sys/devices/virtual/dmi/id/product_serial` | Linux (ARM / 嵌入式) | | 4 | `/etc/machine-id` | systemd Linux | | 5 | `socket.gethostname()` | 通用回退 | ## 安装钩子 下载完成后,脚本调用 `_install(files, handling)` 方法执行实际安装。**默认实现仅做文件存在性检查,生产环境必须覆盖此方法**。 ### 方式一:子类继承 ```python from ddi_client import HawkbitDDIClient class MyClient(HawkbitDDIClient): def _install(self, files, handling): import subprocess for f in files: subprocess.run(["unzip", "-o", f, "-d", "/data/ota"], check=True) return True ``` ### 方式二:Monkey-Patch ```python client = HawkbitDDIClient(...) client._install = lambda files, h: do_my_install(files) ``` ## 兼容性 - Python ≥ 3.7 - Android (adb shell, 需安装 Python) - Linux (x86_64 / aarch64 / armv7l) - macOS - Hawkbit DDI API v1 ## 文件结构 ``` ddi-client.py # 脚本本体(单文件,可直接部署) ``` 无其他依赖文件。