# m2-gateway 镜像构建标准(IMAGE-BUILD-SPEC)

> **Audience**: 镜像团队(m2aicli / Vcflux / 任何为 m2-gateway 准备 Windows 模板镜像的人)
> **Agent 版本**: 0.3.1+(Master-Worker 架构)
> **关联**: [TEMPLATE-VM-PREP.md](./TEMPLATE-VM-PREP.md)(模板机准备 + SetupComplete.cmd 模板)· [CAPABILITIES.md](./CAPABILITIES.md)(能力矩阵)

---

## 1. 目标

四类镜像必须能通过 **变体 A 简化版 SetupComplete.cmd**(见 [TEMPLATE-VM-PREP §3.2.1](./TEMPLATE-VM-PREP.md))自动完成 agent 装机 + ws 上线,**无 reboot**,SLA **60-90 秒**。

| 镜像 | metadata source | AutoLogon 配置者 |
|---|---|---|
| vSphere(Vcflux 编排) | VMware Tools `guestinfo.userdata` | Vcflux GuestCustomizationSpec(unattend.xml) |
| 阿里云 ECS | IMDS `http://100.100.100.200/latest/user-data` | cloudbase-init `SetUserPasswordPlugin` |
| 腾讯云 CVM | IMDS `http://169.254.0.23/latest/user-data` | cloudbase-init `SetUserPasswordPlugin` |
| 华为云 ECS | IMDS `http://169.254.169.254/openstack/latest/user_data` | cloudbase-init `SetUserPasswordPlugin` |

> 裸 vSphere(**无** Vcflux GuestCustomizationSpec)或 Hyper-V GuestOps 走变体 B(自管 AutoLogon),详见 [TEMPLATE-VM-PREP §3.2.2](./TEMPLATE-VM-PREP.md)。本文档只覆盖变体 A 主推路径。

---

## 2. 镜像内必装组件 + 安装位置约定

| 组件 | 路径 | 来源 | 备注 |
|---|---|---|---|
| `m2-gateway.exe` | `C:\Program Files\m2-gateway\m2-gateway.exe` | `https://api.gateway.wechas.com/static/m2-gateway.exe`(SHA256 见 release notes) | **不跑** install-template,只 copy 文件 |
| `SetupComplete.cmd` | `C:\Windows\Setup\Scripts\SetupComplete.cmd` | 复制 [TEMPLATE-VM-PREP §3.2.1](./TEMPLATE-VM-PREP.md) 简化版代码 | Windows OOBE 完成自动跑 |
| `unattend.xml` | `C:\Windows\System32\Sysprep\unattend.xml` | §3 模板 | 让 Vcflux/cloudbase-init 有钩子注入 AutoLogon + 灌密码 |
| `cloudbase-init` | `C:\Program Files\Cloudbase Solutions\Cloudbase-Init\` | `https://www.cloudbase.it/downloads/CloudbaseInitSetup_Stable_x64.msi` | 仅公有云 + 用 cloudbase-init 的 vSphere 镜像 |
| `cloudbase-init.conf` | `C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf` | §4 四套模板之一 | 按目标云选 |
| 防锁屏 / 关屏保 / 防 RDP 注销组策略 | 注册表预配 | §5 reg 命令 | 镜像构建期预设,sysprep 不清 |

---

## 3. `unattend.xml` 模板(让 specialize 阶段灌密码 + AutoLogon)

放到 `C:\Windows\System32\Sysprep\unattend.xml`,sysprep 时被 `/unattend:` 引用:

```xml
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
  <settings pass="specialize">
    <component name="Microsoft-Windows-Shell-Setup"
               processorArchitecture="amd64"
               publicKeyToken="31bf3856ad364e35"
               language="neutral" versionScope="nonSxS">
      <ComputerName>*</ComputerName>
      <TimeZone>China Standard Time</TimeZone>
    </component>
  </settings>

  <settings pass="oobeSystem">
    <component name="Microsoft-Windows-Shell-Setup"
               processorArchitecture="amd64"
               publicKeyToken="31bf3856ad364e35"
               language="neutral" versionScope="nonSxS">
      <OOBE>
        <HideEULAPage>true</HideEULAPage>
        <HideLocalAccountScreen>true</HideLocalAccountScreen>
        <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
        <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
        <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
        <NetworkLocation>Work</NetworkLocation>
        <ProtectYourPC>3</ProtectYourPC>
        <SkipMachineOOBE>true</SkipMachineOOBE>
        <SkipUserOOBE>true</SkipUserOOBE>
      </OOBE>

      <!-- AutoLogon:Vcflux/cloudbase-init 会在 specialize 阶段把这段
           覆盖成真实 per-VM 值(灌密码 + 配 AutoLogonCount)。
           这里写占位是为了让 unattend.xml schema 通过模板机阶段的 sysprep
           dry-run,实际密码不会进模板镜像 — 由编排系统每次克隆时注入。 -->
      <AutoLogon>
        <Enabled>true</Enabled>
        <LogonCount>999</LogonCount>
        <Username>Administrator</Username>
        <Password>
          <Value>__PLACEHOLDER_WILL_BE_OVERWRITTEN__</Value>
          <PlainText>true</PlainText>
        </Password>
      </AutoLogon>

      <UserAccounts>
        <AdministratorPassword>
          <Value>__PLACEHOLDER_WILL_BE_OVERWRITTEN__</Value>
          <PlainText>true</PlainText>
        </AdministratorPassword>
      </UserAccounts>

      <TimeZone>China Standard Time</TimeZone>
    </component>
  </settings>
</unattend>
```

