Skip to content

13 — 固件资源服务器 ​

部署时间:2026-11-08 05:26~05:30 CST 名称:TenoDX Firmware Registry | 版本 1.0.0 纯 API 服务,没有任何浏览器界面。


1. 选型:为什么自己写 ​

候选否决理由
MinIO自带 Web Console,与"API-only"要求冲突;常驻内存 ≥200 MB,本机仅剩 ~1 GB
Gitea Releases同样自带 Web UI;为"发固件"引入整套 issue/PR 体系,过重
Nexus / ArtifactoryJava 栈,内存 500 MB 起步,2C1.7G 跑不动
nginx/Caddy 静态目录没有版本元数据、没有更新说明、没有鉴权上传
自建 FastAPI ✅只做发固件这一件事:版本 + 时间 + 更新说明 + SHA256 + 鉴权上传

关键取舍:元数据直接存进已经存在的 PostgreSQL(新增库 tenodx_fw), 这样"数据库每日备份""数据库监控"自动覆盖新服务,不用再引一套有状态组件。 文件本体落在 Docker 卷 tenodx-fwdata。


2. 部署信息 ​

项值
镜像本地构建 tenodx/fw-registry:1.0.0(python:3.12-slim + FastAPI)
容器tenodx-fw-registry
监听127.0.0.1:8000(仅 loopback)
内存~51 MB
数据库PostgreSQL 库 tenodx_fw(复用 tenodx-postgres)
文件存储Docker 卷 tenodx-fwdata → /data/firmware
部署目录/opt/stacks/fw-registry/
源码server/fw-registry/(Dockerfile + app/main.py + publish.ps1)
Caddy 路由fw.localhost → tenodx-fw-registry:8000(见 12-Web入口.md)
监控Uptime Kuma「Firmware Registry」项,探针 /api/v1/health

⚠️ 重要:当前对外不可达 ​

阿里云安全组只放行 22/tcp,所以这台服务器上的固件服务公网访问不到。 现在的使用方式是走 SSH 隧道:

powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\server-info\scripts\tunnel.ps1 -Service fw
# 本地 http://127.0.0.1:18000  ->  服务器 127.0.0.1:8000

要真正"向用户分发",必须先放行安全组(步骤见 02-防火墙与端口.md 第 7 节), 然后在 12-Web入口.md 第 7 节把 Caddy 换成真域名 + 自动 HTTPS。


3. API 参考 ​

所有接口返回 JSON;下载接口返回二进制流。没有 /docs、/redoc、/openapi.json(均已关闭)。

鉴权 ​

请求头:X-API-Token: <token>

Token权限存放位置
FW_ADMIN_TOKEN上传 / 修改 / 删除(隐含读权限)服务器 /opt/stacks/fw-registry/.env
FW_READ_TOKEN只读(仅在开启下载鉴权后使用)同上
  • 缺 token 或 token 错误 → 401
  • 比较用 hmac.compare_digest(常数时间,防时序攻击)

公开接口(无需 token) ​

GET /api/v1/health ​

json
{ "status": "ok", "database": "ok" }

数据库不可用时返回 503 且 status: "degraded"。(监控用的就是这个)

GET /api/v1/firmware ​

查询参数:model(默认全部)、channel、include_yanked、limit(默认 100)

json
{
  "count": 2,
  "items": [
    {
      "id": 2,
      "model": "h503",
      "version": "1.2.3",
      "channel": "stable",
      "filename": "h503-1.2.3.bin",
      "size": 131072,
      "sha256": "22250e12fcdf...",
      "content_type": "application/octet-stream",
      "changelog": "- 修复触摸基线漂移\n- 提升采样率至 240Hz",
      "uploaded_at": "2026-11-07T21:31:13.259688+00:00",
      "uploaded_by": "build-agent",
      "yanked": false,
      "downloads": 3,
      "download_path": "/api/v1/firmware/2/download"
    }
  ]
}

排序是按版本号智能比较(1.10 > 1.9,v1.2.3 也认,正式版优于预发布版), 不是按上传时间。所以重新上传旧版本不会让它变成"最新"。

GET /api/v1/firmware/latest?model=h503&channel=stable ​

返回单个最新版本对象;没有任何固件时 404。

GET /api/v1/firmware/{id} ​

返回单个版本对象。

GET /api/v1/firmware/{id}/download ​

返回文件流,附带头:

X-Firmware-Version: 1.2.3
X-Firmware-SHA256: 22250e12...
Cache-Control: public, max-age=31536000, immutable

并自动累加下载计数。文件在磁盘上缺失时返回 410 Gone。

当前下载公开。要改成需要 token:服务器 .env 里设 FW_REQUIRE_DOWNLOAD_TOKEN=1 然后 docker compose up -d。 这就是你说的"之后可能要鉴权"的开关,不用改代码。

管理接口(需要 admin token) ​

POST /api/v1/firmware — 上传 ​

multipart/form-data:

字段必填说明
file✅固件,「,.bin .hex .elf .srec .s19 .zip .dfu」
version✅如 1.2.3
model默认 h503
channelstable / beta / dev,默认 stable
changelog更新说明,纯文本(换行保留)
uploaded_by上传者标识

返回 201 + 版本对象。

错误码:

码含义
400缺 version / 扩展名不支持 / 空文件
401token 无效
409同 model+version+channel 已存在(版本号必须唯一,不能覆盖)
413超过 FW_MAX_UPLOAD_BYTES(默认 64 MB)

服务端会边写边算 SHA256,返回的 sha256 可与本地 Get-FileHash 对照。

PATCH /api/v1/firmware/{id} ​

可改 changelog、channel、yanked(下架但不删文件)。

DELETE /api/v1/firmware/{id}?purge_file=true ​

删除记录并(默认)删除磁盘文件。


4. 上传固件(你手动上传的流程) ​

一次性准备:拿 admin token ​

bash
ssh tenodx "grep '^FW_ADMIN_TOKEN=' /opt/stacks/fw-registry/.env | cut -d= -f2-"

方式 A:用 publish.ps1(推荐) ​

powershell
# 1) 开隧道
powershell -NoProfile -ExecutionPolicy Bypass -File .\server-info\scripts\tunnel.ps1 -Service fw

# 2) 上传
$env:FW_ADMIN_TOKEN = "<上一步拿到的 token>"
powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\server\fw-registry\publish.ps1 `
  -File .\TenoDXController-H503\build\h503.bin `
  -Version 1.2.3 `
  -Changelog @"
- 修复触摸基线漂移
- 采样率提升至 240Hz
- 优化 USB 重连时序
"@

脚本会打印本地/服务端 SHA256 并在不一致时告警。

方式 B:curl ​

bash
curl -X POST http://127.0.0.1:18000/api/v1/firmware \
  -H "X-API-Token: $FW_ADMIN_TOKEN" \
  -F "file=@h503.bin" \
  -F "version=1.2.3" \
  -F "model=h503" \
  -F "channel=stable" \
  -F "changelog=修复触摸基线漂移" \
  -F "uploaded_by=me"

方式 C:curl 走 Caddy(域名可用后) ​

bash
curl -X POST https://fw.your.domain.example/api/v1/firmware \
  -H "X-API-Token: $FW_ADMIN_TOKEN" -F file=@h503.bin -F version=1.2.3

5. 目录与文件布局 ​

/data/firmware/h503/1.2.3/h503-1.2.3.bin      # 卷 tenodx-fwdata 内

DB 表结构:

sql
firmware(
  id, model, version, channel, filename, size, sha256, content_type,
  changelog, uploaded_at, uploaded_by, yanked, downloads
  UNIQUE (model, version, channel)
)

6. 备份 ​

内容方式覆盖
元数据已在 pg_dump 里(库 tenodx_fw)每日 03:30 ✅
固件文件尚未备份 ⚠️—

建议补充(未自动做,因为固件体积可能很大):

bash
docker run --rm -v tenodx-fwdata:/data -v /var/backups:/backup alpine \
  tar czf /backup/fwdata-$(date +%F).tar.gz -C /data .

7. 运维命令 ​

bash
cd /opt/stacks/fw-registry

docker compose ps
docker compose logs --tail 50
docker compose up -d            # 改 .env 后应用
docker compose build && docker compose up -d   # 改代码后重建

# 看磁盘用量
docker system df -v | grep fwdata

# 直接查库
docker exec -it tenodx-postgres psql -U tenodx -d tenodx_fw \
  -c "select id, version, channel, size, uploaded_at from firmware order by id;"

改上传上限 ​

bash
sed -i 's/^FW_MAX_UPLOAD_BYTES=.*/FW_MAX_UPLOAD_BYTES=134217728/' /opt/stacks/fw-registry/.env
docker compose up -d

下架某个版本(保留文件) ​

bash
curl -X PATCH http://127.0.0.1:8000/api/v1/firmware/3 \
  -H "X-API-Token: $ADMIN" -F yanked=true

8. 安全说明 ​

项状态
只监听 looback✅ 127.0.0.1:8000
无 Web UI✅ /、/docs、/openapi.json 全部 404
写操作鉴权✅ 常数时间比较,401 已验证
文件名清洗✅ 去除路径与非法字符,防目录穿越
扩展名白名单✅ 只允许固件后缀
上传大小限制✅ 默认 64 MB,流式校验
SHA256 完整性✅ 上传即计算,端到端验证过
容器权限✅ 普通非特权容器,mem_limit: 256m
日志轮转✅ 20 MB × 3

待办:下载鉴权默认关闭。若将来要限制"只有授权用户能下载", 把 FW_REQUIRE_DOWNLOAD_TOKEN=1 打开并给用户分发 FW_READ_TOKEN。


9. 验证记录(2026-11-08) ​

检查结果
构建✅ 镜像 tenodx/fw-registry:1.0.0
健康检查✅ healthy,/api/v1/health → {"status":"ok"}
无 token 上传✅ 401
错误 token 上传✅ 401
无 token 删除✅ 401
带 token 上传✅ 201,返回 SHA256
下载 + 校验✅ 上传/下载 SHA256 完全一致
删除 + 清文件✅ {"deleted":1,"file_purged":true}
/docs /openapi.json /✅ 均 404(确认 API-only)
Caddy 路由✅ Host: fw.localhost → health 200、list 正常
监控✅ Uptime Kuma「Firmware Registry」UP
端到端(Hub)✅ 上传 → Hub 拉取到版本/时间/更新说明 → 删除

TenoDX · 内部文档