手头要给一批 Jetson Orin Nano Super 做无人值守刷机:预置账号、跳过首次开机向导、最好还能一条命令刷多台。NVIDIA 的命令行流程默认你在 Ubuntu x86_64 主机上操作,换成 Arch 会有几个包要自己映射,另有一些参数在中文教程里传错了。这篇把在 Arch 上跑通整套流程的步骤、参数修正和批量方案记下来,最后评估一下做跨平台 GUI 的可行性。

先对齐 2026 年的现状,免得照着旧教程白折腾:

  • JetPack 7.2(L4T r39.2,Ubuntu 24.04 基础)起,官方主推 ISO 安装器 :U 盘启动、设备自己安装,做盘在 Windows / Mac / Linux 都行,SDK Manager 不再是必须。
  • SD 卡镜像从 7.2 起不再提供。7.2 还要求设备里已有 36.x 代的 UEFI/QSPI 固件,出厂固件太新老(低于 36.0)得先走一遍 JetPack 6.x 升级路径。
  • 但凡是批量、无头、要定制 rootfs 的场景,还得用命令行 l4t_initrd_flash.sh,它没被砍,参数与 6.2 基本一致。下面全部围绕这条路。

0. 先纠正几个到处流传的说法

这几条是核对官方文档和论坛时发现的,先列出来:

  1. l4t_create_default_user.sh 的 -a 不是"自动接受许可"。官方参数表写得明白,-a 是 --autologin(自动登录桌面);接受 EULA 的是 --accept-license,漏掉它脚本会停在 EULA 交互确认上等输入,CI 或无人值守环境直接卡死。
  2. massflash 产物是 mfi_<板型>.tar.gz,不是 .tbz2。网上有教程写 .tbz2,照做出不来文件。
  3. 刷 NVMe 的命令结尾参数,官方示例统一写 internal(SDK Manager 生成的就是这个)。社区有人写 external,NVIDIA 工程师在论坛里回复过 两者在该场景等价,但没必要标新立异,跟官方对齐。
  4. --massflash N 里的 N 是并发上限。实际接的设备少于 N 没事,文档原话如此,不用为了数量重新打包;超过 N 的话要么加 N,要么分批。
  5. “JetPack 7 = R38.x” 这个印象要修正:R38 是 Thor 那条线的 7.0/7.1;Orin 系列的 JetPack 7.2 其实是 R39.2,7.2.1 是 R39.2.1。

1. Arch 主机的依赖

Ubuntu 主机上 NVIDIA 提供 tools/l4t_flash_prerequisites.sh 一键装依赖(apt)。Arch 上没有对应物,需要手动装。这是按官方脚本和社区文档映射出来的一份清单:

官方仓库:

sudo pacman -S --needed libxml2 lz4 sshpass nfs-utils dosfstools \
  e2fsprogs cpio dtc binutils rsync usbutils iproute2 python-yaml vim

对应关系几个容易搞混的:

Ubuntu 包 Arch 对应 用途
qemu-user-static AUR: qemu-user-static + qemu-user-static-binfmt chroot 里跑 aarch64 二进制
binfmt-support systemd 自带的 systemd-binfmt 注册 binfmt handler
libxml2-utils libxml2(提供 xmllint) 解析 flash 配置
nfs-kernel-server nfs-utils initrd flash 的 NFS 传输
abootimg AUR: abootimg boot.img 相关处理
sshpass sshpass 刷写时通过 usb0 登录设备
device-tree-compiler dtc 设备树
xxd vim(自带 /usr/bin/xxd) tegraflash 内部工具

AUR 部分(用 yay 或 paru):

yay -S qemu-user-static qemu-user-static-binfmt abootimg

装完验证 binfmt 已经注册,否则 apply_binaries.sh 会报 Exec format error:

ls /proc/sys/fs/binfmt_misc/ | grep aarch64
# 应能看到 qemu-aarch64(-static) 字样

nfs-utils 保险起见把服务带起来(initrd flash 全程要用 NFS):