> **关键**:`<AutoLogon>` + `<UserAccounts>` 的 `<Value>` 是**占位符**,实际密码由编排系统(Vcflux GuestCustomizationSpec 或 cloudbase-init `SetUserPasswordPlugin`)在 clone-then-customize 阶段**覆盖**。模板镜像里**不能存真实密码**,否则克隆出来的 VM 全部用同一个密码。

---

## 4. cloudbase-init.conf 四套配置模板

通用基础部分(四套都用):

```ini
[DEFAULT]
username=Administrator
groups=Administrators
inject_user_password=true
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
verbose=true
debug=true
logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
logfile=cloudbase-init.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\

plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,
        cloudbaseinit.plugins.windows.ntpclient.NTPClientPlugin,
        cloudbaseinit.plugins.windows.setuserpassword.SetUserPasswordPlugin,
        cloudbaseinit.plugins.windows.createuser.CreateUserPlugin,
        cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,
        cloudbaseinit.plugins.common.userdata.UserDataPlugin
```

**差异部分** — `metadata_services` 按目标云选一套:

### 4.1 vSphere(VMware Tools `guestinfo.userdata`)

```ini
metadata_services=cloudbaseinit.metadata.services.vmwareguestinfoservice.VMwareGuestInfoService
```

注意:vSphere 镜像需要 VMware Tools ≥ 11.x 才有完整的 `guestinfo` API。Vcflux adapter 推荐 12.x 以上。

### 4.2 阿里云 ECS

```ini
metadata_services=cloudbaseinit.metadata.services.ec2service.EC2Service
ec2_metadata_base_url=http://100.100.100.200/
```

阿里云 IMDS 走 EC2 兼容接口,base URL 跟标准 EC2 不同。

### 4.3 腾讯云 CVM

```ini
metadata_services=cloudbaseinit.metadata.services.ec2service.EC2Service
ec2_metadata_base_url=http://169.254.0.23/
```

腾讯云用了非标准 IMDS IP 段(`169.254.0.23`,不是 EC2 的 `169.254.169.254`)。

### 4.4 华为云 ECS

```ini
metadata_services=cloudbaseinit.metadata.services.httpservice.HttpService
metadata_base_url=http://169.254.169.254/
add_metadata_private_ip_route=true
```

华为云走 OpenStack 元数据协议(HttpService),不是 EC2。

---

## 5. 防锁屏 / 防 RDP 注销 / 关屏保组策略(镜像预配,sysprep 保留)

变体 A SetupComplete.cmd 不再自己写这些注册表,改由镜像构建期预设。模板机里跑一次:

```cmd
:: 防自动锁屏
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" ^
    /v DisableLockWorkstation /t REG_DWORD /d 1 /f

:: 关屏保(LocalMachine 层级,sysprep generalize 保留)
reg add "HKLM\Software\Policies\Microsoft\Windows\Control Panel\Desktop" ^
    /v ScreenSaveActive /t REG_SZ /d 0 /f

:: 防 RDP 断开自动注销(保留 disconnected session,worker 继续跑)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" ^
    /v fResetBroken /t REG_DWORD /d 0 /f

:: 关 UAC 提权 prompt(可选,如果业务要求 admin 操作不被打断)
:: 慎用 — 会降低镜像安全等级,只在隔离的游戏 VM 镜像里用
:: reg add "HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System" ^
::     /v EnableLUA /t REG_DWORD /d 0 /f
```

这些注册表在 `HKLM` 层级,**sysprep generalize 不清**,克隆出的 VM 自动继承。

