跳转至

量产设备的远程可达与出厂流程

结论先行:OTA 和远程入网是两条独立链路。OTA 只要求设备能主动出网拉取,不需要 VPN、不需要公网 IP、不需要在客户路由器上开端口; 只有「运维要够得着这台机器」才需要入网方案。把 VPN 塞进 OTA 链路是最常见的架构错误。 本页把入网选型、费用红线、金镜像容量事故、分区级救砖、出厂品牌后置合成一条可执行的产线流程。

问题背景

设备进客户现场后,网络环境完全不可控:没有公网 IP,多数落在运营商 NAT/CGNAT 后面,现场也未必有人能配合开端口。 这类设备一旦失联,只能派人跑现场,运维成本立刻失控。

同时量产阶段有几个只在批量时才暴露的问题:金镜像比新批次 eMMC 大一百多兆、改设备树把机器刷成砖、 每台机要打客户自己的开机画面。这三件事没做成流程,返工率会远高于装配环节。

一、两条链路先分开

链路 谁发起连接 前提条件 用途
状态上报 / 配置 OTA 设备主动出网拉 能访问分发地址 心跳、printer.cfg 与主题下发、固件升级
远程入网(mesh VPN) 设备加入虚拟内网 能出 443 + STUN SSH、抓日志、看屏、临时排障

判据很简单:如果一台设备永远不需要人去操作它,它就不需要入网。绝大多数量产机的日常只需要第一种。 反过来,只要有人需要 SSH 进去,就要有第二种,而且这一层必须在做控制台和 OTA 之前就位 —— 客户现场网络千奇百怪, 没有「网络永远可达」这一层,后面的控制台、批量执行、远程升级全是空谈。

推进顺序不能乱:状态总览 → 一页 dashboard → 配置 OTA(风险最低、收益最大)→ 固件 OTA → 系统 OTA。 每一步都应当独立可用,不要一次上大改造。

自建时这六层分别由什么承担

层 干什么 自建选型
① 网络层 每台机永远可达(不用公网 IP、不在客户路由器开端口) Headscale(设备无上限)或官方 Tailscale + 自建 DERP
② 接入运维 批量执行命令 / 传文件 Tailscale SSH(免密钥分发)+ Ansible(无 agent)
③ 控制台 看每台机状态、点按钮 Headscale 的 Web UI + 自己拼一页 dashboard
④ 配置 OTA(最实用) 推 printer.cfg / 触屏主题 / 宏 git 或自建 HTTP + 版本清单 manifest:每台机定时拉取 → 比对 → 应用 → 重启(留上一版可回滚)
⑤ 固件 / 系统 OTA MCU 固件 / 整机系统 固件复用出厂初始化的烧录能力;系统级 = RAUC A/B 分区 + 签名 + 回滚(要改金镜像分区表,放最后做)
⑥ 监控告警 掉线 / 异常主动通知 探活(Uptime Kuma 一类)+ 设备侧上报脚本

第 ④ 层之所以「最实用」,是因为它只动文本与资源文件,出错可以回滚到上一版,风险远低于系统级 OTA。 第 ⑤ 层的系统升级会改分区结构,一旦和「金镜像缩容」的成果冲突,返工成本极高 —— 所以顺序上必须排在最后。

二、批量入网:三种做法,只有两种能上量产

做法 机器侧行为 机器上有长期密钥吗 产线要动手吗 适合量产
① 出厂注入一次性 key 首启服务读 key 文件 → 入网 → 成功后自删 只在「出厂到首启」这个窗口存在 要(主机侧一条命令) 可以,够简单
② 板上自取(通网即连) 首启服务向内网签发端点要 key → 入网 → 用完即弃 零长期密钥(OAuth secret 只在签发端点上) 不用 上百台首选
③ 镜像里塞 reusable key 直接用镜像里的 在镜像里,永久 不用 绝对不行

第 ③ 种不是「不安全一点」,而是根本性错误:镜像流到客户手里,等于把整个内网的入场券一起交出去, 任何拿到镜像的人都能入网。上游文档本身就把 reusable key 标成「很危险,被偷了很糟糕」。

第 ② 种之所以是量产正解,是因为它把「谁有资格发 key」这个判断留在了你的服务端: 设备上没有任何东西可以被拷走,产线也不需要为每台机执行一条命令 —— 一台机刷完、联网、自己入网,流程上零人工。 缺点是依赖一个始终在线的签发端点,这个端点挂了,新机就入不了网(但已入网的老机不受影响)。

官方对第 ② 种的推荐写法是 OAuth client + auth_keys scope:

tailscale up \
  --auth-key='<OAuth secret>?ephemeral=false&preauthorized=true' \
  --advertise-tags=tag:prod \
  --hostname=<机型>-<序号>

