215 lines
8.3 KiB
Markdown
215 lines
8.3 KiB
Markdown
|
|
# 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://<hawkbit>:9090/rest/v1/targets/<controllerId>" \
|
|||
|
|
-d '{"securityToken": "my-device-token"}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
启用租户级 TargetToken 认证:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -u admin:admin -X PUT \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
"http://<hawkbit>: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://<hawkbit>: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://<hawkbit>: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 # 脚本本体(单文件,可直接部署)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
无其他依赖文件。
|