---

## 6. 模板机构建顺序(Packer / 手工通用)

```
1. 创建空 Windows VM(Windows Server 2019/2022 Datacenter,2 vCPU / 4 GB RAM)
2. 网络 / 时区 / 主机名 配好,加域(若需要),全部 Windows Update 装完
3. 装 VMware Tools(vSphere)/ 装各云 Guest Agent(公有云)
4. 装 cloudbase-init MSI:
     msiexec /i CloudbaseInitSetup_Stable_x64.msi /qn /norestart RUN_SERVICE_AS_LOCAL_SYSTEM=1
5. 写 cloudbase-init.conf(§4,按目标云选)
6. 写 unattend.xml 到 C:\Windows\System32\Sysprep\unattend.xml(§3)
7. 跑 §5 防锁屏 / 防 RDP / 关屏保注册表预配
8. 装 m2-gateway.exe 到 C:\Program Files\m2-gateway\(curl 下载或 copy)
9. 把简化版 SetupComplete.cmd 放到 C:\Windows\Setup\Scripts\
   (TEMPLATE-VM-PREP §3.2.1 完整代码)
10. (可选)Audit Mode 跑一次冒烟测试(见 §7)
11. 跑 prep-cleanup.cmd 清干净所有 per-machine 状态
    (TEMPLATE-VM-PREP §2.2)
12. 跑 sysprep 前验证清单全部 OK
    (TEMPLATE-VM-PREP §2.3)
13. sysprep /generalize /oobe /shutdown /unattend:C:\Windows\System32\Sysprep\unattend.xml
14. 关机后镜像化:
    - vSphere:Clone to Template 或导出 OVA
    - 阿里云:停机 → 创建自定义镜像
    - 腾讯云:停机 → 创建自定义镜像
    - 华为云:停机 → 创建系统盘镜像
```

---

## 7. Packer 构建 hint

Packer JSON 骨架(`m2-gateway-template.json`):

```json
{
  "builders": [
    {
      "type": "vsphere-iso",
      "vcenter_server": "{{user `vsphere_server`}}",
      "username": "{{user `vsphere_user`}}",
      "password": "{{user `vsphere_password`}}",
      "vm_name": "m2-gateway-template-{{timestamp}}",
      "iso_paths": ["[datastore1] iso/SW_DVD9_Win_Server_STD_CORE_2022.iso"],
      "communicator": "winrm",
      "winrm_username": "Administrator",
      "winrm_password": "{{user `winrm_password`}}",
      "boot_command": ["..."]
    }
  ],
  "provisioners": [
    {
      "type": "file",
      "source": "files/m2-gateway.exe",
      "destination": "C:/Program Files/m2-gateway/m2-gateway.exe"
    },
    {
      "type": "file",
      "source": "files/SetupComplete.cmd",
      "destination": "C:/Windows/Setup/Scripts/SetupComplete.cmd"
    },
    {
      "type": "file",
      "source": "files/unattend.xml",
      "destination": "C:/Windows/System32/Sysprep/unattend.xml"
    },
    {
      "type": "file",
      "source": "files/cloudbase-init.conf",
      "destination": "C:/Program Files/Cloudbase Solutions/Cloudbase-Init/conf/cloudbase-init.conf"
    },
    {
      "type": "windows-shell",
      "script": "scripts/install-cloudbase-init.cmd"
    },
    {
      "type": "windows-shell",
      "script": "scripts/preset-policies.cmd"
    },
    {
      "type": "windows-shell",
      "script": "scripts/prep-cleanup.cmd"
    },
    {
      "type": "windows-shell",
      "inline": [
        "C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /shutdown /unattend:C:\\Windows\\System32\\Sysprep\\unattend.xml"
      ]
    }
  ]
}
```

`files/` 目录内容:

```
files/
├── m2-gateway.exe                  ← 从 release 拉,见 §2 来源
├── m2-gateway.exe.sha256           ← 校验文件
├── SetupComplete.cmd               ← TEMPLATE-VM-PREP §3.2.1 完整代码
├── unattend.xml                    ← 本文档 §3 模板
└── cloudbase-init.conf             ← 本文档 §4 按目标云选

scripts/
├── install-cloudbase-init.cmd      ← MSI 静默安装
├── preset-policies.cmd             ← 本文档 §5 注册表预配
└── prep-cleanup.cmd                ← TEMPLATE-VM-PREP §2.2 cleanup
```

> 各云的 Packer builder 跟 vSphere 不同(`alicloud-ecs` / `tencentcloud-cvm` / `huaweicloud-ecs`),但 `provisioners` 段(provisioner 顺序 + file destinations)四类镜像**完全一致**。

