跟新 CICD,每次制作 ota 升级包
Docker Build and Push / build-image (push) Successful in 2m50s

This commit is contained in:
cheney
2026-08-07 10:04:57 +08:00
parent 4f5cdb4325
commit 8999b29646
3 changed files with 123 additions and 33 deletions
+31 -33
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env node
// OTA 升级包生成脚本 (兼容 tauri-plugin-hotswap)
// 用法: node scripts/build-ota.mjs [--out <dir>] [--sequence <n>] [--min-binary <x>] [--notes <s>] [--no-sign]
// 用法:
// node scripts/build-ota.mjs [--out <dir>] [--sequence <n>] [--min-binary <x>] [--notes <s>] [--no-sign] [--key <path>]
// 流程:
// 1) 校验 dist/ 已构建
// 2) 生成 bundle.tar.gz (dist/ 内容)
// 3) minisign 签名 (除非 --no-sign)
// 3) minisign 签名 (除非 --no-sign; 私钥优先读 MINISIGN_PRIVATE_KEY 环境变量)
// 4) 写 latest.json (manifest, 含 sequence/version/signature/bundle_size/notes/...)
// 5) 全部产物输出到 out 目录 (默认 ota-package/)
//
@@ -29,8 +30,9 @@ const SEQUENCE = Number(getArg('--sequence', String(Date.now()))) // 默认用
const MIN_BINARY = getArg('--min-binary', '')
const NOTES = getArg('--notes', '')
const NO_SIGN = args.includes('--no-sign')
// minisign 私钥路径 (默认读 ./ota-package/hotswap.key; 也可显式 --key)
// 私钥优先来源: MINISIGN_PRIVATE_KEY 环境变量 (CI 注入) → 文件 (--key 路径)
const KEY_PATH = path.resolve(getArg('--key', path.join(OUT_DIR, 'hotswap.key')))
const KEY_FROM_ENV = !!process.env.MINISIGN_PRIVATE_KEY
// 旧版 tauri.conf.json 里的 pubkey (自动同步过去, 方便用户复制)
const TAURI_CONF = path.join(ROOT, 'src-tauri', 'tauri.conf.json')
@@ -61,15 +63,30 @@ const bundleSha256 = crypto.createHash('sha256').update(fs.readFileSync(BUNDLE_P
let signature = ''
let pubkey = ''
if (!NO_SIGN) {
if (!fs.existsSync(KEY_PATH)) {
console.error('[build-ota] 私钥不存在:', KEY_PATH)
// CI 注入: 私钥走环境变量, 写到临时文件, 签名后立即删除 (避免落盘)
let KEY_PATH_TMP = null
let KEY_PATH_USE = KEY_PATH
if (KEY_FROM_ENV) {
KEY_PATH_TMP = path.join(OUT_DIR, `.hotswap.key.${process.pid}`)
fs.writeFileSync(KEY_PATH_TMP, process.env.MINISIGN_PRIVATE_KEY, { mode: 0o600 })
KEY_PATH_USE = KEY_PATH_TMP
}
if (!fs.existsSync(KEY_PATH_USE)) {
console.error('[build-ota] 私钥不存在:', KEY_PATH_USE)
console.error(' 注入方式二选一:')
console.error(' 1) CI: 设环境变量 MINISIGN_PRIVATE_KEY (整段 .key 文件内容, 含首行 untrusted comment)')
console.error(' 2) 本机: 放 ' + KEY_PATH + ', 或用 --key <path>')
console.error(' 生成方法: minisign -G -W -p ota-package/hotswap.pub -s ota-package/hotswap.key')
process.exit(1)
}
// minisign -Sm <file> -s <key> [-W] 生成 detached signature
const sigArgs = ['-Sm', BUNDLE_PATH, '-s', KEY_PATH]
// 私钥无密码时不需要 -W
// minisign -Sm <file> -s <key> 生成 detached signature
const sigArgs = ['-Sm', BUNDLE_PATH, '-s', KEY_PATH_USE]
const sigR = spawnSync('minisign', sigArgs, { stdio: 'inherit' })
// 清理 CI 临时私钥 (无论成功失败都删)
if (KEY_PATH_TMP) {
try { fs.unlinkSync(KEY_PATH_TMP) } catch { /* ignore */ }
KEY_PATH_TMP = null
}
if (sigR.status !== 0) {
console.error('[build-ota] minisign 签名失败')
process.exit(1)
@@ -81,7 +98,6 @@ if (!NO_SIGN) {
const PUB_PATH = path.join(path.dirname(KEY_PATH), 'hotswap.pub')
if (fs.existsSync(PUB_PATH)) {
pubkey = fs.readFileSync(PUB_PATH, 'utf8').trim()
// 提取 base64 部分 (第二行)
const lines = pubkey.split(/\r?\n/)
pubkey = (lines.find((l) => l.startsWith('RWR') || l.startsWith('RWT') || l.startsWith('RWQ')) || '').trim()
}
@@ -110,7 +126,8 @@ const manifest = {
fs.writeFileSync(path.join(OUT_DIR, 'latest.json'), JSON.stringify(manifest, null, 2))
// 4) 提示
console.log('\n=== OTA 包生成完成 ===')
console.log('')
console.log('=== OTA 包生成完成 ===')
console.log('输出目录: ', OUT_DIR)
console.log('版本: ', manifest.version)
console.log('sequence: ', manifest.sequence)
@@ -118,32 +135,13 @@ console.log('bundle 大小: ', (bundleStat.size / 1024).toFixed(1) + ' KB')
console.log('bundle sha256:', bundleSha256)
console.log('签名: ', signature ? '(已生成, 写入 latest.json)' : '(未签名, --no-sign)')
if (pubkey) {
console.log('\n请将下面这一行 pubkey 写到 src-tauri/tauri.conf.json -> plugins.hotswap.pubkey:')
console.log('')
console.log('请将下面这一行 pubkey 写到 src-tauri/tauri.conf.json -> plugins.hotswap.pubkey:')
console.log(' ' + pubkey)
// 自动同步 (可选, 注释掉默认行为; 用户自己粘更安全)
// syncTauriConf(pubkey)
}
console.log('\n下一步: 把这两个文件发布到 OTA 存储:')
console.log('')
console.log('下一步: 把这两个文件发布到 OTA 存储:')
console.log(' ' + BUNDLE_NAME)
console.log(' latest.json')
console.log('并确保 server 进程环境变量 OTA_PACKAGE_DIR 指向该目录。')
// 同步 tauri.conf.json pubkey 字段 (用户首次签名后手动确认)
// 注释: 启用此函数可自动写入, 但为安全默认关闭
function syncTauriConf(pub) {
if (!fs.existsSync(TAURI_CONF)) return
try {
const txt = fs.readFileSync(TAURI_CONF, 'utf8')
const newTxt = txt.replace(
/"pubkey":\s*"[^"]*"/,
'"pubkey": "' + pub + '"'
)
if (newTxt !== txt) {
fs.writeFileSync(TAURI_CONF, newTxt)
console.log('\n[build-ota] 已同步 pubkey 到 tauri.conf.json')
}
} catch (e) {
console.warn('[build-ota] 同步 pubkey 失败:', e.message)
}
}