sudo systemctl enable --now nfs-server

缺命令时怎么查包:

pacman -F 缺的命令名

2. 下载与解压

从 Jetson Linux 归档页 或 JetPack 下载页取两个文件:BSP 和 Sample Root Filesystem。以 6.2 为例:

  • Jetson_Linux_R36.4.3_aarch64.tbz2
  • Tegra_Linux_Sample-Root-Filesystem_R36.4.3_aarch64.tbz2

7.2.1 对应 R39.2.1,文件名规律相同。BSP 和 rootfs 的版本号必须一致。

tar xf Jetson_Linux_R36.4.3_aarch64.tbz2
sudo tar xpf Tegra_Linux_Sample-Root-Filesystem_R36.4.3_aarch64.tbz2 -C Linux_for_Tegra/rootfs
cd Linux_for_Tegra
sudo ./apply_binaries.sh

rootfs 必须用 sudo 解压并保留权限(-p),否则后面刷出来的系统文件属主是错的。apply_binaries.sh 把 NVIDIA 驱动和用户态组件铺进 rootfs,之后再动手定制。

3. 预置账号与 rootfs 定制

这一步决定了"无人值守"能不能成立,官方说法是:系统启动时如果没有默认用户就会跑 oem-config 向导;预置了默认用户就跳过。所以全部要在生成镜像包之前完成。

3.1 创建默认用户

sudo ./tools/l4t_create_default_user.sh -u dev -p '你的密码' -n dev-01 --accept-license

参数含义(官方文档 ):

  • -u 用户名,默认 nvidia
  • -p 密码,不给则随机生成
  • -n 主机名,默认 tegra-ubuntu
  • -a 自动登录桌面(想要开机免登录再加)
  • --accept-license 接受 EULA,无人值守必加

批量场景里主机名反正会被 first-boot 服务改掉(见 3.3),这里随便给个占位即可。

3.2 chroot 定制(装包、塞文件)

需要往 rootfs 里装 apt 包或放自己的二进制(比如 Rust 写的 setup wizard、systemd 服务文件)时,用 qemu 进 chroot 操作。核心步骤:

cd Linux_for_Tegra

# 1. 把静态 qemu 放进 rootfs,binfmt 已注册就能跑 aarch64
sudo cp /usr/bin/qemu-aarch64-static rootfs/usr/bin/

# 2. 挂载必要文件系统
sudo mount -t proc proc rootfs/proc
sudo mount -t sysfs sys rootfs/sys
sudo mount --bind /dev rootfs/dev
sudo mount --bind /dev/pts rootfs/dev/pts

# 3. 让 chroot 里有 DNS(先备份原来的)
sudo mv rootfs/etc/resolv.conf rootfs/etc/resolv.conf.bak
sudo cp /etc/resolv.conf rootfs/etc/resolv.conf

# 4. 装包
sudo chroot rootfs /bin/bash -c \
  "export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y --no-install-recommends curl htop && apt-get clean && rm -rf /var/lib/apt/lists/*"

# 5. 卸载、清理、恢复
sudo umount rootfs/dev/pts rootfs/dev rootfs/sys rootfs/proc
sudo rm rootfs/usr/bin/qemu-aarch64-static
sudo mv rootfs/etc/resolv.conf.bak rootfs/etc/resolv.conf

注意事项:

  • 动作都在 apply_binaries.sh 之后做,定制完不要再跑 apply_binaries.sh,它会重新覆盖一批文件。
  • 挂载和清理最好用 trap ... EXIT 包一下,中途报错也能自动卸载,不然残留挂载点会坑到下一步的打包。
  • rootfs 里想放 overlay 文件(服务单元、二进制)直接 cp -a 进去就行,但 systemd 服务不会自动启用,要在 overlay 里自己放好 multi-user.target.wants 下的软链接。

3.3 批量设备的三件"去重"

同一个 rootfs 刷出来的设备,默认是"三胞胎":SSH 主机密钥相同、machine-id 相同、主机名相同。前两个是安全问题,第三个是运维问题。刷机前处理:

# 删掉 SSH 主机密钥,首启时重新生成
sudo rm -f rootfs/etc/ssh/ssh_host_*

# 清空 machine-id(首启时 systemd 会生成新的)
sudo truncate -s 0 rootfs/etc/machine-id

# 可选:dbus 的 machine-id 做成链接
sudo ln -sf /etc/machine-id rootfs/var/lib/dbus/machine-id

SSH 公钥直接写文件,用数字 uid/gid 设属主,不用进 chroot:

uid=$(awk -F: -v u=dev '$1==u{print $3}' rootfs/etc/passwd)
gid=$(awk -F: -v u=dev '$1==u{print $4}' rootfs/etc/passwd)
sudo install -d -m 700 rootfs/home/dev/.ssh
sudo tee -a rootfs/home/dev/.ssh/authorized_keys < ~/.ssh/id_ed25519.pub
sudo chmod 600 rootfs/home/dev/.ssh/authorized_keys
sudo chown -R "$uid:$gid" rootfs/home/dev/.ssh

主机名唯一化交给一个 first-boot 服务:首次开机时生成 SSH 主机密钥、按序列号后 6 位设置主机名(读不到序列号退到网卡 MAC,再不行用随机数)。服务单元骨架:

[Unit]
Description=Jetson first boot provisioning (host keys, hostname)
DefaultDependencies=no
After=local-fs.target
Before=sockets.target ssh.socket ssh.service network-pre.target
ConditionPathExists=!/var/lib/jetson-firstboot.done

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/jetson-firstboot.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

两个细节:

  • DefaultDependencies=no 是为了让服务在早期就跑,避免和 ssh.socket 形成依赖环,不要去掉。
  • Ubuntu 从 22.10 起 SSH 默认 socket 激活,JetPack 7(Ubuntu 24.04)用的是 ssh.socket,JetPack 6.2(22.04)还是传统的 ssh.service。Before= 里两个都写上,systemd 对不存在的单元会静默忽略,不用为版本切换改配置。

脚本里记得 ssh-keygen -A 补主机密钥,flag 文件(/var/lib/jetson-firstboot.done)在结束时 touch,保证只跑一次。

4. 进入 Recovery 模式

设备没系统或系统已坏时,这步只能手工(除非有产线夹具):

  1. 跳线法:断电,用跳线短接板上的 FC REC 与 GND 针脚,再上电。
  2. 按钮法:按住 RECOVERY 按钮不放,点一下 RESET(或重新上电)。

如果设备系统还能启动、SSH 可达,可以省掉手工操作,直接从主机让它重启进 Recovery:

ssh <user>@<设备地址> sudo reboot forced-recovery

设备会直接以 Recovery 模式重新枚举(0955:7523),接着跑刷机命令即可。这个方式适合重刷和自动化场景;全新设备或系统已损坏的,只能老实用上面的手工方式。

主机上确认设备出现:

lsusb | grep -i nvidia
# Bus 001 Device 011: ID 0955:7523 NVIDIA Corp. APX

Orin Nano 的 Recovery 模式 ID 是 0955:7523(T23x 家族的规律是 0955:7X23,AGX Orin 是 7023)。注意刷写过程中设备会以 0955:7035(initrd 模式)重新枚举,这是正常的,等待循环判断"设备进入 Recovery"时匹配 7523 即可。

5. 单台刷机

sudo ./tools/kernel_flash/l4t_initrd_flash.sh --external-device nvme0n1p1 \
  -c tools/kernel_flash/flash_l4t_t234_nvme.xml \
  -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
  --showlogs --network usb0 jetson-orin-nano-devkit-super internal

这条命令同时刷两处:QSPI 里的 bootloader(-p 传的 qspi 配置)和 NVMe 上的系统(--external-device nvme0n1p1 + nvme 分区表)。JetPack 6.2 与 7.2 的参数一致,只有目录里的版本号不同。