后台一次性准备:建 tag:prod 与 ACL(只允许自己的设备互相访问)→ 在控制台建 OAuth client (scope 给 auth_keys 写权限、tag 绑 tag:prod)→ secret 只放运维主机(keychain 或环境变量), 不进镜像、不进 git、不进部署脚本的明文日志。

镜像侧只需要两样:tailscale 二进制 + 一个 enroll 服务。服务本体用 ConditionPathExists 盯一个 key 文件, 没有 key 就什么都不做 —— 这样同一个镜像既能用于「一次性注入」的批次,也能用于「板上自取」的批次。

三个必备件,缺一个就连不上

  1. 等网络 + 重试:After=network-online.target,Restart=on-failure + RestartSec=120,脚本内部再做退避。 客户现场首启常常没网,服务必须能在「联网之后自己连上」,并且已入网则跳过(幂等,不能每次开机都重新注册一台)。
  2. 出网端口只要 443 + STUN。客户网络抽风时靠 DERP 中继兜底,不要指望 UDP 直连永远成功。
  3. 命名唯一:--hostname=<机型>-<序号>,序号由量产脚本传入。否则后台会堆出一排同名节点, 分不清哪台是哪台 —— 这和镜像内主机名重复是同一类问题,只是发生在网络层。

三条缺一个都会「连不上」,且失败形态各不相同,可以按现象反查:

现象 缺的是哪一条 处置
节点一直不出现 首启无网,服务退出且不重试 补 network-online 依赖 + 重试与退避
出现一排同名节点 没传 hostname 序号 量产脚本必须带序号参数
入网了但连不通 / 看不到 ACL 未放行或 tag 不匹配 核对 tag:prod 的 ACL 与签发时绑的 tag
每次开机后台都多一台 入网判断非幂等 先检测状态,已入网直接退出
直连很慢、时通时断 只靠默认境外中继 自建 DERP,或配 OmitDefaultRegions

三、开源边界与费用:选型先看这两张表

组件 开源情况 结论
客户端 tailscaled / CLI 全开源(BSD-3) 任意 Linux/ARM 可自编译;支持 --tun=userspace-networking(内核无 tun 也能跑);有 tsnet Go 库可把 mesh 嵌进自己的程序
DERP 中继 可自建(go install tailscale.com/cmd/derper@latest,官方标 alpha) 默认中继都在境外,自建(国内 VPS)能明显降时延;可配 OmitDefaultRegions 只走自家中继;需要域名 + 443/80 + STUN + ICMP,不能躲在 NAT 或负载均衡后面
控制面 官方闭源(closed source coordination server) 要自建控制面只能用 Headscale

Headscale 的能力边界要提前确认,别以为「自建 = 全都有」:

  • 有:MagicDNS、Taildrop、Tags、子网路由、出口节点、ACL、自动批准、Tailscale SSH、内嵌 DERP、OIDC 注册。
  • 没有:Funnel;OIDC 组不能用于 ACL;企业功能(审批后台、MDM、SSO 管理)。

费用上有一条最实际的红线:个人免费版对 user devices 是无限(≤6 用户、≤3 组), 但 tagged resources 只免费 50 个,超出按量计费。而量产机队几乎必然要用 tag:prod 做 ACL 隔离 —— 所以超过 50 台就要花钱,不 tag 才是免费无限。选型结论:

规模 方案
≤50 台 官方免费版,省事
上百台 官方付费,或 Headscale + 自建 DERP(设备无上限、零月费、数据自控)

若要自建,按阶段推进,每一阶段都能独立用起来:

阶段 做什么
0 用一台常开的 Mac 当「服务器」,官方免费 tailnet 先接 3~5 台验证流程
1 一台轻量 VPS + 域名 → Headscale + 签发端点 + dashboard + OTA 分发
2 国内节点做自建 DERP,压时延

VPS 选香港/新加坡轻量:免备案、国内时延 30–60 ms。国内轻量最快但域名必须备案(1~2 周),会拖住整个排期。 规格 2 vCPU / 2 GB / 40 GB SSD / Debian 12 足够 —— 控制面只走信令(几十 KB 级),只有做 DERP 才吃带宽。

一个国内特有的坑:临时匿名隧道(trycloudflare.com 这类)在国内不可用 —— 公共 DNS 有记录或直接 NXDOMAIN, 板子本地解析也失败。量产只能走具名隧道或自建,不要把它当应急通道写进流程。

四、金镜像 eMMC 缩容:99% 处 Write LBA failed 的真因

这是量产时最容易被误判成「线材/接口接触不良」的一类故障。实际连卡两轮,根因是新板 eMMC 比源板小 118 MiB, 镜像尾部的 rootfs 装不下。