---

## 8. 镜像验收清单(发布到 marketplace / template library 前必跑)

镜像构建完成 + sysprep 完成后,**用镜像克隆一个测试 VM** 验证:

```cmd
:: 1. m2-gateway.exe 存在
dir "C:\Program Files\m2-gateway\m2-gateway.exe"
:: → 应该有,版本 = release 版本

:: 2. SetupComplete.cmd 存在
dir "C:\Windows\Setup\Scripts\SetupComplete.cmd"

:: 3. cloudbase-init service 装好
sc query cloudbase-init
:: → STATE: RUNNING

:: 4. cloudbase-init.conf metadata_services 配对了
type "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf" ^
  | findstr metadata_services
:: → 应该匹配目标云

:: 5. 防锁屏 / 防 RDP 注销组策略生效
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v DisableLockWorkstation
:: → 0x1
reg query "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v fResetBroken
:: → 0x0

:: 6. 模板机 per-machine 状态清干净
sc query m2-gateway-svc 2>nul && echo FAIL || echo OK: no service
schtasks /query /tn m2-gateway-worker 2>nul && echo FAIL || echo OK: no task
dir "%ProgramData%\m2-gateway\setup.conf" 2>nul && echo FAIL || echo OK: no setup.conf
:: → 三条都 OK

:: 7. 准备一份 per-VM user_data(模拟 m2aicli provision)
:: 拷贝到 IMDS / guestinfo / config drive

:: 8. 启动 VM,等 60-90s,检查 ws 上线
:: 中枢 admin web 看 host detail:
::   online: true
::   ws_connected: true
::   installed_version: 0.3.1
::   session 状态: autologon_active
```

---

## 9. 各厂商专属注意事项

### 9.1 vSphere

- VMware Tools 必须 ≥ 11.x,推荐 12.x。低版本 `guestinfo` API 不全。
- Vcflux GuestCustomizationSpec 跟 cloudbase-init 二选一,**不要同时启用**(谁先跑谁配 AutoLogon,顺序不定会冲突)。Vcflux adapter 默认 spec,我们镜像里**禁用** cloudbase-init service 启动(`sc config cloudbase-init start= demand`),让 Vcflux 来管;非 Vcflux 路径再开。
- 镜像导出 OVA 时关掉 fixed MAC(Hardware → Network Adapter → MAC Address → Automatic),否则克隆 VM 全部 MAC 冲突。

### 9.2 阿里云

- IMDS endpoint `100.100.100.200`,**不是** EC2 标准 `169.254.169.254`。
- 阿里云 cloudbase-init 有官方 fork,但**推荐用上游版本**(Microsoft/cloudbase-init),fork 行为不稳定(详见 [TEMPLATE-VM-PREP §3.4.5](./TEMPLATE-VM-PREP.md))。
- user_data 16 KB 限制,我们 JSON ~1 KB,**充裕**。
- 自定义镜像 ECS 实例创建必须用 `RAM` 模式 IMDS(token 鉴权),老 IMDSv1 在 2026-01 后阿里逐步关闭。cloudbase-init 1.1.4+ 支持 IMDSv2。

### 9.3 腾讯云

- IMDS endpoint `169.254.0.23`,非标准。
- user_data 16 KB 限制。
- CVM 跨可用区克隆需要把镜像复制到目标 region,延迟 5-15 分钟。

### 9.4 华为云

- IMDS endpoint `169.254.169.254/openstack`,走 OpenStack 协议,用 `HttpService` 不是 `EC2Service`。
- user_data 32 KB 限制,**最宽松**。
- 华为云有 IMS(镜像服务)和 BMS(裸机),仅 IMS 支持自定义镜像 + cloudbase-init。

---

## 10. 维护

- 每次 m2-gateway release(0.3.x → 0.4.0 等)**必须**重建镜像并验证 §8 清单。
- cloudbase-init 大版本升级(1.1.x → 1.2.x)需要回归 §4 配置 + §8 验收。
- 镜像构建脚本(Packer / 手工)放 m2aicli 镜像仓库 `m2aicli/images/`(由 m2aicli 镜像团队维护)。本文档由 wechas/gateway 团队维护,变更走 PR。

---

## 11. 反馈

文档锚点 `docs/api/IMAGE-BUILD-SPEC.md`(本仓库)+ 同步 `marketing-site/public/api-docs/`(公开)。

集成方实测中踩到的厂商专属坑欢迎反馈,会更新到第 §9 节。