几个提醒:

  • 报 Invalid target board 就是板型名不对,ls *.conf 看 BSP 里实际有哪些(jetson-orin-nano-devkit、-super、以及较新版本里的 -nvme 变体)。老 BSP 没有 -super 配置,升级 BSP。
  • 官方特别强调用高质量的 USB-C 数据线,劣质线是刷机失败的一大来源。尽量直插主机 USB 口,别过便宜 hub。
  • 耗时不短,QSPI + NVMe 全套十几分钟正常,--showlogs 拉出来看进度。

6. 批量刷机:massflash

同一批设备刷同一份镜像,走 massflash 流程:先在开发机上生成一次 mfi 包,之后在任何一台 Linux 电脑上用包里的内容反复刷。

6.1 生成镜像包(只做一次)

sudo ./tools/kernel_flash/l4t_initrd_flash.sh --no-flash --network usb0 --massflash 5 \
  --external-device nvme0n1p1 \
  -c tools/kernel_flash/flash_l4t_t234_nvme.xml \
  -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" \
  jetson-orin-nano-devkit-super internal
  • --no-flash 只生成不刷机,生成阶段可以不接设备;手头有设备的话,接一台进 Recovery 在线生成也行,工具会自己读 EEPROM。
  • --massflash 5 指这个包最多同时刷 5 台,按产线并行数取。N 只是上限,后面实际刷 1 台还是 5 台都行。
  • 产物是 mfi_jetson-orin-nano-devkit-super.tar.gz,第 3 步预置的账号、定制全都在里面。
  • 完全离线生成(不接设备)时,官方示例里通过 BOARDID FAB BOARDSKU BOARDREV 环境变量提供板卡信息,替代 EEPROM 读取。

6.2 在任意刷机机上使用

刷机机不需要完整 BSP,也不用再解压 rootfs、跑 apply_binaries.sh,解压 mfi 包就够:

tar xpf mfi_jetson-orin-nano-devkit-super.tar.gz
cd mfi_jetson-orin-nano-devkit-super
sudo ./tools/kernel_flash/l4t_initrd_flash.sh --flash-only --network usb0 --massflash 5 --showlogs

把所有待刷设备进 Recovery、连好 USB,命令一跑就开始并行刷。几个来自官方文档的实用建议:

  • --keep 保留刷机环境,后续再刷时用 --reuse 复用,能明显加速。
  • 用 ionice 把刷机进程的 I/O 优先级提到最高:sudo ionice -c 1 -n 0 ./tools/...。
  • 所有待刷设备的硬件版本要一致,这是官方要求。
  • 并行刷写变慢或失败,通常是 hub 供电或带宽不够,减少同时数量或换好点的 hub。
  • 解压出来的目录里通常自带说明,R39 文档里命令形式略有差别(./l4t_initrd_flash.sh --flash-only --massflash 5 <target-board>),以包内说明为准。

7. 常见问题

卡在 Waiting for target to boot-up:多数是 usb0 网络没起来。检查主机防火墙(initrd flash 要用 NFS + SSH 走 IPv6 fc00:1:1::/48)、NetworkManager 是否接管了 usb0、USB 线/口是否可靠。重刷前把设备重新进一次 Recovery。

板型配置缺失:ls *.conf,找不到 -super 说明 BSP 版本旧,需要 6.2 及以后的 BSP。

刷完不是 MAXN SUPER 模式:确认用了 jetson-orin-nano-devkit-super 板型;另外电源模式要在设备上手动切到 MAXN SUPER(用 super 板型刷完后默认可能是 25W)。

密码含特殊字符:命令行里用单引号包起来。

重刷设备:NVMe 上旧分区会被覆盖,先备份数据。

Arch 特有:apply_binaries.sh 报 Exec format error,是 binfmt 没注册或 qemu-user-static 没装;报 command not found 就用 pacman -F 查缺哪个包。

8. 跨平台 GUI 能不能做?