现象 数据
两轮烧录都停在 99% 两份 8.3 MB、238,568 行进度点的日志,错误同一句 Write LBA failed!
金镜像体积 15,758,000,128 B(14.676 GiB)
99% 失败点 15,600,420,126 B,剩余未写入 ≈ 150.3 MiB
rootfs 分区 LBA 2957312 → 30777310,结束于 15,757,983,232 B
新板 eMMC(SAMSUNG) 30,535,680 扇区 = 15,634,268,160 B
超出量 ≈ 118 MiB(源板 ~15028 MiB vs 新板 ~14910 MiB)

判据:两端都停在同一个百分比、同一个错误 ⇒ 确定性问题(容量边界),不是概率性硬件故障。 概率故障的表现是每次停在不同位置、错误码会变。

不要直接 truncate 截断:末尾 8 MB 里有 806 万个非零字节,一截 rootfs 立刻损坏,机器能烧进去但起不来。

能不能缩,先读 ext4 超级块,看已用量:

项 值
rootfs 文件系统 13.266 GiB
实际已用 仅 4.77 GiB
空闲 8.38 GiB

余量极大 ⇒ 缩到 12 GiB 就能让整盘落进 15.63 GB 以内。全程在副本上操作,原件不动:

E=/opt/homebrew/opt/e2fsprogs/sbin   # 主机侧 e2fsprogs

# ① 按偏移提取 rootfs 分区(偏移 1,514,143,744 / 长度 14,243,839,488),约 32.7 s
# ② 修复:运行时 dd 出来的镜像必然有脏计数/checksum,属正常
$E/e2fsck -f -y rootfs-shrink.img

# ③ 缩容 + 截断
$E/resize2fs rootfs-shrink.img 12G
truncate -s 12G rootfs-shrink.img

# ④ 只读复核
$E/e2fsck -f -n rootfs-shrink.img     # 期望 clean、零错误;已用 4.87 GiB

# ⑤ 重拼整盘镜像 + 修正 GPT 分区表(重算 CRC32)→ 精确 15,634,268,160 B

五项校验全过才允许烧:

  1. 新镜像字节数正好等于新板 eMMC 全容量(不是「小于」,要相等);
  2. GPT primary + backup 两份分区表逐字节一致,CRC32 全对;
  3. 9 个分区全部落在可用区内;
  4. rootfs clean;
  5. 原始完整镜像完好未动(有 md5 可比)。

结果:得到 15,634,268,160 B 的镜像,烧录 0% → 100% 零 Write LBA failed,耗时 15 分 50 秒; 收尾 Reset Device OK,随后 USB 设备名从 Maskrom 阶段的通用名变成 SoC 的 rk3xxx 名 ⇒ 系统已从 eMMC 正常引导。

烧录侧的三条硬教训

  1. 下载 loader 只能做一次:板子进入 Loader 模式后再发一次 db 会直接挂死(Download boot failed!), 之后连读信息、重启的命令都无响应,只能断电重进 Maskrom。 烧录脚本必须一把连续跑「下 loader → 写 → 重启」,中间不插任何探测命令。
  2. 板子 USB 直插主机机身口。走两级 hub 的扩展坞时 Maskrom 阶段不稳定; 注意 LocationID 变化只代表地址变了,不代表真的换了物理口。
  3. eMMC 供应商与批次会变 ⇒ 每批板先读一次真实容量再烧,不要假设与金样机同容量。这条是本节的根因预防。

五、DTB 改错刷砖后的分区级恢复

设备树里触摸控制器的 irq-gpios 是三格属性:0x45000000 / 0 / 0x08000000,依次是 gpio、pin、flags。 flags 写成 8 时,INT 引脚带上偏置且被配成电平触发 ⇒ 中断线持续有效 ⇒ 永久误触发, 实测 719–722 次/秒,启动日志里有 Failed get int-trigger-type from dts, set default 这条线索。

一族板子上救过,于是在同型号另一批机子上照搬「8 改成 0」⇒ 直接卡在第一屏,ARP 都不回。 教训是硬的:同 SoC、同型号、不同批次,DTB 不能整套照搬,pinconf 的偏置方向与板级走线是绑定的。

恢复不需要全盘重烧。整盘 15.6 GB 重烧约 40 分钟,而且会丢掉现场配置与校准;只还原承载 DTB 的那个分区就够:

步骤 命令 结果
读分区表 rkdeveloptool ppt boot 分区 = LBA 0x8000 = mmcblk0p3(64 MB,DTB 就在里面)
进 MASKROM 按住板上 MASKROM 键(或短接 eMMC CLK 测试点)+ USB 接主机,2–3 秒后松手 loader 就绪
下 loader rkdeveloptool db <loader> 成功
只写回受损分区 rkdeveloptool wl 0x8000 p3_backup.img 100%
重启 rkdeveloptool rd 3 秒后 ping 通

实测全程 2 分钟,系统、printer.cfg、标定值全部保留。

