Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- 'scripts/verify-pages-releases.py'
- '.github/workflows/pages.yml'
workflow_run:
workflows: ['NexaWrt AX9000 reproducible RAM-test release']
workflows: ['NexaWrt AX9000 reproducible RAM-test release', 'NexaWrt x86_64 VM release']
types: [completed]
schedule:
- cron: '17 */6 * * *'
Expand Down
483 changes: 483 additions & 0 deletions .github/workflows/vm-release.yml

Large diffs are not rendered by default.

36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,23 @@ SHA-256 锁定的 OpenWrt ImageBuilder,检查启动、SSH、LuCI HTTP、`ubus`
OpenWrt 用户空间和自动化流程可运行,不能证明 AX9000、Qualcomm NSS、交换芯片、Wi-Fi、温度、
断电恢复或持久存储安全。**

在浏览器中打开 **Actions → VM smoke (QEMU only) → Run workflow** 即可重新运行;Pull Request 修改
VM 相关文件时也会自动执行。测试阶段的 AX9000 候选则通过轻量 tag 发布为 GitHub prerelease:
VM 有两条彼此隔离的路径:

- **VM smoke (QEMU only)**:覆盖 `x86-64` 与 `armsr-armv8`,注入一次性 CI SSH 公钥,只用于仓库自动化冒烟检查,不发布给用户。
- **NexaWrt x86_64 VM release**:只构建 `x86-64` 用户发行镜像,不注入任何 SSH 公钥、默认禁用 Dropbear。工作流对即将发布的精确 `.img.gz` 启动 QEMU,验证启动存活、LuCI、串口 VM-only 标签、Dropbear 已禁用且未运行、`authorized_keys` 缺失,以及转发到 guest 22 的随机主机端口没有 SSH 服务;通过后发布独立的 `vm-x86_64-vX.Y.Z-rc.N` prerelease。

在浏览器中打开 **Actions → NexaWrt x86_64 VM release → Run workflow**,必须选择 `main` 并填写例如
`vm-x86_64-v0.1.0-rc.1`。预检从 GitHub 远程读取当时的 `main` 精确 commit SHA,要求 dispatch 的
`GITHUB_SHA` 与之完全一致,再在该 SHA 创建轻量 tag;构建、证明和发布阶段都继续固定并复验同一个 SHA,
不是“任意 `main` 祖先”即可发布。VM Release 精确包含 9 个资产:镜像、镜像摘要、manifest、安全标签、
使用说明、QEMU 报告、`SHA256SUMS` 和两份 provenance。

Pages 只有在 immutable prerelease、精确 9 资产、`artifact-labels.env` 安全合同、精确 23 字段的
`smoke-report.txt` 合同、镜像与 `SHA256SUMS` attestation,以及每个资产的 Release ID/name/size/SHA-256
proof 全部匹配后,才会在独立的 x86_64 VM 区域提供下载。任一项失败只隐藏 VM 条目,不会把 VM PASS
升级成 AX9000 可刷写或生产结论。完整规则和使用说明见 [VM x86_64 文档](docs/VM-X86_64.md)。

测试阶段的 AX9000 候选仍通过轻量 tag 发布为 GitHub prerelease:

```sh
tag=ram-test-nss-v0.1.0-rc.1
Expand Down Expand Up @@ -294,8 +309,16 @@ AX9000 initramfs firmware 与 CycloneDX SBOM。随后四个 provenance bundle
匹配 Release ID 的 proof 才会把下载项写入页面索引。因此,即使有人手工创建名称和六资产外观都相同的
immutable/prerelease lookalike,只要缺少上述可信来源与摘要证明,也会从网站中排除。

x86_64 VM 下载区使用独立门禁:候选必须是 immutable prerelease,并具有精确 9 个资产;验证器复验
精确 15 字段的 `artifact-labels.env` 和精确 23 字段的 `smoke-report.txt`,其中 QEMU 报告必须绑定
实际发布的镜像文件名,并证明 `qemu_boot=PASS`、LuCI HTTP、VM-only 串口标签、Dropbear disabled/未运行、
`authorized_keys` 缺失以及 guest 22 转发端口无 SSH 服务。镜像和 `SHA256SUMS` 必须具有受信任工作流
attestation;proof 还要为全部 9 个资产绑定同一 Release 的 asset ID、name、size 与实际 SHA-256,
Pages 生成器再与 GitHub Release API 数据逐项匹配。任一 VM 证据不符,VM 下载即 fail-closed;AX9000
和 VM 两个区域相互隔离,某一区域无效不会自动禁用另一区域。

站点会在 `main` 更新、发布工作流成功后以及每 6 小时周期复验并重新部署。Official 与 NSS 分频道,
Release 尚不存在或证明失败时页面会明确显示不可下载。站点从 schema-v2 目录生成浏览器云编译、恢复与
Release 尚不存在或证明失败时页面会明确显示不可下载。站点从 schema-v3 目录分别生成 AX9000 RAM-test 与 x86_64 VM 下载区;AX9000 区继续提供浏览器云编译、恢复与
测试文档的固定链接,并提供只生成易失性 RAM 会话 UCI 配置片段的生成器。前端对异常设备元数据、异常
URL、历史顺序、重复 tag、`latest` 不一致或非 RAM-only 状态全部 fail-closed。**当前目录仍仅支持
Xiaomi AX9000 的 RAM-only 候选,硬件状态为未验证,绝非生产可用或可刷写固件。**网站不是刷机工具,
Expand Down Expand Up @@ -323,12 +346,13 @@ scripts/backup-router.sh 只读备份
scripts/nss-diagnostics.sh NSS/ECM 只读运行时诊断
scripts/prepare.sh 获取、锁定并校验上游
scripts/build.sh Linux 干净构建
scripts/build-vm-image.sh 构建 x86_64/ARM64 VM-only 测试镜像
scripts/test-vm-smoke.sh QEMU 启动、网络、SSH 与 LuCI 冒烟测试
scripts/build-vm-image.sh 构建 VM smoke 或 x86_64 用户发行镜像
scripts/test-vm-smoke.sh CI 专用 QEMU 启动、网络、SSH 与 LuCI 冒烟测试
scripts/test-vm-release.sh 对无注入密钥的精确 x86_64 Release 镜像做 QEMU/LuCI 检查
devices/xiaomi-ax9000/device.json AX9000 RAM-only 设备目录单一事实来源
scripts/device_metadata.py 严格设备目录与构建请求校验器
scripts/verify-pages-releases.py 验证 Release 来源、资产、摘要与 provenance 并生成 proof manifest
scripts/generate-pages-data.py 生成 schema-v2 严格白名单 Pages Release 索引
scripts/generate-pages-data.py 生成 schema-v3 AX9000/VM 严格白名单 Pages Release 索引
site/ GitHub Pages 下载与安全配置站点
scripts/check-kernel-build-identity.sh Kconfig 构建身份与带产品前缀的 source-lock revision 门禁
tests/test_openwrt_defconfig_version.sh 锁定 OpenWrt Kconfig defconfig 保留测试
Expand Down
48 changes: 48 additions & 0 deletions docs/RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,51 @@ If and only if the release is still a draft for the exact triggering tag:
If the workflow implementation itself must change, the failed tag still identifies the old workflow commit and must remain immutable evidence. Do not move or recreate the failed tag. Merge the fix to `main`, delete only the draft release, and create the next release-candidate tag (for example, `rc.2`) from the corrected `main` commit.

If the release is already published (`isDraft=false` or `publishedAt` is set), stop. Do not delete or overwrite it as routine draft recovery. Investigate the final-state verification failure and treat any correction as an explicit release-management incident; normally a new release-candidate tag is required.

## x86_64 VM candidate releases

The x86_64 virtual-machine release channel is independent from AX9000 RAM-test releases. A VM candidate tag must match exactly:

```text
vm-x86_64-vMAJOR.MINOR.PATCH-rc.N
```

It is a prerelease for QEMU/UTM/PVE-style virtual machines only. It is not AX9000 firmware and does not assert hardware, NSS, Wi-Fi, NAND/UBI, recovery, or flash validation.

### Browser release procedure

1. Merge all VM release changes to trusted `main` and ensure repository policy checks pass.
2. Open **Actions → NexaWrt x86_64 VM release → Run workflow**.
3. Select the `main` branch and enter the complete tag, for example `vm-x86_64-v0.1.0-rc.1`.
4. The preflight reads the current remote `main` commit SHA from GitHub, requires the browser dispatch `GITHUB_SHA` to equal that exact SHA, rejects an existing tag or release, and creates the lightweight tag at that SHA. Build, attestation, draft creation, and final publication continue to check out and reverify the same pinned SHA and remote tag. A historical `main` ancestor is not sufficient for a new browser-triggered release.
5. The workflow builds the exact no-key release image and boots that same compressed image in QEMU. It requires QEMU to remain alive, LuCI HTTP to return an accepted result, the serial VM-only labels to appear, runtime evidence that Dropbear is disabled and not running and `authorized_keys` is absent, and a separate probe showing that the host port forwarded to guest port 22 exposes no SSH service.
6. Only after those checks pass does the workflow attest the image and `SHA256SUMS`, create and recheck the exact draft assets, recheck the pinned source/tag and repository Immutable Releases setting immediately before publication, and publish the result as an immutable prerelease.

A successful VM release contains exactly these nine assets:

```text
NexaWrt-x86_64-vX.Y.Z-rc.N-generic-ext4-combined.img.gz
NexaWrt-x86_64-vX.Y.Z-rc.N-generic-ext4-combined.img.gz.sha256
NexaWrt-x86_64-vX.Y.Z-rc.N-generic-ext4-combined.manifest
artifact-labels.env
README-VM.txt
smoke-report.txt
SHA256SUMS
image.provenance.bundle.json
checksums.provenance.bundle.json
```

The Pages verifier accepts a VM release only when all of the following hold:

- it is an immutable, non-draft prerelease whose lightweight tag resolves to a commit in the trusted `main` history;
- its uploaded asset set is exactly the nine names above, with no additions, omissions, duplicates, invalid sizes, or AX9000-labelled names;
- the external image checksum and `SHA256SUMS` match the downloaded assets exactly;
- `artifact-labels.env` has the exact 15-key release safety contract;
- `smoke-report.txt` has exactly these 23 keys: `status`, `target`, `image`, `vm_only`, `not_ax9000_firmware`, `hardware_validation`, `nss_validation`, `exact_release_image`, `qemu_boot`, `serial_labels`, `http`, `ssh_runtime_evidence`, `ssh_port_probe`, `ssh`, `authorized_keys`, `dropbear_enabled`, `dropbear_running`, `http_status`, `auth_challenge`, `http_host_port`, `ssh_host_port`, `serial_log`, and `ssh_probe_log`;
- those 23 fields bind the exact published image and report PASS/expected values for QEMU boot, LuCI HTTP, VM-only serial labels, Dropbear disabled and not running, absent `authorized_keys`, and no SSH service on the host port forwarded to guest port 22;
- the image and `SHA256SUMS` provenance bundles verify against `tifycloud/NexaWrt/.github/workflows/vm-release.yml`, the release source digest, and a GitHub-hosted runner;
- the generated proof binds every one of the nine assets by asset ID, name, size, and downloaded SHA-256; the Pages generator then requires those identities to match the same GitHub Release API response (and its trusted `digest` when provided).

If any VM condition fails, the site hides that VM candidate. This VM fail-closed path is isolated from a separately valid AX9000 catalog, and the reverse is also true. A VM PASS is evidence only for the exact x86_64 virtual-machine image and release pipeline; it is not permission to flash AX9000 and is not an AX9000 production-readiness claim.

Do not move a VM release tag, replace assets, or manually repair a published immutable release. Merge the correction and use the next RC tag. See [`docs/VM-X86_64.md`](VM-X86_64.md) for download and QEMU instructions.
218 changes: 218 additions & 0 deletions docs/VM-X86_64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# NexaWrt x86_64 虚拟机发行版

NexaWrt x86_64 VM 是供用户在本地虚拟机中体验 NexaWrt Web、LuCI、软件包和基础网络的发行镜像。

> **硬边界:VM PASS 不等于 AX9000 可刷,也不等于 AX9000 生产可用。** 这个镜像只适用于 x86_64 虚拟机;它不是 Xiaomi AX9000 固件,不能上传到 AX9000 LuCI,不能用于 `sysupgrade`、`mtd`、UBI、NAND 或任何路由器刷写流程。QEMU 通过只说明“被测试的精确 x86_64 镜像”满足下文的虚拟机合同,不证明 AX9000 的内核启动、DTS、Wi-Fi、交换芯片、NSS、闪存布局、断电恢复或救砖路径。

## 浏览器发布与源提交绑定

VM 发行版 tag 必须使用:

```text
vm-x86_64-vX.Y.Z-rc.N
```

在 GitHub 中打开 **Actions → NexaWrt x86_64 VM release → Run workflow**,选择 `main` 并填写完整 tag,例如 `vm-x86_64-v0.1.0-rc.1`。

浏览器触发不是对“某个曾经属于 `main` 的提交”发布。预检会:

1. 从 GitHub 远程读取触发时当前 `main` 的精确 commit SHA;
2. 要求 dispatch 的 `GITHUB_REF=refs/heads/main`,且 `GITHUB_SHA` 与该 SHA 完全一致;
3. 拒绝已存在的同名 tag 或 Release;
4. 在该精确 SHA 创建轻量 tag;
5. 将该 SHA 作为 `expected_source_sha` 传入后续作业。

构建、attestation、草稿资产检查和公开发布前都会继续固定并复验同一 SHA、本地 tag 与 GitHub 远程 tag。公开前还会再次确认仓库 Immutable Releases 已启用。任一身份或不可变设置检查失败,流程都会 fail-closed,不公开 Release。

## QEMU 实际验证范围

发布工作流不是只检查“能否解压”或启动另一份临时镜像。它把即将发布的精确 `.img.gz` 解压后作为 QEMU 磁盘,并要求以下条件全部成立:

- QEMU 在检查完成并生成 PASS 报告前保持运行,最终记录 `qemu_boot=PASS`;
- 串口出现 VM-only、非 AX9000、无硬件/NSS 验证等发行安全标签;
- LuCI HTTP 可达,并得到合同允许的响应:`200`,或带 LuCI 登录挑战的 `403`;
- 串口运行时证据明确报告 Dropbear 已禁用、未运行,且 `/etc/dropbear/authorized_keys` 不存在或为空;
- QEMU 将随机主机端口转发到 guest TCP 22,单独探测该端口;只要收到 SSH banner、SSH 协议响应或其他服务数据,就判定失败;
- 报告中的镜像文件名必须与 Release 中的镜像资产名称完全一致。

因此,发行镜像默认不注入 CI SSH 公钥,Dropbear 默认关闭;测试证明的是“本次精确镜像在本次 QEMU 启动中的观测结果”,不是对所有未来配置或用户手动启用 SSH 后状态的保证。

## Pages 展示门禁

成功的 VM Release 必须精确包含以下 9 个资产:

```text
NexaWrt-x86_64-vX.Y.Z-rc.N-generic-ext4-combined.img.gz
NexaWrt-x86_64-vX.Y.Z-rc.N-generic-ext4-combined.img.gz.sha256
NexaWrt-x86_64-vX.Y.Z-rc.N-generic-ext4-combined.manifest
artifact-labels.env
README-VM.txt
smoke-report.txt
SHA256SUMS
image.provenance.bundle.json
checksums.provenance.bundle.json
```

网站不会因为 Release “看起来像成功”就提供下载。Pages 验证器只接受 immutable、非 draft 的 prerelease,并要求 tag 属于受信任 `main` 历史、9 个资产名称集合精确、状态与大小有效、外部镜像摘要和 `SHA256SUMS` 内容完全匹配。

`artifact-labels.env` 必须具有精确 15 字段合同,并包含与当前 tag/version 一致的发行身份、`TARGET=x86-64`、`MODE=release`、VM-only/非 AX9000 边界、硬件与 NSS 未验证边界、可信 OpenWrt ImageBuilder URL/SHA-256,以及 SSH 默认关闭/无授权密钥标记。

`smoke-report.txt` 必须恰好具有以下 23 个字段,不能缺少、增加或重复:

```text
status
target
image
vm_only
not_ax9000_firmware
hardware_validation
nss_validation
exact_release_image
qemu_boot
serial_labels
http
ssh_runtime_evidence
ssh_port_probe
ssh
authorized_keys
dropbear_enabled
dropbear_running
http_status
auth_challenge
http_host_port
ssh_host_port
serial_log
ssh_probe_log
```

其中必须精确证明:`status=PASS`、`target=x86-64`、测试的是发布镜像、`qemu_boot=PASS`、串口标签与 LuCI 通过、SSH 运行时证据通过、guest 22 转发端口探测通过、`ssh=DISABLED_BY_DEFAULT`、`authorized_keys=ABSENT`、`dropbear_enabled=NO`、`dropbear_running=NO`;HTTP/SSH 主机端口必须是不同的有效非特权端口,日志路径也必须等于工作流约定路径。

此外:

- `image.provenance.bundle.json` 必须证明发行镜像;
- `checksums.provenance.bundle.json` 必须证明 `SHA256SUMS`;
- 两份 attestation 必须绑定本仓库的 `.github/workflows/vm-release.yml`、相应 source digest 和 GitHub-hosted runner;
- proof manifest 必须为全部 9 个资产分别记录 Release asset ID、name、size 和实际下载内容的 SHA-256;
- Pages 生成器必须再把这些身份与同一 GitHub Release API 响应逐项匹配;API 提供可信 `digest` 时也必须一致。

只有上述门禁全部通过,网站才展示对应 VM 下载。任何一项失败都会隐藏该 VM 候选;VM 与 AX9000 下载区双向隔离,一边的无效数据不会自动把另一边判成有效或无效。

## 下载后校验

镜像资产名称示例:

```text
NexaWrt-x86_64-v0.1.0-rc.1-generic-ext4-combined.img.gz
```

下载镜像、单文件摘要和 `SHA256SUMS` 后执行:

```sh
sha256sum -c NexaWrt-x86_64-v0.1.0-rc.1-generic-ext4-combined.img.gz.sha256
sha256sum -c SHA256SUMS
```

如果校验失败,不要启动镜像。

## 解压与 QEMU 启动

```sh
gzip -dk NexaWrt-x86_64-v0.1.0-rc.1-generic-ext4-combined.img.gz
```

解压后得到 raw 磁盘镜像:

```text
NexaWrt-x86_64-v0.1.0-rc.1-generic-ext4-combined.img
```

示例启动命令:

```sh
qemu-system-x86_64 \
-m 512 \
-smp 2 \
-display none \
-monitor none \
-serial stdio \
-machine q35,accel=tcg \
-drive file=NexaWrt-x86_64-v0.1.0-rc.1-generic-ext4-combined.img,format=raw,if=ide \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:8080-:80 \
-device e1000,netdev=net0
```

打开 LuCI:

```text
http://127.0.0.1:8080/cgi-bin/luci/
```

512 MiB 是当前自动化和示例使用的 QEMU 内存配置,不是 AX9000 的内存需求结论。

## 首次登录和 SSH

Remote SSH is disabled by default。公开发行版默认不注入 CI SSH 公钥,并默认停止、禁用 Dropbear。推荐流程:

1. 从 QEMU 串口控制台进入系统;
2. 执行 `passwd` 设置 root 密码;
3. 如确实需要 SSH,再手动启用:

```sh
/etc/init.d/dropbear enable
/etc/init.d/dropbear start
```

不要在未设置密码和访问控制前把虚拟机桥接到真实局域网。用户手动启用 SSH 后,运行状态当然不再等于 Release 自动测试时的“默认关闭”状态。

## 内置安全标记与命令保护

镜像内置以下标记:

```text
ARTIFACT_CLASS=VM_DISTRIBUTION_IMAGE
VM_ONLY=1
NOT_AX9000_FIRMWARE=1
HARDWARE_VALIDATION=0
NSS_VALIDATION=0
VALIDATION_SCOPE=QEMU_BOOT_AND_USERSPACE_ONLY
SSH_DEFAULT=disabled
SSH_AUTHORIZED_KEYS=absent
```

并拦截以下危险写入命令:

```text
factoryreset
firstboot
jffs2mark
jffs2reset
mtd
sysupgrade
ubiattach
ubidetach
ubiformat
```

这些保护用于降低把虚拟机教程误用于真实路由器的风险,不能替代 AX9000 的启动、刷写和恢复验证。

## 与 AX9000 验证的关系

x86_64 VM PASS 可以说明:

- 被发布的精确 x86_64 镜像能在当前 QEMU 配置中完成规定检查;
- LuCI HTTP、基础用户空间和发行校验链路在该环境中可工作;
- 默认 SSH 关闭状态满足本次运行时合同。

它不能说明:

- AX9000/IPQ807x 内核能启动;
- AX9000 DTS 正确;
- 有线交换芯片端口映射正确;
- 2.4G/5G/5.8G Wi-Fi 正常;
- NSS 硬件加速正常;
- NAND/UBI/MTD 布局兼容;
- `sysupgrade` 或其他刷写安全;
- 断电、回滚、串口和恢复路径可靠;
- 已达到 AX9000 生产级使用标准。

因此,VM PASS 最多是 AX9000 后续静态检查和真机验证前的一项辅助证据,不能直接等同于“可以安全刷机”,不能跳过 AX9000 真机启动与恢复验证,也不能据此发布“可直接刷入”或“生产可用”的 AX9000 固件结论。
Loading
Loading