结论先说:GUI 可以跨平台,刷机引擎不能。底层工具链是一堆 x86_64 Linux 二进制加 bash 脚本,全程依赖 Linux 内核的 USB 行为和时序,这不是 UI 层能封装掉的。评估三条路:

  • Windows:官方 SDK Manager 支持 Windows,但本质是自动配好 WSL2 + usbipd-win 转发 USB。官方把"不支持刷外置存储"列为已知限制,社区反馈也两极,不太稳。
  • macOS:无官方路径。有人的实测是 Apple Silicon 上跑 x86_64 虚拟机 + USB 直通失败(USB 超时),最终靠把整块 USB 控制器 PCI 直通给一台 Linux 虚拟机才成功,前提是你得有一台 x86_64 Linux 物理机。
  • Docker 也绕不过:官方有 jetson-linux-flash-x86 刷机容器,但宿主必须是 Linux,容器要 --privileged + /dev/bus/usb + 网络直通;Docker Desktop 在 Mac 上连 USB 直通都不完整。

所以想跨平台,现实架构是把"界面"和"刷机"拆开:

方案 A:跨平台 GUI 客户端 + Linux 刷机后端。 后端跑在连着 Jetson 的 Linux 机器上(可以是那台开发机),提供本地 HTTP/WebSocket 服务;GUI 负责命令编排、流式日志、进度展示。USB 检测发生在后端。适合产线上"一台刷机机 + 多台监听工位"的布局。

方案 B:GUI 只做 Linux 版。 直接在刷机机上跑,解锁后调脚本。Tauri、Electron、Qt 都能做,Electron 系可以参考官方 SDK Manager(Electron 技术栈)和 balena 的 jetson-flash(要求 x86 Linux 宿主)。

技术选型对比(针对"调外部命令 + 长日志流 + USB 感知 + 提权"这个场景):

框架 体积 调进程/日志流 USB 生态 备注
Electron 大(~85MB 起) child_process 成熟 node-usb 成熟 有先例(SDK Manager、balenaEtcher)
Tauri 小(~3MB 起) 官方 shell 插件 + sidecar 需自己用 Rust nusb/rusb 体积优势大,USB 要自己啃
Qt 中 QProcess 成熟 libusb 绑定 桌面老牌,Linux 原生体验好
Flutter 中 dart:io Process libusb FFI 小众 可行但生态不是最优
Avalonia 中 Process API 成熟 LibUsbDotNet .NET 技术栈可选

提权方面,Linux 桌面建议走 polkit/pkexec 而不是裸 sudo,刷机后端封装成 systemd 服务或独立的特权组件更干净。

另一个值得研究的成熟方案是 balena 的 jetson-flash :把工具链装进 Docker 容器(宿主只需 Docker 和 USB 直通),CLI 用"设备字典"组织设备差异(每台设备带 BSP 直链、分区映射、刷写介质),目前已支持到 L4T 39.2.0 的 Orin Nano NVMe。它的配置驱动思路、工作目录管理和进度呈现,都值得自研工具时参考。

顺带一提,NVIDIA 官方在 NVIDIA-AI-IOT/jetson-bsp-skills 仓库里放了一套给 AI agent 用的刷机技能(含 recovery 检测、EEPROM 校验、默认用户预置的完整流程),如果真要做 GUI 或自动化平台,那套流程设计值得抄。

小结

  • 单台/批量、预置账号、跳过向导,整套流程在 Arch 上完全可行,依赖映射见第 1 节。
  • 关键修正:--accept-license 必须加、mfi 包是 .tar.gz、rootdev 用 internal、N 是并发上限。
  • GUI 若要做跨平台,定位成"远程控制台":界面随便跨,刷机引擎乖乖放在 Linux x86_64 物理机上。

参考

本文流程整理自 NVIDIA 官方文档与社区实践,相关脚本只在 Arch 上做过语法级检查;第一次上机建议先单台验证参数,尤其是串行号读取和 first-boot 服务在不同 JetPack 版本上的行为。

后续:这套流程我在用 Go 桌面框架 MyGo 工具化,项目叫 nvflasher(批量刷写、rootfs 预置、代理下载这些都做进去),等能跑通了单独写一篇。