支撑这条流程的是备份纪律,它比恢复命令本身更重要:动 DTB 之前先把 p3(64 MB)整块 dump 出来,记 md5, 板上与运维主机各存一份并交叉校验。有了这个 64 MB 文件,「砖」就从事故降级成一次两分钟内可控的实验; 没有它,同样的动作就是一次需要返厂的故障。

两个读 DTB 时的实操细节:

  • 三格必须一起解码。有人事后用 awk 取错列(取到中间的 gpio number),据此得出「flag 已经是 0、板上没留改动」的 错误结论 —— 单看一格一定会读错。
  • 恢复后要核回三格原值,不是核「中间那格变成什么」。

当前口径:生产机上不再试 DTB。 触摸风暴白烧约 10% CPU、把 load 抬到 6 上下,但打印本身不受影响, 不在紧急路径上。要治就两条路:在备用板上验 flags=16(另一种偏置)这个候选值,或找主板方案商要原始 DTS 源码重编。 此外,「换更轻的 rootfs / 更轻的发行版能治触摸」是错的 —— DTB 与内核驱动原样保留,中断速率会一模一样地继续。

六、出厂品牌后置:镜像通用 + 每台机打标

镜像保持出厂态不动,客户的品牌与界面在每台机上做。好处是镜像通用、一条流水线能吃多个客户; 风险是「某台机网络不通就漏做」,所以必须配巡检。

标准流程:

烧机(金镜像)→ 重启 → 自动连产线 WiFi 取 DHCP → 扫网定位 → 
  ① SSH 替换开机画面(U-Boot 与 kernel 两份)
  ② 装触屏主题(默认主题指向客户方案)
  ③ 初始化 MCU(烧 bootloader + 写 [mcu] serial + 按机型套 printer.cfg)
  → 板上先备份原件 → reboot → 回网后回读 md5 校验 → 记日志

自动化侧用定时任务每 10 分钟扫一遍网段,新板一上线就处理;日志按行追加(时间戳 + 地址 + 动作 + md5 + 结果)。 只有真的改了板子才推一条通知,跳过的不推 —— 否则巡检消息会淹没真正需要人处理的异常。

开机画面的位置与格式

项 值
载体 GPT 第 6 分区 logo_rk(vfat,板内挂 /mnt/logo)
文件 logo.bmp + logo_kernel.bmp,U-Boot 与 kernel 各读一份,内容必须一致
格式 1280×800 / 24-bit BMP / 3,072,054 B
关键约束 同画幅、同字节数替换 ⇒ 不动 FAT、不动分区表,风险最低
生成 源图去白边 → 等比缩到内容高 620 px → 居中贴到 1280×800 纯白画布
回滚 镜像侧留两原文件 + 整分区 68 MiB;板内 /mnt/logo/provision_orig/ 留原件

画幅由板内 U-Boot 日期决定:较新的 U-Boot 会多转 90°,要用 1280×800;较老的一批要用 800×1280。 写死一个方向,另一批机出来的画面就是躺倒的。

⚠️ 查现役画面不能用「搜 BMP 签名」:FAT 的空闲簇里躺着历史版本,按签名扫会扫到副本上, 改完一重启又变回去。必须按 FAT 根目录项 + 簇链回读真正的现役文件。

五条安全守卫(防误伤在产机器)

  1. 只认指定 MAC 前缀的板(或「特定端口开着且型号匹配」),不碰任何远程/内网节点。 扫描范围限定在产线网段,这条是防止把客户在跑的机器当成新板。
  2. 正在打印绝不重启:读到打印状态为 printing/paused 或任务活跃 → 跳过并交回队列重排。
  3. 现役画面不在出厂白名单 → 拒绝。说明这台机被人改过,不是新板,交人工判断。
  4. 已是目标画面 → 跳过(幂等)。重复扫不会反复重启。
  5. 改完必须回读校验:重启回网后重算 md5,与写入值比对,一致才算完成。

地址冲突这件小事

有一次板子从 DHCP 拿到一个与现场 AP 自身相同的地址,两边互相抢答,SSH 与 Moonraker 都打不通, 看起来像「板子没起来」。根治办法二选一:把该地址从上游 DHCP 池排除,或量产时给每台机固定分配高位段的地址。 不要靠「拔了再插」,那是把确定性配置问题当成偶发故障。

七、可复用的三条铁律

  1. 镜像出厂态 + 每台机后置品牌化:镜像通用、客户隔离,代价是必须有巡检兜住「漏做的那台」。
  2. 改动前先备份、改完回读校验 + 重启复验,并把「已是目标态 → 跳过」做成幂等。
  3. 品牌只落在开机画面这一帧。Web 前端与触屏 UI 里没有客户品牌,不要为了「看起来更定制」去改前端 —— 那是把一次性打标变成长期维护负担。

相关阅读