@zqagent/core 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/update/controller.ts","../../../../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.12_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","../../../src/update/downloader.ts","../../../src/update/version.ts","../../../src/update/manifest.ts","../../../src/update/signature.ts","../../../src/update/platform.ts","../../../src/update/installer.ts","../../../src/update/state.ts"],"sourcesContent":["/**\n * 自动更新调度器。\n *\n * 生命周期:`start()` 后按「启动延迟 + 固定间隔」拉清单;发现新版本即静默下载并自检,\n * 之后**等到节点空闲**(连续 60s 没有 running / waiting 会话)才替换可执行文件并退出,\n * 由 systemd/launchd 拉起新版本。\n *\n * 所有重活都在后台进行,任何失败只记日志,不影响节点服务。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fetchManifest, downloadBinary, selfCheck } from \"./downloader.js\";\nimport { platformKey } from \"./manifest.js\";\nimport { compareVersions, parseVersion } from \"./version.js\";\nimport { currentVersion as currentVersionOf, isSeaRuntime, serviceEnvValue, serviceManager, selfUpdateCapability } from \"./platform.js\";\nimport { parsePublicKey } from \"./signature.js\";\nimport { installStaged, rollback as rollbackExecutable } from \"./installer.js\";\nimport { loadState, patchState, updatesDir, type UpdateState } from \"./state.js\";\n\nconst DEFAULT_BASE = \"https://agent.cd-zq.com/downloads/zqagent\";\n\n/**\n * 安装上下文。\n *\n * `excludeSessionId`:发起这次更新的会话。它自己通常一直是 running,\n * 必须从「忙」的判定里排除,否则等待空闲永远不成立(见 `waitForIdle`)。\n */\nexport interface InstallContext {\n excludeSessionId?: string;\n /**\n * 是否要求进程已启动足够久(默认 true)。\n *\n * 这道闸门是给**自动**更新用的:避免刚起来就因清单抖动反复重启、\n * 或在启动过程中替换二进制。用户显式发起安装时传 false——\n * 「点了更新却要再等 10 分钟」同样会让用户以为没反应。\n */\n requireUptime?: boolean;\n}\n\nexport interface UpdaterOptions {\n dataDir: string;\n /** 是否空闲(无 running / waiting 会话;可排除发起更新的会话) */\n isIdle: (excludeSessionId?: string) => boolean;\n /** 替换前关闭服务(落盘账本、断连) */\n prepareExit?: () => void;\n log?: (msg: string) => void;\n /** 清单地址覆盖(测试/内网) */\n url?: string;\n}\n\nexport interface UpdaterStatus {\n enabled: boolean;\n reason?: string;\n currentVersion: string;\n channel: string;\n manifestUrl: string;\n serviceManager: string | null;\n lastCheckAt?: string;\n stagedVersion?: string;\n pendingVersion?: string;\n previousVersion?: string;\n backupVersion?: string;\n lastError?: string;\n}\n\n/** 版本检查结果(`self_update --action check` 用;不下载、不写状态) */\nexport interface UpdateInspect {\n currentVersion: string;\n channel: string;\n manifestUrl: string;\n /** 清单里是否有比当前更新的版本 */\n updateAvailable: boolean;\n /** 清单里的最新版本(拉不到清单时缺省) */\n latestVersion?: string;\n notes?: string;\n /** 不可更新的原因(已是最新 / 未启用 / 需手动升级…) */\n reason?: string;\n /** 低于清单要求的最低版本时给出该下限 */\n manualUpgradeRequired?: string;\n}\n\n/** 远端触发安装的回执(ack 先回,安装本身在后台跑) */\nexport interface UpdateInstallStart {\n /** 是否已受理(false 表示无需更新或不可更新) */\n accepted: boolean;\n fromVersion: string;\n toVersion?: string;\n message: string;\n}\n\n/**\n * 读取更新配置:进程环境优先,缺失时回落到 systemd unit 注入的变量。\n *\n * 后台服务天然带全部变量;运维在交互 shell 里执行 `zqagent update` 时\n * 只有回落这一条路,否则公钥/通道/清单地址都会被判成「未配置」。\n */\nfunction env(key: string): string | undefined {\n const v = process.env[key]?.trim();\n if (v) return v;\n const fromService = serviceEnvValue(key)?.trim();\n return fromService ? fromService : undefined;\n}\n\nfunction num(key: string, fallback: number): number {\n const n = Number(env(key));\n return Number.isFinite(n) && n >= 0 ? n : fallback;\n}\n\nexport function updaterChannel(): string {\n return env(\"ZQAGENT_UPDATE_CHANNEL\") ?? \"stable\";\n}\n\nexport function updaterUrl(channel = updaterChannel()): string {\n return env(\"ZQAGENT_UPDATE_URL\") ?? `${DEFAULT_BASE}/${channel}.json`;\n}\n\n/** 更新间隔:默认 6h,±15% 抖动,避免整批节点同时打同一台静态服务器 */\nfunction nextIntervalMs(): number {\n const base = num(\"ZQAGENT_UPDATE_INTERVAL_MS\", 6 * 60 * 60 * 1000);\n const jitter = base * 0.15 * (Math.random() * 2 - 1);\n return Math.max(60_000, Math.round(base + jitter));\n}\n\n/**\n * 清单 → 检查结论(纯函数,便于单测)。\n *\n * 只有「清单版本更高、且当前版本满足 minimumVersion」才算可更新;\n * 其余情况都要给出 `reason`,让远端 UI 能直接展示原因而不是靠猜。\n */\nexport function evaluateManifest(\n base: { currentVersion: string; channel: string; manifestUrl: string },\n manifest: { version: string; minimumVersion?: string; notes?: string },\n): UpdateInspect {\n if (!parseVersion(base.currentVersion) ||\n compareVersions(manifest.version, base.currentVersion) <= 0) {\n return {\n ...base,\n updateAvailable: false,\n latestVersion: manifest.version,\n reason: `已是最新版本(v${base.currentVersion})`,\n };\n }\n if (manifest.minimumVersion &&\n compareVersions(base.currentVersion, manifest.minimumVersion) < 0) {\n return {\n ...base,\n updateAvailable: false,\n latestVersion: manifest.version,\n manualUpgradeRequired: manifest.minimumVersion,\n reason: `当前版本过低,需手动升级到 >= ${manifest.minimumVersion}`,\n };\n }\n return {\n ...base,\n updateAvailable: true,\n latestVersion: manifest.version,\n ...(manifest.notes ? { notes: manifest.notes } : {}),\n };\n}\n\nexport class UpdateController {\n private readonly log: (msg: string) => void;\n private timer: NodeJS.Timeout | null = null;\n private stopped = false;\n private checking = false;\n private installing = false;\n /**\n * 进行中的「等空闲」被要求立刻执行。\n *\n * 用户点「立即更新」时若已有一个在等空闲的任务,不能回一句「已有任务在进行中」\n * 就完事——那个任务可能正因为某个 running 会话而永远等下去。置位后\n * `waitForIdle` 下一拍直接放行。\n */\n private forceRequested = false;\n private startedAt = Date.now();\n private lastCheckAt?: string;\n\n constructor(\n private readonly opts: UpdaterOptions,\n private readonly currentVersion = currentVersionOf(),\n ) {\n this.log = opts.log ?? ((msg) => console.log(`[update] ${msg}`));\n }\n\n /** 公钥(未配置时禁用自更新:无法验签就不能替换可执行文件) */\n private get publicKey() {\n return parsePublicKey(env(\"ZQAGENT_UPDATE_PUBKEY\") ?? \"\");\n }\n\n private get autoUpdateOff(): boolean {\n return env(\"ZQAGENT_AUTO_UPDATE\") === \"0\";\n }\n\n get enabled(): boolean {\n return !this.autoUpdateOff && selfUpdateCapability(Boolean(this.publicKey)).ok;\n }\n\n private manifestUrl(): string {\n return this.opts.url ?? updaterUrl();\n }\n\n /** 启动调度并核对上次更新结果 */\n start(): void {\n this.reconcile();\n if (this.autoUpdateOff) {\n this.log(\"自动更新已关闭(ZQAGENT_AUTO_UPDATE=0)\");\n return;\n }\n const cap = selfUpdateCapability(Boolean(this.publicKey));\n if (!cap.ok) {\n this.log(`自动更新未启用:${cap.reason}`);\n return;\n }\n this.log(\n `自动更新已启用(v${this.currentVersion},通道 ${updaterChannel()},` +\n `由 ${serviceManager()} 托管)`,\n );\n this.schedule(num(\"ZQAGENT_UPDATE_INITIAL_DELAY_MS\", 5 * 60 * 1000));\n }\n\n stop(): void {\n this.stopped = true;\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n }\n\n /** 启动时对账:确认上次更新是否成功、清理失效的暂存/备份状态 */\n private reconcile(): void {\n const state = loadState(this.opts.dataDir);\n const current = this.currentVersion;\n\n if (state.stagedPath && !fs.existsSync(state.stagedPath)) {\n patchState(this.opts.dataDir, { stagedVersion: undefined, stagedPath: undefined });\n }\n\n const patch: Partial<UpdateState> = { runningVersion: current };\n if (state.pendingVersion && state.pendingVersion === current) {\n patch.pendingVersion = undefined;\n this.log(`已更新到 v${current}(此前 v${state.previousVersion ?? \"?\"})`);\n } else if (state.pendingVersion) {\n // 新版本没能起来(被回滚或被服务管理器恢复):保留备份信息,仅提示\n this.log(\n `注意:上次安装 v${state.pendingVersion} 未生效,当前仍为 v${current}` +\n (state.backupPath ? \";可执行 update --rollback 恢复备份\" : \"\"),\n );\n patch.pendingVersion = undefined;\n }\n patchState(this.opts.dataDir, patch);\n\n // Windows:清理上次替换留下的 .old\n if (process.platform === \"win32\") {\n fs.rmSync(`${process.execPath}.old`, { force: true });\n }\n }\n\n private schedule(delayMs: number): void {\n if (this.stopped) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.tick();\n }, delayMs);\n this.timer.unref();\n }\n\n private async tick(): Promise<void> {\n try {\n await this.check();\n } catch (err) {\n this.log(`检查更新失败:${(err as Error).message}`);\n }\n this.schedule(nextIntervalMs());\n }\n\n /**\n * 只读检查:拉清单比对版本,不下载、不改 state。\n *\n * 供远端 `self_update --action check` 与 UI 展示用;与定时器 `check()` 不同,\n * 这里不加 `checking` 锁、不写 `lastCheckAt`,因此不会干扰自动更新流程。\n */\n async inspect(): Promise<UpdateInspect> {\n const base = {\n currentVersion: this.currentVersion,\n channel: updaterChannel(),\n manifestUrl: this.manifestUrl(),\n };\n const key = this.publicKey;\n const cap = selfUpdateCapability(Boolean(key));\n if (!cap.ok) {\n return { ...base, updateAvailable: false, reason: `自更新不可用:${cap.reason}` };\n }\n const res = await fetchManifest(base.manifestUrl, key!, {\n allowInsecureUrl: env(\"ZQAGENT_UPDATE_INSECURE\") === \"1\",\n });\n if (!res.manifest) {\n return {\n ...base,\n updateAvailable: false,\n reason: `更新检查失败:${res.error ?? \"清单校验失败\"}`,\n };\n }\n return evaluateManifest(base, res.manifest);\n }\n\n /**\n * 远端触发安装:先同步给出结论(受理/无需更新),安装本身在后台跑。\n *\n * 必须「先回执、后安装」:安装成功后进程会 `exit(70)` 交给 systemd 拉起新版本,\n * 如果等到安装完成再回执,回执永远发不出去(表现为网关超时)。\n *\n * 复用定时器同一条 `check()` 路径:验签、下载、自检、失败计数、安装后自检与\n * 回滚保护全都一致,只是把 `installMode` 设为 `force` 时不等待空闲。\n */\n async requestInstall(force = false, opts: InstallContext = {}): Promise<UpdateInstallStart> {\n const fromVersion = this.currentVersion;\n const cap = selfUpdateCapability(Boolean(this.publicKey));\n if (!cap.ok) {\n return { accepted: false, fromVersion, message: `自更新不可用:${cap.reason}` };\n }\n\n const info = await this.inspect();\n if (!info.updateAvailable) {\n return {\n accepted: false,\n fromVersion,\n ...(info.latestVersion ? { toVersion: info.latestVersion } : {}),\n message: info.reason ?? \"已是最新版本\",\n };\n }\n\n // 已有任务在进行中:force 时必须**打断它的等待**,否则「立即更新」名不副实\n if (this.checking || this.installing) {\n if (force) this.forceRequested = true;\n return {\n accepted: true,\n fromVersion,\n toVersion: info.latestVersion,\n message: force\n ? `已请求立即安装 v${info.latestVersion}(打断此前的等待)`\n : `已有更新任务在进行中(目标 v${info.latestVersion}),可在稍后用 status 查看结果`,\n };\n }\n\n // 后台执行(ignoreSkip=true:用户显式触发,可越过「已回滚版本跳过」)\n void this.check(force, false, true, {\n ...opts,\n // 用户显式请求:跳过「启动满 10 分钟」这道只为自动更新设的闸门\n requireUptime: false,\n }).catch((err) => {\n this.recordFailure(info.latestVersion, `安装失败:${(err as Error).message}`);\n });\n return {\n accepted: true,\n fromVersion,\n toVersion: info.latestVersion,\n message: force\n ? `开始安装 v${info.latestVersion}(立即重启)`\n : `开始安装 v${info.latestVersion}(等待节点空闲后重启)`,\n };\n }\n\n /**\n * 检查并准备更新;`installMode` 为 true 时下载后立即安装(不再等空闲)。\n * 返回可读结论,供 CLI `update --check` 使用。\n */\n async check(\n installMode = false,\n dryRun = false,\n ignoreSkip = false,\n ctx: InstallContext = {},\n ): Promise<string> {\n const key = this.publicKey;\n const cap = selfUpdateCapability(Boolean(key));\n if (!cap.ok) return `未启用自动更新:${cap.reason}`;\n if (this.checking) return \"已有检查在进行中\";\n this.checking = true;\n\n try {\n const current = this.currentVersion;\n const url = this.manifestUrl();\n this.log(`检查更新:${url}`);\n const res = await fetchManifest(url, key!, {\n allowInsecureUrl: env(\"ZQAGENT_UPDATE_INSECURE\") === \"1\",\n });\n if (!res.manifest) {\n this.recordFailure(undefined, res.error ?? \"清单校验失败\");\n return `更新检查失败:${res.error}`;\n }\n\n const manifest = res.manifest;\n this.lastCheckAt = new Date().toISOString();\n patchState(this.opts.dataDir, { lastCheckAt: this.lastCheckAt });\n\n if (!parseVersion(current) || compareVersions(manifest.version, current) <= 0) {\n patchState(this.opts.dataDir, {\n stagedVersion: undefined,\n stagedPath: undefined,\n lastError: undefined,\n });\n this.log(`已是最新版本(v${current})`);\n return `已是最新版本(v${current})`;\n }\n\n if (manifest.minimumVersion && compareVersions(current, manifest.minimumVersion) < 0) {\n this.recordFailure(manifest.version, `当前版本过低,需手动升级到 >= ${manifest.minimumVersion}`);\n return `需要手动升级:当前 v${current} 低于最低要求 v${manifest.minimumVersion}`;\n }\n\n // 只检查不下载:`update --check` 用,避免为看版本号拉一个 100MB 的包\n if (dryRun) {\n const notes = manifest.notes ? `:${manifest.notes}` : \"\";\n return `发现新版本 v${manifest.version}(当前 v${current})${notes}`;\n }\n\n const state = loadState(this.opts.dataDir);\n\n // 用户回滚过这个版本:自动更新跳过,直到发布更新的版本\n if (!ignoreSkip && state.skippedVersion === manifest.version) {\n const msg = `v${manifest.version} 已被回滚,自动更新跳过(手动 update --now 可强制安装)`;\n this.log(msg);\n return msg;\n }\n if (state.skippedVersion && compareVersions(manifest.version, state.skippedVersion) > 0) {\n // 出现更新的版本,解除跳过\n patchState(this.opts.dataDir, { skippedVersion: undefined });\n }\n\n /*\n * 下面两处\"不更新\"都有正当理由,但后台定时器调用时没有调用方会读返回值:\n * 不写日志就等于完全静默。曾经有过节点连续几周每天检查、却因为清单里少了\n * 自己的平台键而一直停在旧版本,日志里只有「检查更新:…」一行,无从排查。\n */\n const maxFailures = num(\"ZQAGENT_UPDATE_MAX_FAILURES\", 3);\n if (state.failedVersion === manifest.version && (state.failureCount ?? 0) >= maxFailures) {\n const msg = `v${manifest.version} 已连续失败 ${state.failureCount} 次,跳过(等待下一版本)`;\n this.log(msg);\n return msg;\n }\n\n const platform = platformKey();\n const bin = manifest.binaries[platform];\n if (!bin) {\n const available = Object.keys(manifest.binaries).join(\", \") || \"(空)\";\n const msg = `清单没有当前平台(${platform})的产物;清单提供:${available}`;\n this.log(msg);\n return msg;\n }\n\n // 已暂存同一版本且文件有效 → 直接进入安装\n let stagedPath = state.stagedPath;\n if (!(state.stagedVersion === manifest.version && stagedPath && fs.existsSync(stagedPath))) {\n stagedPath = path.join(\n updatesDir(this.opts.dataDir),\n \"staged\",\n `zqagent-${manifest.version}${process.platform === \"win32\" ? \".exe\" : \"\"}`,\n );\n const dl = await downloadBinary(bin, stagedPath, this.log);\n if (dl.error) {\n this.recordFailure(manifest.version, dl.error);\n return `下载失败:${dl.error}`;\n }\n\n const selfTest = selfCheck(stagedPath, manifest.version);\n if (!selfTest.ok) {\n fs.rmSync(stagedPath, { force: true });\n this.recordFailure(manifest.version, selfTest.error ?? \"自检失败\");\n return `安装包自检失败:${selfTest.error}`;\n }\n\n patchState(this.opts.dataDir, {\n stagedVersion: manifest.version,\n stagedPath,\n lastError: undefined,\n failureCount: 0,\n failedVersion: undefined,\n });\n }\n\n this.log(\n `v${manifest.version} 已就绪${manifest.notes ? `:${manifest.notes}` : \"\"}`,\n );\n await this.installWhenIdle(manifest.version, stagedPath, installMode, ctx);\n return `v${manifest.version} 已就绪`;\n } finally {\n this.checking = false;\n }\n }\n\n private recordFailure(version: string | undefined, error: string): void {\n const state = loadState(this.opts.dataDir);\n const sameVersion = version !== undefined && state.failedVersion === version;\n patchState(this.opts.dataDir, {\n lastError: error,\n failedVersion: version ?? state.failedVersion,\n failureCount: sameVersion ? (state.failureCount ?? 0) + 1 : 1,\n });\n this.log(error);\n }\n\n /** 等待节点连续空闲后安装;`installMode` 立即安装 */\n private async installWhenIdle(\n version: string,\n stagedPath: string,\n installMode: boolean,\n ctx: InstallContext = {},\n ): Promise<void> {\n if (this.installing) return;\n this.installing = true;\n\n try {\n if (!installMode) {\n const idleMs = num(\"ZQAGENT_UPDATE_IDLE_MS\", 60 * 1000);\n this.log(\n `等待空闲后安装 v${version}(需连续空闲 ${Math.round(idleMs / 1000)}s` +\n `${ctx.excludeSessionId ? \",已排除发起更新的会话\" : \"\"})`,\n );\n const ok = await this.waitForIdle(ctx);\n if (!ok) {\n this.installing = false;\n return;\n }\n }\n this.applyUpdate(version, stagedPath);\n } catch (err) {\n this.installing = false;\n this.recordFailure(version, `安装失败:${(err as Error).message}`);\n }\n }\n\n private waitForIdle(ctx: InstallContext = {}): Promise<boolean> {\n const requireUptime = ctx.requireUptime !== false;\n const minUptime = requireUptime ? num(\"ZQAGENT_UPDATE_MIN_UPTIME_MS\", 10 * 60 * 1000) : 0;\n const idleMs = num(\"ZQAGENT_UPDATE_IDLE_MS\", 60 * 1000);\n let idleSince: number | null = null;\n\n return new Promise((resolve) => {\n const timer = setInterval(() => {\n if (this.stopped) {\n clearInterval(timer);\n resolve(false);\n return;\n }\n // 用户已要求「立即更新」:不再等空闲\n if (this.forceRequested) {\n this.forceRequested = false;\n clearInterval(timer);\n resolve(true);\n return;\n }\n const ready = Date.now() - this.startedAt >= minUptime;\n // 排除发起更新的会话:它自己一直是 running,不排除就永远等不到空闲\n if (!ready || !this.opts.isIdle(ctx.excludeSessionId)) {\n idleSince = null;\n return;\n }\n idleSince ??= Date.now();\n if (Date.now() - idleSince >= idleMs) {\n clearInterval(timer);\n resolve(true);\n }\n }, 1000);\n timer.unref();\n });\n }\n\n private applyUpdate(version: string, stagedPath: string): void {\n this.opts.prepareExit?.();\n const execPath = process.execPath;\n installStaged({\n dataDir: this.opts.dataDir,\n execPath,\n stagedPath,\n version,\n log: this.log,\n });\n\n // 在真实目标路径上再验一次:staged 能跑不代表替换后能跑(SELinux 上下文、\n // noexec 挂载等都会让 systemd 侧 execve 失败并陷入 203/EXEC 重启循环)。\n // 验不过就立刻用备份回滚,保持节点可用。\n const verify = selfCheck(execPath, version);\n if (!verify.ok) {\n const restored = rollbackExecutable(this.opts.dataDir, execPath, this.log);\n // 回滚时 currentVersion() 还是旧版本,显式记下坏版本,避免下一轮又装回来\n patchState(this.opts.dataDir, { skippedVersion: version });\n this.recordFailure(\n version,\n `安装后自检失败(${verify.error ?? \"未知错误\"})` +\n (restored ? `,已回滚到 v${restored}` : \",回滚失败,需人工处理\"),\n );\n process.exit(1);\n }\n\n this.log(`已安装 v${version},正在重启…`);\n // 非零退出:launchd 的 KeepAlive{SuccessfulExit=false} 只在非正常退出时拉起进程,\n // 用 0 退出会导致新版本不被启动(systemd 的 Restart=always 两者都重启)。\n process.exit(70);\n }\n\n /**\n * 用备份回滚;返回恢复到的版本。\n *\n * 目标路径优先取上次安装记录的路径:`zqagent update --rollback` 可能由 node 进程执行\n * (如 install.sh 内),此时 `process.execPath` 是 node 本身,绝不能拿它做替换。\n */\n rollbackNow(): string | null {\n const state = loadState(this.opts.dataDir);\n const target = state.installedExecPath ?? (isSeaRuntime() ? process.execPath : null);\n if (!target) {\n this.log(\"无法确定节点可执行文件位置(缺少安装记录);请用 deploy/service/install.sh rollback\");\n return null;\n }\n return rollbackExecutable(this.opts.dataDir, target, this.log);\n }\n\n status(): UpdaterStatus {\n const state = loadState(this.opts.dataDir);\n const cap = selfUpdateCapability(Boolean(this.publicKey));\n return {\n enabled: !this.autoUpdateOff && cap.ok,\n reason: cap.reason,\n currentVersion: this.currentVersion,\n channel: updaterChannel(),\n manifestUrl: this.manifestUrl(),\n serviceManager: serviceManager(),\n lastCheckAt: this.lastCheckAt ?? state.lastCheckAt,\n stagedVersion: state.stagedVersion,\n pendingVersion: state.pendingVersion,\n previousVersion: state.previousVersion,\n backupVersion: state.backupVersion,\n lastError: state.lastError,\n };\n }\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * 清单拉取与产物下载。\n *\n * 下载全程流式写入 `.part` 文件并同步计算 SHA-256,校验通过后才 rename 为正式文件。\n * 任何一步失败都会清理临时文件,不会留下半截可执行文件。\n */\nimport { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { spawnSync } from \"node:child_process\";\nimport type { KeyObject } from \"node:crypto\";\nimport * as path from \"node:path\";\nimport { parseManifest, type UpdateBinary, type UpdateManifest } from \"./manifest.js\";\nimport { verifySignature } from \"./signature.js\";\n\n/** 单个产物的下载上限(SEA 带内嵌 ripgrep,当前约 100MB;留足余量) */\nconst MAX_BINARY_BYTES = 400 * 1024 * 1024;\nconst MANIFEST_TIMEOUT_MS = 15_000;\nconst DOWNLOAD_TIMEOUT_MS = 10 * 60_000;\n\nexport interface FetchManifestResult {\n manifest?: UpdateManifest;\n /** 校验失败的说明 */\n error?: string;\n raw?: Buffer;\n}\n\n/**\n * 拉取并校验清单:\n * 1. GET `<url>` 与 `<url>.sig`\n * 2. Ed25519 验签(对原始字节)\n * 3. JSON 解析 + 字段/URL/哈希校验\n */\nexport async function fetchManifest(\n url: string,\n publicKey: KeyObject,\n opts: { allowInsecureUrl?: boolean } = {},\n): Promise<FetchManifestResult> {\n let raw: Buffer;\n let sig: string;\n try {\n const [mRes, sRes] = await Promise.all([\n fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) }),\n fetch(`${url}.sig`, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) }),\n ]);\n if (!mRes.ok) return { error: `清单请求失败 HTTP ${mRes.status}` };\n if (!sRes.ok) return { error: `签名请求失败 HTTP ${sRes.status}` };\n raw = Buffer.from(await mRes.arrayBuffer());\n sig = (await sRes.text()).trim();\n } catch (err) {\n return { error: `拉取清单失败:${(err as Error).message}` };\n }\n\n if (!sig) return { error: \"签名文件为空\" };\n if (!verifySignature(raw, sig, publicKey)) {\n return { error: \"清单签名校验失败(公钥不匹配或内容被篡改)\" };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw.toString(\"utf-8\"));\n } catch (err) {\n return { error: `清单不是合法 JSON:${(err as Error).message}` };\n }\n const { manifest, error } = parseManifest(parsed, opts);\n if (!manifest) return { error };\n return { manifest, raw };\n}\n\n/**\n * 下载产物到 `destPath`,边下边校验 sha256 与大小。返回实际字节数。\n */\nexport async function downloadBinary(\n bin: UpdateBinary,\n destPath: string,\n log: (msg: string) => void = () => {},\n): Promise<{ bytes?: number; error?: string }> {\n const partPath = `${destPath}.part`;\n fs.mkdirSync(path.dirname(destPath), { recursive: true });\n fs.rmSync(partPath, { force: true });\n\n let res: Response;\n try {\n res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });\n } catch (err) {\n return { error: `下载失败:${(err as Error).message}` };\n }\n if (!res.ok) return { error: `下载失败 HTTP ${res.status}` };\n if (!res.body) return { error: \"下载响应没有 body\" };\n\n const expected = bin.size && bin.size > 0 ? bin.size : undefined;\n const limit = expected ? Math.max(expected * 2, 1024 * 1024) : MAX_BINARY_BYTES;\n const hash = createHash(\"sha256\");\n let bytes = 0;\n\n try {\n const source = Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]);\n await pipeline(\n source,\n async function* (chunks: AsyncIterable<Buffer>) {\n for await (const chunk of chunks) {\n bytes += chunk.length;\n if (bytes > limit) throw new Error(`产物超过大小上限(>${limit} 字节)`);\n hash.update(chunk);\n yield chunk;\n }\n },\n fs.createWriteStream(partPath, { mode: 0o755 }),\n );\n } catch (err) {\n fs.rmSync(partPath, { force: true });\n return { error: `下载中断:${(err as Error).message}` };\n }\n\n const actual = hash.digest(\"hex\");\n if (actual !== bin.sha256) {\n fs.rmSync(partPath, { force: true });\n return { error: `SHA-256 不匹配(期望 ${bin.sha256.slice(0, 12)}…,实际 ${actual.slice(0, 12)}…)` };\n }\n if (expected && bytes !== expected) {\n fs.rmSync(partPath, { force: true });\n return { error: `产物大小不匹配(期望 ${expected},实际 ${bytes})` };\n }\n\n fs.rmSync(destPath, { force: true });\n fs.renameSync(partPath, destPath);\n fs.chmodSync(destPath, 0o755);\n log(`已下载 ${path.basename(destPath)}(${Math.round(bytes / 1024 / 1024)}MB,校验通过)`);\n return { bytes };\n}\n\n/**\n * 安装前自检:跑一次 `--version`,输出必须包含清单版本。\n * 防住「架构不匹配 / 包损坏 / 误标版本」三类事故。\n */\nexport function selfCheck(binPath: string, expectedVersion: string): { ok: boolean; error?: string } {\n try {\n const r = spawnSync(binPath, [\"--version\"], {\n timeout: 20_000,\n encoding: \"utf-8\",\n env: { ...process.env, ZQAGENT_AUTO_UPDATE: \"0\" },\n });\n if (r.error) return { ok: false, error: `执行自检失败:${r.error.message}` };\n if (r.status !== 0) return { ok: false, error: `自检退出码 ${r.status}:${(r.stderr || \"\").trim()}` };\n const out = `${r.stdout ?? \"\"}${r.stderr ?? \"\"}`;\n if (!out.includes(expectedVersion)) {\n return { ok: false, error: `自检版本不符(期望 ${expectedVersion},输出:${out.trim().slice(0, 120)})` };\n }\n return { ok: true };\n } catch (err) {\n return { ok: false, error: `自检异常:${(err as Error).message}` };\n }\n}\n","/**\n * 极简 semver 解析与比较(不引入 semver 依赖)。\n *\n * 只处理节点更新场景需要的子集:`major.minor.patch[-prerelease]`,\n * 允许前缀 `v`,忽略 build metadata(`+xxx`)。解析失败返回 null。\n */\n\nexport interface SemVer {\n major: number;\n minor: number;\n patch: number;\n /** 预发布标识(`beta.1` → [\"beta\", 1]);正式版为空数组 */\n pre: (string | number)[];\n}\n\nconst PATTERN = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/;\n\nexport function parseVersion(input: string): SemVer | null {\n const m = PATTERN.exec(input.trim());\n if (!m) return null;\n const pre = m[4]\n ? m[4].split(\".\").map((part) => (/^\\d+$/.test(part) ? Number(part) : part))\n : [];\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n pre,\n };\n}\n\n/** 比较预发布标识:数字段按数值比较,字符串按字典序,数字段低于字符串段 */\nfunction comparePre(a: (string | number)[], b: (string | number)[]): number {\n // 无预发布标识 = 正式版,优先级高于任何预发布\n if (a.length === 0 && b.length === 0) return 0;\n if (a.length === 0) return 1;\n if (b.length === 0) return -1;\n for (let i = 0; i < Math.max(a.length, b.length); i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined) return -1;\n if (y === undefined) return 1;\n if (typeof x === \"number\" && typeof y === \"number\") {\n if (x !== y) return x < y ? -1 : 1;\n } else if (typeof x === \"number\") {\n return -1;\n } else if (typeof y === \"number\") {\n return 1;\n } else if (x !== y) {\n return x < y ? -1 : 1;\n }\n }\n return 0;\n}\n\n/** 返回 -1 / 0 / 1;任一版本无法解析时按「视为相同」返回 0(调用方需先校验) */\nexport function compareVersions(a: string, b: string): number {\n const va = parseVersion(a);\n const vb = parseVersion(b);\n if (!va || !vb) return 0;\n if (va.major !== vb.major) return va.major < vb.major ? -1 : 1;\n if (va.minor !== vb.minor) return va.minor < vb.minor ? -1 : 1;\n if (va.patch !== vb.patch) return va.patch < vb.patch ? -1 : 1;\n return comparePre(va.pre, vb.pre);\n}\n\n/** a 是否比 b 新 */\nexport function isNewer(a: string, b: string): boolean {\n return compareVersions(a, b) > 0;\n}\n","/**\n * 节点更新清单(manifest)的类型与校验。\n *\n * 清单由发布流程生成,托管在静态目录(默认\n * `https://agent.cd-zq.com/downloads/zqagent/stable.json`),\n * 与同名 `.sig`(Ed25519 签名)配对使用。\n */\nimport { parseVersion } from \"./version.js\";\n\n/** 单个平台的产物描述 */\nexport interface UpdateBinary {\n /** 绝对 https 地址(或显式允许的 http) */\n url: string;\n /** 小写 hex 的 SHA-256 */\n sha256: string;\n /** 字节数(可选;用于下载上限与进度) */\n size?: number;\n}\n\nexport interface UpdateManifest {\n /** 新版本号(bare semver,如 0.2.2) */\n version: string;\n /** 允许自动更新的最低版本:低于它的节点拒绝升级(防止跨破坏性变更) */\n minimumVersion?: string;\n notes?: string;\n publishedAt?: string;\n channel?: string;\n /** 平台键(见 platformKey)→ 产物 */\n binaries: Record<string, UpdateBinary>;\n}\n\n/** 产物名与 build_cli_binary.mjs 的 DISPLAY_PLATFORM 保持一致:macos / linux / windows */\nconst PLATFORM_NAMES: Record<string, string> = {\n darwin: \"macos\",\n linux: \"linux\",\n win32: \"windows\",\n};\n\n/**\n * 构建期注入的 libc 变体标记:普通产物为空,glibc 2.17 兼容产物为 `glibc217`。\n *\n * 由 build_cli_binary.mjs 在 `--libc glibc-217` 时传入环境变量,tsup 在打包时\n * 把 `process.env.ZQAGENT_LIBC_VARIANT` 替换成字面量,因此运行时不受环境影响。\n */\nconst LIBC_VARIANT = process.env.ZQAGENT_LIBC_VARIANT ?? \"\";\n\n/** 当前进程对应的清单键,如 `macos-arm64`、`linux-x64`、`linux-x64-glibc217` */\nexport function platformKey(\n platform: string = process.platform,\n arch: string = process.arch,\n): string {\n const base = `${PLATFORM_NAMES[platform] ?? platform}-${arch}`;\n return LIBC_VARIANT ? `${base}-${LIBC_VARIANT}` : base;\n}\n\nconst SHA256_HEX = /^[0-9a-f]{64}$/;\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\n/**\n * 校验并规范化清单。允许 http 仅当调用方显式放行(内网自测)。\n */\nexport function parseManifest(\n raw: unknown,\n opts: { allowInsecureUrl?: boolean } = {},\n): { manifest?: UpdateManifest; error?: string } {\n if (!isRecord(raw)) return { error: \"清单不是 JSON 对象\" };\n\n const version = typeof raw.version === \"string\" ? raw.version.trim() : \"\";\n if (!parseVersion(version)) return { error: `清单 version 非法:${String(raw.version)}` };\n\n if (raw.minimumVersion !== undefined) {\n if (typeof raw.minimumVersion !== \"string\" || !parseVersion(raw.minimumVersion)) {\n return { error: `清单 minimumVersion 非法:${String(raw.minimumVersion)}` };\n }\n }\n\n if (!isRecord(raw.binaries)) return { error: \"清单缺少 binaries\" };\n const binaries: Record<string, UpdateBinary> = {};\n for (const [key, value] of Object.entries(raw.binaries)) {\n if (!isRecord(value)) return { error: `binaries.${key} 不是对象` };\n const url = typeof value.url === \"string\" ? value.url.trim() : \"\";\n const sha256 = typeof value.sha256 === \"string\" ? value.sha256.trim().toLowerCase() : \"\";\n if (!url) return { error: `binaries.${key} 缺少 url` };\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return { error: `binaries.${key}.url 非法:${url}` };\n }\n if (parsed.protocol !== \"https:\" && !opts.allowInsecureUrl) {\n return { error: `binaries.${key}.url 必须是 https:${url}` };\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n return { error: `binaries.${key}.url 协议不支持:${url}` };\n }\n if (!SHA256_HEX.test(sha256)) {\n return { error: `binaries.${key}.sha256 需为 64 位小写 hex` };\n }\n const size = typeof value.size === \"number\" && value.size > 0 ? value.size : undefined;\n binaries[key] = { url, sha256, ...(size ? { size } : {}) };\n }\n if (Object.keys(binaries).length === 0) return { error: \"清单 binaries 为空\" };\n\n return {\n manifest: {\n version,\n ...(typeof raw.minimumVersion === \"string\" ? { minimumVersion: raw.minimumVersion } : {}),\n ...(typeof raw.notes === \"string\" ? { notes: raw.notes } : {}),\n ...(typeof raw.publishedAt === \"string\" ? { publishedAt: raw.publishedAt } : {}),\n ...(typeof raw.channel === \"string\" ? { channel: raw.channel } : {}),\n binaries,\n },\n };\n}\n","/**\n * 更新清单的签名校验(Ed25519)。\n *\n * 签名方案:对清单原始字节做 Ed25519 签名,签名以 base64 存于同名 `.sig` 文件。\n * 公钥通过 `ZQAGENT_UPDATE_PUBKEY` 提供(PEM 或 base64 的 SPKI DER)。\n * 未配置公钥时**禁用自动更新**——无法验签就不能替用户改可执行文件。\n */\nimport { createPublicKey, verify, type KeyObject } from \"node:crypto\";\n\n/** 从 PEM 或 base64(SPKI DER) 解析公钥;失败返回 null */\nexport function parsePublicKey(input: string): KeyObject | null {\n const text = input.trim();\n if (!text) return null;\n try {\n if (text.includes(\"BEGIN PUBLIC KEY\")) {\n return createPublicKey({ key: text, format: \"pem\" });\n }\n return createPublicKey({\n key: Buffer.from(text, \"base64\"),\n format: \"der\",\n type: \"spki\",\n });\n } catch {\n return null;\n }\n}\n\n/** 校验 `data` 的签名;任何异常都按失败处理 */\nexport function verifySignature(\n data: Buffer | Uint8Array,\n signatureBase64: string,\n publicKey: KeyObject,\n): boolean {\n try {\n const sig = Buffer.from(signatureBase64.trim(), \"base64\");\n if (sig.length !== 64) return false;\n return verify(null, data, publicKey, sig);\n } catch {\n return false;\n }\n}\n","/**\n * 运行形态与自更新能力判定。\n *\n * 只有「SEA 单文件 + 由 systemd/launchd 托管 + 配置了公钥」三者同时满足,\n * 才允许自动替换可执行文件并重启——否则退出后没人把它拉起来。\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\n/**\n * 当前版本:构建期注入(SEA/tsup define)优先,其次读包内 package.json,\n * 最后回退 \"dev\"。后两条让源码运行(tsx)也能显示真实版本。\n */\nexport function currentVersion(): string {\n const injected = process.env.ZQAGENT_VERSION?.trim();\n if (injected) return injected;\n try {\n const req = createRequire(import.meta.url);\n const pkg = req(\"../../package.json\") as { version?: string };\n if (pkg?.version) return pkg.version;\n } catch {\n // SEA 内取不到包信息,忽略\n }\n return \"dev\";\n}\n\n/**\n * 是否为 SEA 单文件运行。\n *\n * 判据优先取 execPath 的文件名:SEA 产物是 `zqagent`,经 node 运行的则是 `node`。\n * 构建期还会注入 `ZQAGENT_SEA=1`(见 tsup.sea.config.ts)作为权威标记。\n */\nexport function isSeaRuntime(): boolean {\n if (process.env.ZQAGENT_SEA === \"1\") return true;\n const base = path.basename(process.execPath).toLowerCase().replace(/\\.exe$/, \"\");\n return base !== \"node\" && base !== \"nodejs\";\n}\n\nexport type ServiceManager = \"systemd\" | \"launchd\";\n\n/** 未指定 ZQAGENT_SERVICE_NAME 时的默认 systemd unit 名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_NAME = \"zqagent\";\n\n/** 未指定 ZQAGENT_SERVICE_LABEL 时的默认 launchd 作业名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_LABEL = \"com.zqagent.node\";\n\n/**\n * 从 `/proc/self/cgroup` 的内容判定进程是否属于某个 systemd unit。\n *\n * 抽取为纯函数以便测试。兼容 cgroup v1(`1:name=systemd:/system.slice/x.service`)\n * 与 v2(`0::/system.slice/x.service`)两种行格式。\n *\n * 只认路径中**独立成段**的 `<serviceName>.service`:`my-zqagent.service`、\n * `zqagent.service.bak` 这类名字相近的 unit 不会被误判成 `zqagent.service`。\n */\nexport function serviceManagerFromCgroup(\n cgroup: string,\n serviceName: string = DEFAULT_SERVICE_NAME,\n): ServiceManager | null {\n const unitName = `${serviceName}.service`;\n for (const line of cgroup.split(\"\\n\")) {\n // 路径在最后一个冒号之后;v1 的 `name=systemd` 不会干扰\n const idx = line.lastIndexOf(\":\");\n if (idx < 0) continue;\n const unitPath = line.slice(idx + 1).trim();\n if (unitPath.split(\"/\").includes(unitName)) return \"systemd\";\n }\n return null;\n}\n\n/** launchd 作业所在域:root 装的是 LaunchDaemon(system),普通用户是 LaunchAgent(gui/<uid>) */\nfunction launchdDomain(): string {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n return uid === 0 ? \"system\" : `gui/${uid}`;\n}\n\nfunction launchdLabel(): string {\n return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;\n}\n\n/** 与 install.sh 写入的位置保持一致 */\nfunction launchdPlistPath(): string {\n const label = launchdLabel();\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;\n return path.join(os.homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\n/**\n * 从 `launchctl print <domain>/<label>` 的输出判定是否为 launchd 托管。\n *\n * macOS 没有 cgroup 可用,改用作业描述里的两处信息:\n * - `pid = N` 与当前进程一致 → 本进程就是该作业拉起的;\n * - `program = /path` 与 execPath 相同 → 服务运行的正是这个可执行文件,\n * 在交互 shell 里执行 `zqagent update` 时靠这条判定(Linux 靠 cgroup 归属)。\n *\n * `sea` 为 false(npm/source 形态)时作业存在即算托管:这种形态本来就不允许\n * 自替换二进制(见 selfUpdateCapability),这里只影响 `update --status` 的显示。\n */\nexport function serviceManagerFromLaunchctl(\n output: string,\n pid: number,\n execPath: string,\n sea = true,\n): ServiceManager | null {\n const pidMatch = /^\\s*pid = (\\d+)\\s*$/m.exec(output);\n if (pidMatch && Number(pidMatch[1]) === pid) return \"launchd\";\n\n const program = /^\\s*program = (.+?)\\s*$/m.exec(output);\n if (program) {\n const shown = program[1].replace(/^\"|\"$/g, \"\");\n if (path.resolve(shown) === path.resolve(execPath)) return \"launchd\";\n }\n\n if (!sea && /^\\s*state = /m.test(output)) return \"launchd\";\n return null;\n}\n\n/**\n * 解析 launchd plist 中 `EnvironmentVariables` 字典的键值。\n *\n * 只处理本脚本会写出的 `<key>K</key><string>V</string>` 形式;`<true/>` 之类的\n * 非字符串值忽略(更新配置用不到)。\n */\nexport function parseLaunchdEnvironment(xml: string): Record<string, string> {\n const out: Record<string, string> = {};\n const marker = xml.indexOf(\"<key>EnvironmentVariables</key>\");\n if (marker < 0) return out;\n const open = xml.indexOf(\"<dict>\", marker);\n if (open < 0) return out;\n const close = xml.indexOf(\"</dict>\", open);\n if (close < 0) return out;\n const body = xml.slice(open + \"<dict>\".length, close);\n for (const m of body.matchAll(/<key>([^<]+)<\\/key>\\s*<string>([^<]*)<\\/string>/g)) {\n out[m[1]] = m[2];\n }\n return out;\n}\n\n/**\n * 未注入 ZQAGENT_SERVICE_MANAGER 时的兜底检测。\n *\n * 服务进程的环境变量由 unit 注入,但运维在交互 shell 里手动执行\n * `zqagent update --status/--check/--now` 时没有这些变量——此前会被判成\n * 「未托管」,导致诊断结论错误、`--now` 直接拒绝执行。这里用 cgroup 归属\n * 兜底:Linux 上进程落在 `/<service>.service` 里,说明 systemd 确实会拉起它。\n *\n * 刻意不使用「/run/systemd/system 存在即视为托管」这类判据:那样任何在\n * systemd 机器上裸跑的进程都会通过自检,替换二进制后 `exit(70)` 却没人拉起,\n * 节点会直接下线。\n */\nfunction detectServiceManager(): ServiceManager | null {\n if (process.platform === \"darwin\") return detectLaunchd();\n if (process.platform !== \"linux\") return null;\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n return serviceManagerFromCgroup(\n fs.readFileSync(\"/proc/self/cgroup\", \"utf8\"),\n serviceName,\n );\n } catch {\n return null;\n }\n}\n\n/**\n * macOS 兜底:读取 launchd 作业描述,按 pid / program 判定(见\n * `serviceManagerFromLaunchctl`)。作业不存在、launchctl 不可用时返回 null。\n */\nfunction detectLaunchd(): ServiceManager | null {\n try {\n const res = spawnSync(\n \"launchctl\",\n [\"print\", `${launchdDomain()}/${launchdLabel()}`],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return null;\n return serviceManagerFromLaunchctl(\n res.stdout,\n process.pid,\n process.execPath,\n isSeaRuntime(),\n );\n } catch {\n return null;\n }\n}\n\n/**\n * 进程由哪个服务管理器托管;无托管返回 null。\n *\n * 优先采信 `deploy/service/install.sh` 注入的环境变量,缺失时在 Linux 上\n * 按 cgroup 归属兜底检测(见 `detectServiceManager`)。\n */\nexport function serviceManager(): ServiceManager | null {\n const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();\n if (v === \"systemd\" || v === \"launchd\") return v;\n return detectServiceManager();\n}\n\nexport function autoUpdateDisabled(): boolean {\n return process.env.ZQAGENT_AUTO_UPDATE?.trim() === \"0\";\n}\n\n/**\n * 解析 `systemctl show -p Environment --value` 的输出为键值表。\n *\n * 输出形如 `NODE_ENV=production PATH=/usr/local/bin:/usr/bin KEY=abc`。\n * 值里若含空格会被拆开——本函数只用于取公钥/通道/清单地址这类不含空格的\n * 变量,够用且无需处理 systemd 的引号规则。\n */\nexport function parseServiceEnvironment(raw: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const token of raw.trim().split(/\\s+/)) {\n const idx = token.indexOf(\"=\");\n if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);\n }\n return out;\n}\n\nlet cachedServiceEnv: Record<string, string> | null = null;\n\nfunction readServiceEnvironment(): Record<string, string> {\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n const res = spawnSync(\n \"systemctl\",\n [\"show\", serviceName, \"--property=Environment\", \"--value\", \"--no-pager\"],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return {};\n return parseServiceEnvironment(res.stdout);\n } catch {\n return {};\n }\n}\n\n/**\n * 读取 systemd unit 注入的环境变量。\n *\n * 服务进程拿得到这些变量,但运维在交互 shell 里手动执行 `zqagent update`\n * 时拿不到——公钥缺失会让自更新被判为不可用。这里按需从 unit 补齐,\n * 取不到(非 systemd、无 systemctl、超时)时返回 undefined。\n */\nexport function serviceEnvValue(key: string): string | undefined {\n if (process.platform === \"darwin\") {\n if (serviceManager() !== \"launchd\") return undefined;\n cachedLaunchdEnv ??= readLaunchdEnvironment();\n return cachedLaunchdEnv[key];\n }\n if (process.platform !== \"linux\") return undefined;\n if (serviceManager() !== \"systemd\") return undefined;\n cachedServiceEnv ??= readServiceEnvironment();\n return cachedServiceEnv[key];\n}\n\nlet cachedLaunchdEnv: Record<string, string> | null = null;\n\n/**\n * 读取 launchd plist 注入的环境变量。\n *\n * 与 systemd 的 `systemctl show -p Environment` 对应:干净 shell 里执行\n * `zqagent update --status` 时,公钥/通道/清单地址只能从服务定义里取回。\n */\nfunction readLaunchdEnvironment(): Record<string, string> {\n try {\n return parseLaunchdEnvironment(fs.readFileSync(launchdPlistPath(), \"utf8\"));\n } catch {\n return {};\n }\n}\n\nexport interface Capability {\n ok: boolean;\n /** 不满足时的可读原因(用于日志/`update --status`) */\n reason?: string;\n}\n\n/**\n * 自更新能力判定。`hasPublicKey` 由调用方传入(公钥只在验签时解析一次)。\n */\nexport function selfUpdateCapability(hasPublicKey: boolean): Capability {\n if (!isSeaRuntime()) {\n return {\n ok: false,\n reason: \"当前不是单文件(SEA)形态:请用 npm 升级(npm i -g @zqagent/cli@latest)\",\n };\n }\n if (!serviceManager()) {\n return {\n ok: false,\n reason: \"未被 systemd/launchd 托管:请用 deploy/service/install.sh 安装,或手动升级\",\n };\n }\n if (!hasPublicKey) {\n return { ok: false, reason: \"未配置 ZQAGENT_UPDATE_PUBKEY,无法验签,已禁用自动更新\" };\n }\n return { ok: true };\n}\n","/**\n * 原子替换与回滚。\n *\n * 替换策略:\n * - Unix:先复制到 exe 同目录的 `.new`,再 rename 覆盖(同目录内 rename 是原子的)。\n * 不能直接跨目录 rename:那样会把 inode 连同源目录的 SELinux 上下文(如 `var_lib_t`)\n * 一起搬进 `/usr/local/bin`,systemd 执行时会因类型不允许而 `203/EXEC`。\n * 在目标目录新建文件则可继承父目录的 `bin_t`,与 install.sh 的 `cp` 行为一致。\n * - Windows:运行中的 exe 不能覆盖,但允许改名——先把旧文件改成 `.old`,再落新文件。\n *\n * 备份统一放在 `<dataDir>/updates/backup/`(该目录一定可写),供 `update --rollback` 使用。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { currentVersion } from \"./platform.js\";\nimport { patchState, updatesDir, type UpdateState } from \"./state.js\";\n\nexport interface InstallOptions {\n dataDir: string;\n /** 当前正在运行的可执行文件(SEA 的 process.execPath) */\n execPath: string;\n /** 已下载并通过自检的新文件 */\n stagedPath: string;\n version: string;\n log?: (msg: string) => void;\n}\n\nfunction backupDir(dataDir: string): string {\n return path.join(updatesDir(dataDir), \"backup\");\n}\n\n/** 备份当前可执行文件,返回备份路径 */\nexport function backupCurrent(dataDir: string, execPath: string, version: string): string {\n const dir = backupDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n const target = path.join(dir, `zqagent-${version}${process.platform === \"win32\" ? \".exe\" : \"\"}`);\n fs.copyFileSync(execPath, target);\n fs.chmodSync(target, 0o755);\n return target;\n}\n\n/**\n * 用 `stagedPath` 替换 `execPath`。\n * 调用方负责在这之后退出进程,让服务管理器拉起新版本。\n */\nexport function replaceExecutable(execPath: string, stagedPath: string): void {\n if (process.platform === \"win32\") {\n const oldPath = `${execPath}.old`;\n fs.rmSync(oldPath, { force: true });\n fs.renameSync(execPath, oldPath);\n fs.copyFileSync(stagedPath, execPath);\n fs.chmodSync(execPath, 0o755);\n return;\n }\n\n // 始终在目标目录落一个新文件:跨目录 rename 会保留源端的 SELinux 上下文\n const tmp = `${execPath}.new`;\n fs.rmSync(tmp, { force: true });\n try {\n fs.copyFileSync(stagedPath, tmp);\n fs.chmodSync(tmp, 0o755);\n fs.renameSync(tmp, execPath);\n } catch (err) {\n fs.rmSync(tmp, { force: true });\n throw err;\n }\n fs.rmSync(stagedPath, { force: true });\n}\n\n/**\n * 执行安装(备份 → 替换 → 记录状态)。不负责退出进程。\n * 失败时抛错,调用方保留 staged 文件以便下次重试。\n */\nexport function installStaged(opts: InstallOptions): UpdateState {\n const log = opts.log ?? (() => {});\n const from = currentVersion();\n\n log(`正在安装 v${opts.version}(当前 v${from})`);\n const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);\n log(`已备份当前版本到 ${path.basename(backupPath)}`);\n\n replaceExecutable(opts.execPath, opts.stagedPath);\n\n return patchState(opts.dataDir, {\n stagedVersion: undefined,\n stagedPath: undefined,\n pendingVersion: opts.version,\n previousVersion: from,\n backupVersion: from,\n backupPath,\n installedExecPath: opts.execPath,\n lastError: undefined,\n failureCount: 0,\n failedVersion: undefined,\n });\n}\n\n/** 用备份覆盖当前可执行文件并返回被恢复的版本;无备份时返回 null */\nexport function rollback(dataDir: string, execPath: string, log: (msg: string) => void = () => {}): string | null {\n const state = readRollbackTarget(dataDir);\n if (!state?.backupPath || !state.backupVersion) return null;\n if (!fs.existsSync(state.backupPath)) return null;\n\n log(`正在回滚到 v${state.backupVersion}`);\n if (process.platform === \"win32\") {\n fs.rmSync(`${execPath}.old`, { force: true });\n fs.renameSync(execPath, `${execPath}.old`);\n fs.copyFileSync(state.backupPath, execPath);\n fs.chmodSync(execPath, 0o755);\n } else {\n const tmp = `${execPath}.rollback`;\n fs.copyFileSync(state.backupPath, tmp);\n fs.chmodSync(tmp, 0o755);\n fs.renameSync(tmp, execPath);\n }\n\n patchState(dataDir, {\n pendingVersion: state.backupVersion,\n previousVersion: currentVersion(),\n // 不让自动更新立刻把刚回滚掉的版本再装回来\n skippedVersion: currentVersion(),\n backupPath: undefined,\n backupVersion: undefined,\n });\n return state.backupVersion;\n}\n\nfunction readRollbackTarget(dataDir: string): Pick<UpdateState, \"backupPath\" | \"backupVersion\"> | null {\n try {\n const raw = fs.readFileSync(path.join(updatesDir(dataDir), \"state.json\"), \"utf-8\");\n const s = JSON.parse(raw) as UpdateState;\n if (s.backupPath && s.backupVersion) return s;\n } catch {\n // 无状态\n }\n return null;\n}\n","/**\n * 更新状态持久化:`<dataDir>/updates/state.json`\n *\n * 记录最近一次检查、已暂存待安装的版本、失败计数与回滚用的备份路径。\n * 文件损坏或不可读时按「无状态」处理,不阻塞节点启动。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport interface UpdateState {\n lastCheckAt?: string;\n /** 最近一次失败原因(成功检查后清空) */\n lastError?: string;\n /** 同一版本的连续失败次数;达到阈值后放弃该版本 */\n failureCount?: number;\n failedVersion?: string;\n /** 已下载并通过校验、等待空闲安装的版本 */\n stagedVersion?: string;\n stagedPath?: string;\n /** 已替换文件、等待新进程启动确认的版本 */\n pendingVersion?: string;\n /** 回滚备份 */\n backupVersion?: string;\n backupPath?: string;\n /** 上次成功启动的版本(用于判断更新是否生效) */\n runningVersion?: string;\n /** 上次更新前运行的版本 */\n previousVersion?: string;\n /** 被替换的可执行文件路径(回滚时用,避免从 node 进程误推断成 node 自身) */\n installedExecPath?: string;\n /** 被回滚掉的版本:自动更新会跳过它,直到发布更新的版本(手动 --now 不受限) */\n skippedVersion?: string;\n}\n\nexport function updatesDir(dataDir: string): string {\n return path.join(dataDir, \"updates\");\n}\n\nfunction statePath(dataDir: string): string {\n return path.join(updatesDir(dataDir), \"state.json\");\n}\n\nexport function loadState(dataDir: string): UpdateState {\n try {\n const raw = fs.readFileSync(statePath(dataDir), \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null) return parsed as UpdateState;\n } catch {\n // 不存在或损坏:按空状态处理\n }\n return {};\n}\n\nexport function saveState(dataDir: string, state: UpdateState): void {\n const dir = updatesDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n const file = statePath(dataDir);\n const tmp = `${file}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(state, null, 2), \"utf-8\");\n fs.renameSync(tmp, file);\n}\n\n/** 合并写入(读 → 改 → 写),避免并发覆盖 */\nexport function patchState(dataDir: string, patch: Partial<UpdateState>): UpdateState {\n const next = { ...loadState(dataDir), ...patch };\n saveState(dataDir, next);\n return next;\n}\n"],"mappings":"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","names":["fs","path","import_node_crypto","fs","path","import_node_child_process","fs","path","fs","path","currentVersion","resolve","verify"]}
1
+ {"version":3,"sources":["../../../src/update/controller.ts","../../../../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.3.23_jiti@1.21.7_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.12_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","../../../src/update/downloader.ts","../../../src/update/version.ts","../../../src/update/manifest.ts","../../../src/update/signature.ts","../../../src/update/platform.ts","../../../src/update/installer.ts","../../../src/update/state.ts"],"sourcesContent":["/**\n * 自动更新调度器。\n *\n * 生命周期:`start()` 后按「启动延迟 + 固定间隔」拉清单;发现新版本即静默下载并自检,\n * 之后**等到节点空闲**(连续 60s 没有 running / waiting 会话)才替换可执行文件并退出,\n * 由 systemd/launchd 拉起新版本。\n *\n * 所有重活都在后台进行,任何失败只记日志,不影响节点服务。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fetchManifest, downloadBinary, selfCheck } from \"./downloader.js\";\nimport { platformKey } from \"./manifest.js\";\nimport { compareVersions, parseVersion } from \"./version.js\";\nimport { currentVersion as currentVersionOf, isSeaRuntime, serviceEnvValue, serviceManager, selfUpdateCapability } from \"./platform.js\";\nimport { parsePublicKey } from \"./signature.js\";\nimport { installStaged, rollback as rollbackExecutable } from \"./installer.js\";\nimport { loadState, patchState, updatesDir, type UpdateState } from \"./state.js\";\n\nconst DEFAULT_BASE = \"https://agent.cd-zq.com/downloads/zqagent\";\n\n/**\n * 安装上下文。\n *\n * `excludeSessionId`:发起这次更新的会话。它自己通常一直是 running,\n * 必须从「忙」的判定里排除,否则等待空闲永远不成立(见 `waitForIdle`)。\n */\nexport interface InstallContext {\n excludeSessionId?: string;\n /**\n * 是否要求进程已启动足够久(默认 true)。\n *\n * 这道闸门是给**自动**更新用的:避免刚起来就因清单抖动反复重启、\n * 或在启动过程中替换二进制。用户显式发起安装时传 false——\n * 「点了更新却要再等 10 分钟」同样会让用户以为没反应。\n */\n requireUptime?: boolean;\n}\n\nexport interface UpdaterOptions {\n dataDir: string;\n /** 是否空闲(无 running / waiting 会话;可排除发起更新的会话) */\n isIdle: (excludeSessionId?: string) => boolean;\n /** 替换前关闭服务(落盘账本、断连) */\n prepareExit?: () => void;\n log?: (msg: string) => void;\n /** 清单地址覆盖(测试/内网) */\n url?: string;\n}\n\nexport interface UpdaterStatus {\n enabled: boolean;\n reason?: string;\n currentVersion: string;\n channel: string;\n manifestUrl: string;\n serviceManager: string | null;\n lastCheckAt?: string;\n stagedVersion?: string;\n pendingVersion?: string;\n previousVersion?: string;\n backupVersion?: string;\n lastError?: string;\n}\n\n/** 版本检查结果(`self_update --action check` 用;不下载、不写状态) */\nexport interface UpdateInspect {\n currentVersion: string;\n channel: string;\n manifestUrl: string;\n /** 清单里是否有比当前更新的版本 */\n updateAvailable: boolean;\n /** 清单里的最新版本(拉不到清单时缺省) */\n latestVersion?: string;\n notes?: string;\n /** 不可更新的原因(已是最新 / 未启用 / 需手动升级…) */\n reason?: string;\n /** 低于清单要求的最低版本时给出该下限 */\n manualUpgradeRequired?: string;\n}\n\n/** 远端触发安装的回执(ack 先回,安装本身在后台跑) */\nexport interface UpdateInstallStart {\n /** 是否已受理(false 表示无需更新或不可更新) */\n accepted: boolean;\n fromVersion: string;\n toVersion?: string;\n message: string;\n}\n\n/**\n * 读取更新配置:进程环境优先,缺失时回落到 systemd unit 注入的变量。\n *\n * 后台服务天然带全部变量;运维在交互 shell 里执行 `zqagent update` 时\n * 只有回落这一条路,否则公钥/通道/清单地址都会被判成「未配置」。\n */\nfunction env(key: string): string | undefined {\n const v = process.env[key]?.trim();\n if (v) return v;\n const fromService = serviceEnvValue(key)?.trim();\n return fromService ? fromService : undefined;\n}\n\nfunction num(key: string, fallback: number): number {\n const n = Number(env(key));\n return Number.isFinite(n) && n >= 0 ? n : fallback;\n}\n\nexport function updaterChannel(): string {\n return env(\"ZQAGENT_UPDATE_CHANNEL\") ?? \"stable\";\n}\n\nexport function updaterUrl(channel = updaterChannel()): string {\n return env(\"ZQAGENT_UPDATE_URL\") ?? `${DEFAULT_BASE}/${channel}.json`;\n}\n\n/** 更新间隔:默认 6h,±15% 抖动,避免整批节点同时打同一台静态服务器 */\nfunction nextIntervalMs(): number {\n const base = num(\"ZQAGENT_UPDATE_INTERVAL_MS\", 6 * 60 * 60 * 1000);\n const jitter = base * 0.15 * (Math.random() * 2 - 1);\n return Math.max(60_000, Math.round(base + jitter));\n}\n\n/**\n * 清单 → 检查结论(纯函数,便于单测)。\n *\n * 只有「清单版本更高、且当前版本满足 minimumVersion」才算可更新;\n * 其余情况都要给出 `reason`,让远端 UI 能直接展示原因而不是靠猜。\n */\nexport function evaluateManifest(\n base: { currentVersion: string; channel: string; manifestUrl: string },\n manifest: { version: string; minimumVersion?: string; notes?: string },\n): UpdateInspect {\n if (!parseVersion(base.currentVersion) ||\n compareVersions(manifest.version, base.currentVersion) <= 0) {\n return {\n ...base,\n updateAvailable: false,\n latestVersion: manifest.version,\n reason: `已是最新版本(v${base.currentVersion})`,\n };\n }\n if (manifest.minimumVersion &&\n compareVersions(base.currentVersion, manifest.minimumVersion) < 0) {\n return {\n ...base,\n updateAvailable: false,\n latestVersion: manifest.version,\n manualUpgradeRequired: manifest.minimumVersion,\n reason: `当前版本过低,需手动升级到 >= ${manifest.minimumVersion}`,\n };\n }\n return {\n ...base,\n updateAvailable: true,\n latestVersion: manifest.version,\n ...(manifest.notes ? { notes: manifest.notes } : {}),\n };\n}\n\nexport class UpdateController {\n private readonly log: (msg: string) => void;\n private timer: NodeJS.Timeout | null = null;\n private stopped = false;\n private checking = false;\n private installing = false;\n /**\n * 进行中的「等空闲」被要求立刻执行。\n *\n * 用户点「立即更新」时若已有一个在等空闲的任务,不能回一句「已有任务在进行中」\n * 就完事——那个任务可能正因为某个 running 会话而永远等下去。置位后\n * `waitForIdle` 下一拍直接放行。\n */\n private forceRequested = false;\n private startedAt = Date.now();\n private lastCheckAt?: string;\n\n constructor(\n private readonly opts: UpdaterOptions,\n private readonly currentVersion = currentVersionOf(),\n ) {\n this.log = opts.log ?? ((msg) => console.log(`[update] ${msg}`));\n }\n\n /** 公钥(未配置时禁用自更新:无法验签就不能替换可执行文件) */\n private get publicKey() {\n return parsePublicKey(env(\"ZQAGENT_UPDATE_PUBKEY\") ?? \"\");\n }\n\n private get autoUpdateOff(): boolean {\n return env(\"ZQAGENT_AUTO_UPDATE\") === \"0\";\n }\n\n get enabled(): boolean {\n return !this.autoUpdateOff && selfUpdateCapability(Boolean(this.publicKey)).ok;\n }\n\n private manifestUrl(): string {\n return this.opts.url ?? updaterUrl();\n }\n\n /** 启动调度并核对上次更新结果 */\n start(): void {\n this.reconcile();\n if (this.autoUpdateOff) {\n this.log(\"自动更新已关闭(ZQAGENT_AUTO_UPDATE=0)\");\n return;\n }\n const cap = selfUpdateCapability(Boolean(this.publicKey));\n if (!cap.ok) {\n this.log(`自动更新未启用:${cap.reason}`);\n return;\n }\n this.log(\n `自动更新已启用(v${this.currentVersion},通道 ${updaterChannel()},` +\n `由 ${serviceManager()} 托管)`,\n );\n this.schedule(num(\"ZQAGENT_UPDATE_INITIAL_DELAY_MS\", 5 * 60 * 1000));\n }\n\n stop(): void {\n this.stopped = true;\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n }\n\n /** 启动时对账:确认上次更新是否成功、清理失效的暂存/备份状态 */\n private reconcile(): void {\n const state = loadState(this.opts.dataDir);\n const current = this.currentVersion;\n\n if (state.stagedPath && !fs.existsSync(state.stagedPath)) {\n patchState(this.opts.dataDir, { stagedVersion: undefined, stagedPath: undefined });\n }\n\n const patch: Partial<UpdateState> = { runningVersion: current };\n if (state.pendingVersion && state.pendingVersion === current) {\n patch.pendingVersion = undefined;\n this.log(`已更新到 v${current}(此前 v${state.previousVersion ?? \"?\"})`);\n } else if (state.pendingVersion) {\n // 新版本没能起来(被回滚或被服务管理器恢复):保留备份信息,仅提示\n this.log(\n `注意:上次安装 v${state.pendingVersion} 未生效,当前仍为 v${current}` +\n (state.backupPath ? \";可执行 update --rollback 恢复备份\" : \"\"),\n );\n patch.pendingVersion = undefined;\n }\n patchState(this.opts.dataDir, patch);\n\n // Windows:清理上次替换留下的 .old\n if (process.platform === \"win32\") {\n fs.rmSync(`${process.execPath}.old`, { force: true });\n }\n }\n\n private schedule(delayMs: number): void {\n if (this.stopped) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.tick();\n }, delayMs);\n this.timer.unref();\n }\n\n private async tick(): Promise<void> {\n try {\n await this.check();\n } catch (err) {\n this.log(`检查更新失败:${(err as Error).message}`);\n }\n this.schedule(nextIntervalMs());\n }\n\n /**\n * 只读检查:拉清单比对版本,不下载、不改 state。\n *\n * 供远端 `self_update --action check` 与 UI 展示用;与定时器 `check()` 不同,\n * 这里不加 `checking` 锁、不写 `lastCheckAt`,因此不会干扰自动更新流程。\n */\n async inspect(): Promise<UpdateInspect> {\n const base = {\n currentVersion: this.currentVersion,\n channel: updaterChannel(),\n manifestUrl: this.manifestUrl(),\n };\n const key = this.publicKey;\n const cap = selfUpdateCapability(Boolean(key));\n if (!cap.ok) {\n return { ...base, updateAvailable: false, reason: `自更新不可用:${cap.reason}` };\n }\n const res = await fetchManifest(base.manifestUrl, key!, {\n allowInsecureUrl: env(\"ZQAGENT_UPDATE_INSECURE\") === \"1\",\n });\n if (!res.manifest) {\n return {\n ...base,\n updateAvailable: false,\n reason: `更新检查失败:${res.error ?? \"清单校验失败\"}`,\n };\n }\n return evaluateManifest(base, res.manifest);\n }\n\n /**\n * 远端触发安装:先同步给出结论(受理/无需更新),安装本身在后台跑。\n *\n * 必须「先回执、后安装」:安装成功后进程会 `exit(70)` 交给 systemd 拉起新版本,\n * 如果等到安装完成再回执,回执永远发不出去(表现为网关超时)。\n *\n * 复用定时器同一条 `check()` 路径:验签、下载、自检、失败计数、安装后自检与\n * 回滚保护全都一致,只是把 `installMode` 设为 `force` 时不等待空闲。\n */\n async requestInstall(force = false, opts: InstallContext = {}): Promise<UpdateInstallStart> {\n const fromVersion = this.currentVersion;\n const cap = selfUpdateCapability(Boolean(this.publicKey));\n if (!cap.ok) {\n return { accepted: false, fromVersion, message: `自更新不可用:${cap.reason}` };\n }\n\n const info = await this.inspect();\n if (!info.updateAvailable) {\n return {\n accepted: false,\n fromVersion,\n ...(info.latestVersion ? { toVersion: info.latestVersion } : {}),\n message: info.reason ?? \"已是最新版本\",\n };\n }\n\n // 已有任务在进行中:force 时必须**打断它的等待**,否则「立即更新」名不副实\n if (this.checking || this.installing) {\n if (force) this.forceRequested = true;\n return {\n accepted: true,\n fromVersion,\n toVersion: info.latestVersion,\n message: force\n ? `已请求立即安装 v${info.latestVersion}(打断此前的等待)`\n : `已有更新任务在进行中(目标 v${info.latestVersion}),可在稍后用 status 查看结果`,\n };\n }\n\n // 后台执行(ignoreSkip=true:用户显式触发,可越过「已回滚版本跳过」)\n void this.check(force, false, true, {\n ...opts,\n // 用户显式请求:跳过「启动满 10 分钟」这道只为自动更新设的闸门\n requireUptime: false,\n }).catch((err) => {\n this.recordFailure(info.latestVersion, `安装失败:${(err as Error).message}`);\n });\n return {\n accepted: true,\n fromVersion,\n toVersion: info.latestVersion,\n message: force\n ? `开始安装 v${info.latestVersion}(立即重启)`\n : `开始安装 v${info.latestVersion}(等待节点空闲后重启)`,\n };\n }\n\n /**\n * 检查并准备更新;`installMode` 为 true 时下载后立即安装(不再等空闲)。\n * 返回可读结论,供 CLI `update --check` 使用。\n */\n async check(\n installMode = false,\n dryRun = false,\n ignoreSkip = false,\n ctx: InstallContext = {},\n ): Promise<string> {\n const key = this.publicKey;\n const cap = selfUpdateCapability(Boolean(key));\n if (!cap.ok) return `未启用自动更新:${cap.reason}`;\n if (this.checking) return \"已有检查在进行中\";\n this.checking = true;\n\n try {\n const current = this.currentVersion;\n const url = this.manifestUrl();\n this.log(`检查更新:${url}`);\n const res = await fetchManifest(url, key!, {\n allowInsecureUrl: env(\"ZQAGENT_UPDATE_INSECURE\") === \"1\",\n });\n if (!res.manifest) {\n this.recordFailure(undefined, res.error ?? \"清单校验失败\");\n return `更新检查失败:${res.error}`;\n }\n\n const manifest = res.manifest;\n this.lastCheckAt = new Date().toISOString();\n patchState(this.opts.dataDir, { lastCheckAt: this.lastCheckAt });\n\n if (!parseVersion(current) || compareVersions(manifest.version, current) <= 0) {\n patchState(this.opts.dataDir, {\n stagedVersion: undefined,\n stagedPath: undefined,\n lastError: undefined,\n });\n this.log(`已是最新版本(v${current})`);\n return `已是最新版本(v${current})`;\n }\n\n if (manifest.minimumVersion && compareVersions(current, manifest.minimumVersion) < 0) {\n this.recordFailure(manifest.version, `当前版本过低,需手动升级到 >= ${manifest.minimumVersion}`);\n return `需要手动升级:当前 v${current} 低于最低要求 v${manifest.minimumVersion}`;\n }\n\n // 只检查不下载:`update --check` 用,避免为看版本号拉一个 100MB 的包\n if (dryRun) {\n const notes = manifest.notes ? `:${manifest.notes}` : \"\";\n return `发现新版本 v${manifest.version}(当前 v${current})${notes}`;\n }\n\n const state = loadState(this.opts.dataDir);\n\n // 用户回滚过这个版本:自动更新跳过,直到发布更新的版本\n if (!ignoreSkip && state.skippedVersion === manifest.version) {\n const msg = `v${manifest.version} 已被回滚,自动更新跳过(手动 update --now 可强制安装)`;\n this.log(msg);\n return msg;\n }\n if (state.skippedVersion && compareVersions(manifest.version, state.skippedVersion) > 0) {\n // 出现更新的版本,解除跳过\n patchState(this.opts.dataDir, { skippedVersion: undefined });\n }\n\n /*\n * 下面两处\"不更新\"都有正当理由,但后台定时器调用时没有调用方会读返回值:\n * 不写日志就等于完全静默。曾经有过节点连续几周每天检查、却因为清单里少了\n * 自己的平台键而一直停在旧版本,日志里只有「检查更新:…」一行,无从排查。\n */\n const maxFailures = num(\"ZQAGENT_UPDATE_MAX_FAILURES\", 3);\n if (state.failedVersion === manifest.version && (state.failureCount ?? 0) >= maxFailures) {\n const msg = `v${manifest.version} 已连续失败 ${state.failureCount} 次,跳过(等待下一版本)`;\n this.log(msg);\n return msg;\n }\n\n const platform = platformKey();\n const bin = manifest.binaries[platform];\n if (!bin) {\n const available = Object.keys(manifest.binaries).join(\", \") || \"(空)\";\n const msg = `清单没有当前平台(${platform})的产物;清单提供:${available}`;\n this.log(msg);\n return msg;\n }\n\n // 已暂存同一版本且文件有效 → 直接进入安装\n let stagedPath = state.stagedPath;\n if (!(state.stagedVersion === manifest.version && stagedPath && fs.existsSync(stagedPath))) {\n stagedPath = path.join(\n updatesDir(this.opts.dataDir),\n \"staged\",\n `zqagent-${manifest.version}${process.platform === \"win32\" ? \".exe\" : \"\"}`,\n );\n const dl = await downloadBinary(bin, stagedPath, this.log);\n if (dl.error) {\n this.recordFailure(manifest.version, dl.error);\n return `下载失败:${dl.error}`;\n }\n\n const selfTest = selfCheck(stagedPath, manifest.version);\n if (!selfTest.ok) {\n fs.rmSync(stagedPath, { force: true });\n this.recordFailure(manifest.version, selfTest.error ?? \"自检失败\");\n return `安装包自检失败:${selfTest.error}`;\n }\n\n patchState(this.opts.dataDir, {\n stagedVersion: manifest.version,\n stagedPath,\n lastError: undefined,\n failureCount: 0,\n failedVersion: undefined,\n });\n }\n\n this.log(\n `v${manifest.version} 已就绪${manifest.notes ? `:${manifest.notes}` : \"\"}`,\n );\n await this.installWhenIdle(manifest.version, stagedPath, installMode, ctx);\n return `v${manifest.version} 已就绪`;\n } finally {\n this.checking = false;\n }\n }\n\n private recordFailure(version: string | undefined, error: string): void {\n const state = loadState(this.opts.dataDir);\n const sameVersion = version !== undefined && state.failedVersion === version;\n patchState(this.opts.dataDir, {\n lastError: error,\n failedVersion: version ?? state.failedVersion,\n failureCount: sameVersion ? (state.failureCount ?? 0) + 1 : 1,\n });\n this.log(error);\n }\n\n /** 等待节点连续空闲后安装;`installMode` 立即安装 */\n private async installWhenIdle(\n version: string,\n stagedPath: string,\n installMode: boolean,\n ctx: InstallContext = {},\n ): Promise<void> {\n if (this.installing) return;\n this.installing = true;\n\n try {\n if (!installMode) {\n const idleMs = num(\"ZQAGENT_UPDATE_IDLE_MS\", 60 * 1000);\n this.log(\n `等待空闲后安装 v${version}(需连续空闲 ${Math.round(idleMs / 1000)}s` +\n `${ctx.excludeSessionId ? \",已排除发起更新的会话\" : \"\"})`,\n );\n const ok = await this.waitForIdle(ctx);\n if (!ok) {\n this.installing = false;\n return;\n }\n }\n this.applyUpdate(version, stagedPath);\n } catch (err) {\n this.installing = false;\n this.recordFailure(version, `安装失败:${(err as Error).message}`);\n }\n }\n\n private waitForIdle(ctx: InstallContext = {}): Promise<boolean> {\n const requireUptime = ctx.requireUptime !== false;\n const minUptime = requireUptime ? num(\"ZQAGENT_UPDATE_MIN_UPTIME_MS\", 10 * 60 * 1000) : 0;\n const idleMs = num(\"ZQAGENT_UPDATE_IDLE_MS\", 60 * 1000);\n let idleSince: number | null = null;\n\n return new Promise((resolve) => {\n const timer = setInterval(() => {\n if (this.stopped) {\n clearInterval(timer);\n resolve(false);\n return;\n }\n // 用户已要求「立即更新」:不再等空闲\n if (this.forceRequested) {\n this.forceRequested = false;\n clearInterval(timer);\n resolve(true);\n return;\n }\n const ready = Date.now() - this.startedAt >= minUptime;\n // 排除发起更新的会话:它自己一直是 running,不排除就永远等不到空闲\n if (!ready || !this.opts.isIdle(ctx.excludeSessionId)) {\n idleSince = null;\n return;\n }\n idleSince ??= Date.now();\n if (Date.now() - idleSince >= idleMs) {\n clearInterval(timer);\n resolve(true);\n }\n }, 1000);\n timer.unref();\n });\n }\n\n private applyUpdate(version: string, stagedPath: string): void {\n this.opts.prepareExit?.();\n const execPath = process.execPath;\n installStaged({\n dataDir: this.opts.dataDir,\n execPath,\n stagedPath,\n version,\n log: this.log,\n });\n\n // 在真实目标路径上再验一次:staged 能跑不代表替换后能跑(SELinux 上下文、\n // noexec 挂载等都会让 systemd 侧 execve 失败并陷入 203/EXEC 重启循环)。\n // 验不过就立刻用备份回滚,保持节点可用。\n const verify = selfCheck(execPath, version);\n if (!verify.ok) {\n const restored = rollbackExecutable(this.opts.dataDir, execPath, this.log);\n // 回滚时 currentVersion() 还是旧版本,显式记下坏版本,避免下一轮又装回来\n patchState(this.opts.dataDir, { skippedVersion: version });\n this.recordFailure(\n version,\n `安装后自检失败(${verify.error ?? \"未知错误\"})` +\n (restored ? `,已回滚到 v${restored}` : \",回滚失败,需人工处理\"),\n );\n process.exit(1);\n }\n\n this.log(`已安装 v${version},正在重启…`);\n // 非零退出:launchd 的 KeepAlive{SuccessfulExit=false} 只在非正常退出时拉起进程,\n // 用 0 退出会导致新版本不被启动(systemd 的 Restart=always 两者都重启)。\n process.exit(70);\n }\n\n /**\n * 用备份回滚;返回恢复到的版本。\n *\n * 目标路径优先取上次安装记录的路径:`zqagent update --rollback` 可能由 node 进程执行\n * (如 install.sh 内),此时 `process.execPath` 是 node 本身,绝不能拿它做替换。\n */\n rollbackNow(): string | null {\n const state = loadState(this.opts.dataDir);\n const target = state.installedExecPath ?? (isSeaRuntime() ? process.execPath : null);\n if (!target) {\n this.log(\"无法确定节点可执行文件位置(缺少安装记录);请用 deploy/service/install.sh rollback\");\n return null;\n }\n return rollbackExecutable(this.opts.dataDir, target, this.log);\n }\n\n status(): UpdaterStatus {\n const state = loadState(this.opts.dataDir);\n const cap = selfUpdateCapability(Boolean(this.publicKey));\n return {\n enabled: !this.autoUpdateOff && cap.ok,\n reason: cap.reason,\n currentVersion: this.currentVersion,\n channel: updaterChannel(),\n manifestUrl: this.manifestUrl(),\n serviceManager: serviceManager(),\n lastCheckAt: this.lastCheckAt ?? state.lastCheckAt,\n stagedVersion: state.stagedVersion,\n pendingVersion: state.pendingVersion,\n previousVersion: state.previousVersion,\n backupVersion: state.backupVersion,\n lastError: state.lastError,\n };\n }\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * 清单拉取与产物下载。\n *\n * 下载全程流式写入 `.part` 文件并同步计算 SHA-256,校验通过后才 rename 为正式文件。\n * 任何一步失败都会清理临时文件,不会留下半截可执行文件。\n */\nimport { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { spawnSync } from \"node:child_process\";\nimport type { KeyObject } from \"node:crypto\";\nimport * as path from \"node:path\";\nimport { parseManifest, type UpdateBinary, type UpdateManifest } from \"./manifest.js\";\nimport { verifySignature } from \"./signature.js\";\n\n/** 单个产物的下载上限(SEA 带内嵌 ripgrep,当前约 100MB;留足余量) */\nconst MAX_BINARY_BYTES = 400 * 1024 * 1024;\nconst MANIFEST_TIMEOUT_MS = 15_000;\nconst DOWNLOAD_TIMEOUT_MS = 10 * 60_000;\n\nexport interface FetchManifestResult {\n manifest?: UpdateManifest;\n /** 校验失败的说明 */\n error?: string;\n raw?: Buffer;\n}\n\n/**\n * 拉取并校验清单:\n * 1. GET `<url>` 与 `<url>.sig`\n * 2. Ed25519 验签(对原始字节)\n * 3. JSON 解析 + 字段/URL/哈希校验\n */\nexport async function fetchManifest(\n url: string,\n publicKey: KeyObject,\n opts: { allowInsecureUrl?: boolean } = {},\n): Promise<FetchManifestResult> {\n let raw: Buffer;\n let sig: string;\n try {\n const [mRes, sRes] = await Promise.all([\n fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) }),\n fetch(`${url}.sig`, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) }),\n ]);\n if (!mRes.ok) return { error: `清单请求失败 HTTP ${mRes.status}` };\n if (!sRes.ok) return { error: `签名请求失败 HTTP ${sRes.status}` };\n raw = Buffer.from(await mRes.arrayBuffer());\n sig = (await sRes.text()).trim();\n } catch (err) {\n return { error: `拉取清单失败:${(err as Error).message}` };\n }\n\n if (!sig) return { error: \"签名文件为空\" };\n if (!verifySignature(raw, sig, publicKey)) {\n return { error: \"清单签名校验失败(公钥不匹配或内容被篡改)\" };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw.toString(\"utf-8\"));\n } catch (err) {\n return { error: `清单不是合法 JSON:${(err as Error).message}` };\n }\n const { manifest, error } = parseManifest(parsed, opts);\n if (!manifest) return { error };\n return { manifest, raw };\n}\n\n/**\n * 下载产物到 `destPath`,边下边校验 sha256 与大小。返回实际字节数。\n */\nexport async function downloadBinary(\n bin: UpdateBinary,\n destPath: string,\n log: (msg: string) => void = () => {},\n): Promise<{ bytes?: number; error?: string }> {\n const partPath = `${destPath}.part`;\n fs.mkdirSync(path.dirname(destPath), { recursive: true });\n fs.rmSync(partPath, { force: true });\n\n let res: Response;\n try {\n res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });\n } catch (err) {\n return { error: `下载失败:${(err as Error).message}` };\n }\n if (!res.ok) return { error: `下载失败 HTTP ${res.status}` };\n if (!res.body) return { error: \"下载响应没有 body\" };\n\n const expected = bin.size && bin.size > 0 ? bin.size : undefined;\n const limit = expected ? Math.max(expected * 2, 1024 * 1024) : MAX_BINARY_BYTES;\n const hash = createHash(\"sha256\");\n let bytes = 0;\n\n try {\n const source = Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]);\n await pipeline(\n source,\n async function* (chunks: AsyncIterable<Buffer>) {\n for await (const chunk of chunks) {\n bytes += chunk.length;\n if (bytes > limit) throw new Error(`产物超过大小上限(>${limit} 字节)`);\n hash.update(chunk);\n yield chunk;\n }\n },\n fs.createWriteStream(partPath, { mode: 0o755 }),\n );\n } catch (err) {\n fs.rmSync(partPath, { force: true });\n return { error: `下载中断:${(err as Error).message}` };\n }\n\n const actual = hash.digest(\"hex\");\n if (actual !== bin.sha256) {\n fs.rmSync(partPath, { force: true });\n return { error: `SHA-256 不匹配(期望 ${bin.sha256.slice(0, 12)}…,实际 ${actual.slice(0, 12)}…)` };\n }\n if (expected && bytes !== expected) {\n fs.rmSync(partPath, { force: true });\n return { error: `产物大小不匹配(期望 ${expected},实际 ${bytes})` };\n }\n\n fs.rmSync(destPath, { force: true });\n fs.renameSync(partPath, destPath);\n fs.chmodSync(destPath, 0o755);\n log(`已下载 ${path.basename(destPath)}(${Math.round(bytes / 1024 / 1024)}MB,校验通过)`);\n return { bytes };\n}\n\n/**\n * 安装前自检:跑一次 `--version`,输出必须包含清单版本。\n * 防住「架构不匹配 / 包损坏 / 误标版本」三类事故。\n */\nexport function selfCheck(binPath: string, expectedVersion: string): { ok: boolean; error?: string } {\n try {\n const r = spawnSync(binPath, [\"--version\"], {\n timeout: 20_000,\n encoding: \"utf-8\",\n env: { ...process.env, ZQAGENT_AUTO_UPDATE: \"0\" },\n });\n if (r.error) return { ok: false, error: `执行自检失败:${r.error.message}` };\n if (r.status !== 0) return { ok: false, error: `自检退出码 ${r.status}:${(r.stderr || \"\").trim()}` };\n const out = `${r.stdout ?? \"\"}${r.stderr ?? \"\"}`;\n if (!out.includes(expectedVersion)) {\n return { ok: false, error: `自检版本不符(期望 ${expectedVersion},输出:${out.trim().slice(0, 120)})` };\n }\n return { ok: true };\n } catch (err) {\n return { ok: false, error: `自检异常:${(err as Error).message}` };\n }\n}\n","/**\n * 极简 semver 解析与比较(不引入 semver 依赖)。\n *\n * 只处理节点更新场景需要的子集:`major.minor.patch[-prerelease]`,\n * 允许前缀 `v`,忽略 build metadata(`+xxx`)。解析失败返回 null。\n */\n\nexport interface SemVer {\n major: number;\n minor: number;\n patch: number;\n /** 预发布标识(`beta.1` → [\"beta\", 1]);正式版为空数组 */\n pre: (string | number)[];\n}\n\nconst PATTERN = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/;\n\nexport function parseVersion(input: string): SemVer | null {\n const m = PATTERN.exec(input.trim());\n if (!m) return null;\n const pre = m[4]\n ? m[4].split(\".\").map((part) => (/^\\d+$/.test(part) ? Number(part) : part))\n : [];\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n pre,\n };\n}\n\n/** 比较预发布标识:数字段按数值比较,字符串按字典序,数字段低于字符串段 */\nfunction comparePre(a: (string | number)[], b: (string | number)[]): number {\n // 无预发布标识 = 正式版,优先级高于任何预发布\n if (a.length === 0 && b.length === 0) return 0;\n if (a.length === 0) return 1;\n if (b.length === 0) return -1;\n for (let i = 0; i < Math.max(a.length, b.length); i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined) return -1;\n if (y === undefined) return 1;\n if (typeof x === \"number\" && typeof y === \"number\") {\n if (x !== y) return x < y ? -1 : 1;\n } else if (typeof x === \"number\") {\n return -1;\n } else if (typeof y === \"number\") {\n return 1;\n } else if (x !== y) {\n return x < y ? -1 : 1;\n }\n }\n return 0;\n}\n\n/** 返回 -1 / 0 / 1;任一版本无法解析时按「视为相同」返回 0(调用方需先校验) */\nexport function compareVersions(a: string, b: string): number {\n const va = parseVersion(a);\n const vb = parseVersion(b);\n if (!va || !vb) return 0;\n if (va.major !== vb.major) return va.major < vb.major ? -1 : 1;\n if (va.minor !== vb.minor) return va.minor < vb.minor ? -1 : 1;\n if (va.patch !== vb.patch) return va.patch < vb.patch ? -1 : 1;\n return comparePre(va.pre, vb.pre);\n}\n\n/** a 是否比 b 新 */\nexport function isNewer(a: string, b: string): boolean {\n return compareVersions(a, b) > 0;\n}\n","/**\n * 节点更新清单(manifest)的类型与校验。\n *\n * 清单由发布流程生成,托管在静态目录(默认\n * `https://agent.cd-zq.com/downloads/zqagent/stable.json`),\n * 与同名 `.sig`(Ed25519 签名)配对使用。\n */\nimport { parseVersion } from \"./version.js\";\n\n/** 单个平台的产物描述 */\nexport interface UpdateBinary {\n /** 绝对 https 地址(或显式允许的 http) */\n url: string;\n /** 小写 hex 的 SHA-256 */\n sha256: string;\n /** 字节数(可选;用于下载上限与进度) */\n size?: number;\n}\n\nexport interface UpdateManifest {\n /** 新版本号(bare semver,如 0.2.2) */\n version: string;\n /** 允许自动更新的最低版本:低于它的节点拒绝升级(防止跨破坏性变更) */\n minimumVersion?: string;\n notes?: string;\n publishedAt?: string;\n channel?: string;\n /** 平台键(见 platformKey)→ 产物 */\n binaries: Record<string, UpdateBinary>;\n}\n\n/** 产物名与 build_cli_binary.mjs 的 DISPLAY_PLATFORM 保持一致:macos / linux / windows */\nconst PLATFORM_NAMES: Record<string, string> = {\n darwin: \"macos\",\n linux: \"linux\",\n win32: \"windows\",\n};\n\n/**\n * 构建期注入的 libc 变体标记:普通产物为空,glibc 2.17 兼容产物为 `glibc217`。\n *\n * 由 build_cli_binary.mjs 在 `--libc glibc-217` 时传入环境变量,tsup 在打包时\n * 把 `process.env.ZQAGENT_LIBC_VARIANT` 替换成字面量,因此运行时不受环境影响。\n */\nconst LIBC_VARIANT = process.env.ZQAGENT_LIBC_VARIANT ?? \"\";\n\n/** 当前进程对应的清单键,如 `macos-arm64`、`linux-x64`、`linux-x64-glibc217` */\nexport function platformKey(\n platform: string = process.platform,\n arch: string = process.arch,\n): string {\n const base = `${PLATFORM_NAMES[platform] ?? platform}-${arch}`;\n return LIBC_VARIANT ? `${base}-${LIBC_VARIANT}` : base;\n}\n\nconst SHA256_HEX = /^[0-9a-f]{64}$/;\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\n/**\n * 校验并规范化清单。允许 http 仅当调用方显式放行(内网自测)。\n */\nexport function parseManifest(\n raw: unknown,\n opts: { allowInsecureUrl?: boolean } = {},\n): { manifest?: UpdateManifest; error?: string } {\n if (!isRecord(raw)) return { error: \"清单不是 JSON 对象\" };\n\n const version = typeof raw.version === \"string\" ? raw.version.trim() : \"\";\n if (!parseVersion(version)) return { error: `清单 version 非法:${String(raw.version)}` };\n\n if (raw.minimumVersion !== undefined) {\n if (typeof raw.minimumVersion !== \"string\" || !parseVersion(raw.minimumVersion)) {\n return { error: `清单 minimumVersion 非法:${String(raw.minimumVersion)}` };\n }\n }\n\n if (!isRecord(raw.binaries)) return { error: \"清单缺少 binaries\" };\n const binaries: Record<string, UpdateBinary> = {};\n for (const [key, value] of Object.entries(raw.binaries)) {\n if (!isRecord(value)) return { error: `binaries.${key} 不是对象` };\n const url = typeof value.url === \"string\" ? value.url.trim() : \"\";\n const sha256 = typeof value.sha256 === \"string\" ? value.sha256.trim().toLowerCase() : \"\";\n if (!url) return { error: `binaries.${key} 缺少 url` };\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return { error: `binaries.${key}.url 非法:${url}` };\n }\n if (parsed.protocol !== \"https:\" && !opts.allowInsecureUrl) {\n return { error: `binaries.${key}.url 必须是 https:${url}` };\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n return { error: `binaries.${key}.url 协议不支持:${url}` };\n }\n if (!SHA256_HEX.test(sha256)) {\n return { error: `binaries.${key}.sha256 需为 64 位小写 hex` };\n }\n const size = typeof value.size === \"number\" && value.size > 0 ? value.size : undefined;\n binaries[key] = { url, sha256, ...(size ? { size } : {}) };\n }\n if (Object.keys(binaries).length === 0) return { error: \"清单 binaries 为空\" };\n\n return {\n manifest: {\n version,\n ...(typeof raw.minimumVersion === \"string\" ? { minimumVersion: raw.minimumVersion } : {}),\n ...(typeof raw.notes === \"string\" ? { notes: raw.notes } : {}),\n ...(typeof raw.publishedAt === \"string\" ? { publishedAt: raw.publishedAt } : {}),\n ...(typeof raw.channel === \"string\" ? { channel: raw.channel } : {}),\n binaries,\n },\n };\n}\n","/**\n * 更新清单的签名校验(Ed25519)。\n *\n * 签名方案:对清单原始字节做 Ed25519 签名,签名以 base64 存于同名 `.sig` 文件。\n * 公钥通过 `ZQAGENT_UPDATE_PUBKEY` 提供(PEM 或 base64 的 SPKI DER)。\n * 未配置公钥时**禁用自动更新**——无法验签就不能替用户改可执行文件。\n */\nimport { createPublicKey, verify, type KeyObject } from \"node:crypto\";\n\n/** 从 PEM 或 base64(SPKI DER) 解析公钥;失败返回 null */\nexport function parsePublicKey(input: string): KeyObject | null {\n const text = input.trim();\n if (!text) return null;\n try {\n if (text.includes(\"BEGIN PUBLIC KEY\")) {\n return createPublicKey({ key: text, format: \"pem\" });\n }\n return createPublicKey({\n key: Buffer.from(text, \"base64\"),\n format: \"der\",\n type: \"spki\",\n });\n } catch {\n return null;\n }\n}\n\n/** 校验 `data` 的签名;任何异常都按失败处理 */\nexport function verifySignature(\n data: Buffer | Uint8Array,\n signatureBase64: string,\n publicKey: KeyObject,\n): boolean {\n try {\n const sig = Buffer.from(signatureBase64.trim(), \"base64\");\n if (sig.length !== 64) return false;\n return verify(null, data, publicKey, sig);\n } catch {\n return false;\n }\n}\n","/**\n * 运行形态与自更新能力判定。\n *\n * 只有「SEA 单文件 + 由 systemd/launchd 托管 + 配置了公钥」三者同时满足,\n * 才允许自动替换可执行文件并重启——否则退出后没人把它拉起来。\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\n/**\n * 当前版本:构建期注入(SEA/tsup define)优先,其次读包内 package.json,\n * 最后回退 \"dev\"。后两条让源码运行(tsx)也能显示真实版本。\n */\nexport function currentVersion(): string {\n const injected = process.env.ZQAGENT_VERSION?.trim();\n if (injected) return injected;\n try {\n const req = createRequire(import.meta.url);\n const pkg = req(\"../../package.json\") as { version?: string };\n if (pkg?.version) return pkg.version;\n } catch {\n // SEA 内取不到包信息,忽略\n }\n return \"dev\";\n}\n\n/**\n * 是否为 SEA 单文件运行。\n *\n * 判据优先取 execPath 的文件名:SEA 产物是 `zqagent`,经 node 运行的则是 `node`。\n * 构建期还会注入 `ZQAGENT_SEA=1`(见 tsup.sea.config.ts)作为权威标记。\n */\nexport function isSeaRuntime(): boolean {\n if (process.env.ZQAGENT_SEA === \"1\") return true;\n const base = path.basename(process.execPath).toLowerCase().replace(/\\.exe$/, \"\");\n return base !== \"node\" && base !== \"nodejs\";\n}\n\nexport type ServiceManager = \"systemd\" | \"launchd\";\n\n/** 未指定 ZQAGENT_SERVICE_NAME 时的默认 systemd unit 名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_NAME = \"zqagent\";\n\n/** 未指定 ZQAGENT_SERVICE_LABEL 时的默认 launchd 作业名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_LABEL = \"com.zqagent.node\";\n\n/**\n * 从 `/proc/self/cgroup` 的内容判定进程是否属于某个 systemd unit。\n *\n * 抽取为纯函数以便测试。兼容 cgroup v1(`1:name=systemd:/system.slice/x.service`)\n * 与 v2(`0::/system.slice/x.service`)两种行格式。\n *\n * 只认路径中**独立成段**的 `<serviceName>.service`:`my-zqagent.service`、\n * `zqagent.service.bak` 这类名字相近的 unit 不会被误判成 `zqagent.service`。\n */\nexport function serviceManagerFromCgroup(\n cgroup: string,\n serviceName: string = DEFAULT_SERVICE_NAME,\n): ServiceManager | null {\n const unitName = `${serviceName}.service`;\n for (const line of cgroup.split(\"\\n\")) {\n // 路径在最后一个冒号之后;v1 的 `name=systemd` 不会干扰\n const idx = line.lastIndexOf(\":\");\n if (idx < 0) continue;\n const unitPath = line.slice(idx + 1).trim();\n if (unitPath.split(\"/\").includes(unitName)) return \"systemd\";\n }\n return null;\n}\n\n/** launchd 作业所在域:root 装的是 LaunchDaemon(system),普通用户是 LaunchAgent(gui/<uid>) */\nfunction launchdDomain(): string {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n return uid === 0 ? \"system\" : `gui/${uid}`;\n}\n\nfunction launchdLabel(): string {\n return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;\n}\n\n/** 与 install.sh 写入的位置保持一致 */\nfunction launchdPlistPath(): string {\n const label = launchdLabel();\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;\n return path.join(os.homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\n/**\n * 从 `launchctl print <domain>/<label>` 的输出判定是否为 launchd 托管。\n *\n * macOS 没有 cgroup 可用,改用作业描述里的两处信息:\n * - `pid = N` 与当前进程一致 → 本进程就是该作业拉起的;\n * - `program = /path` 与 execPath 相同 → 服务运行的正是这个可执行文件,\n * 在交互 shell 里执行 `zqagent update` 时靠这条判定(Linux 靠 cgroup 归属)。\n *\n * `sea` 为 false(npm/source 形态)时作业存在即算托管:这种形态本来就不允许\n * 自替换二进制(见 selfUpdateCapability),这里只影响 `update --status` 的显示。\n */\nexport function serviceManagerFromLaunchctl(\n output: string,\n pid: number,\n execPath: string,\n sea = true,\n): ServiceManager | null {\n const pidMatch = /^\\s*pid = (\\d+)\\s*$/m.exec(output);\n if (pidMatch && Number(pidMatch[1]) === pid) return \"launchd\";\n\n const program = /^\\s*program = (.+?)\\s*$/m.exec(output);\n if (program) {\n const shown = program[1].replace(/^\"|\"$/g, \"\");\n if (path.resolve(shown) === path.resolve(execPath)) return \"launchd\";\n }\n\n if (!sea && /^\\s*state = /m.test(output)) return \"launchd\";\n return null;\n}\n\n/**\n * 解析 launchd plist 中 `EnvironmentVariables` 字典的键值。\n *\n * 只处理本脚本会写出的 `<key>K</key><string>V</string>` 形式;`<true/>` 之类的\n * 非字符串值忽略(更新配置用不到)。\n */\nexport function parseLaunchdEnvironment(xml: string): Record<string, string> {\n const out: Record<string, string> = {};\n const marker = xml.indexOf(\"<key>EnvironmentVariables</key>\");\n if (marker < 0) return out;\n const open = xml.indexOf(\"<dict>\", marker);\n if (open < 0) return out;\n const close = xml.indexOf(\"</dict>\", open);\n if (close < 0) return out;\n const body = xml.slice(open + \"<dict>\".length, close);\n for (const m of body.matchAll(/<key>([^<]+)<\\/key>\\s*<string>([^<]*)<\\/string>/g)) {\n out[m[1]] = m[2];\n }\n return out;\n}\n\n/**\n * 未注入 ZQAGENT_SERVICE_MANAGER 时的兜底检测。\n *\n * 服务进程的环境变量由 unit 注入,但运维在交互 shell 里手动执行\n * `zqagent update --status/--check/--now` 时没有这些变量——此前会被判成\n * 「未托管」,导致诊断结论错误、`--now` 直接拒绝执行。这里用 cgroup 归属\n * 兜底:Linux 上进程落在 `/<service>.service` 里,说明 systemd 确实会拉起它。\n *\n * 刻意不使用「/run/systemd/system 存在即视为托管」这类判据:那样任何在\n * systemd 机器上裸跑的进程都会通过自检,替换二进制后 `exit(70)` 却没人拉起,\n * 节点会直接下线。\n */\nfunction detectServiceManager(): ServiceManager | null {\n if (process.platform === \"darwin\") return detectLaunchd();\n if (process.platform !== \"linux\") return null;\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n return serviceManagerFromCgroup(\n fs.readFileSync(\"/proc/self/cgroup\", \"utf8\"),\n serviceName,\n );\n } catch {\n return null;\n }\n}\n\n/**\n * macOS 兜底:读取 launchd 作业描述,按 pid / program 判定(见\n * `serviceManagerFromLaunchctl`)。作业不存在、launchctl 不可用时返回 null。\n */\nfunction detectLaunchd(): ServiceManager | null {\n try {\n const res = spawnSync(\n \"launchctl\",\n [\"print\", `${launchdDomain()}/${launchdLabel()}`],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return null;\n return serviceManagerFromLaunchctl(\n res.stdout,\n process.pid,\n process.execPath,\n isSeaRuntime(),\n );\n } catch {\n return null;\n }\n}\n\n/**\n * 进程由哪个服务管理器托管;无托管返回 null。\n *\n * 优先采信 `deploy/service/install.sh` 注入的环境变量,缺失时在 Linux 上\n * 按 cgroup 归属兜底检测(见 `detectServiceManager`)。\n */\nexport function serviceManager(): ServiceManager | null {\n const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();\n if (v === \"systemd\" || v === \"launchd\") return v;\n return detectServiceManager();\n}\n\nexport function autoUpdateDisabled(): boolean {\n return process.env.ZQAGENT_AUTO_UPDATE?.trim() === \"0\";\n}\n\n/**\n * 解析 `systemctl show -p Environment --value` 的输出为键值表。\n *\n * 输出形如 `NODE_ENV=production PATH=/usr/local/bin:/usr/bin KEY=abc`。\n * 值里若含空格会被拆开——本函数只用于取公钥/通道/清单地址这类不含空格的\n * 变量,够用且无需处理 systemd 的引号规则。\n */\nexport function parseServiceEnvironment(raw: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const token of raw.trim().split(/\\s+/)) {\n const idx = token.indexOf(\"=\");\n if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);\n }\n return out;\n}\n\nlet cachedServiceEnv: Record<string, string> | null = null;\n\nfunction readServiceEnvironment(): Record<string, string> {\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n const res = spawnSync(\n \"systemctl\",\n [\"show\", serviceName, \"--property=Environment\", \"--value\", \"--no-pager\"],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return {};\n return parseServiceEnvironment(res.stdout);\n } catch {\n return {};\n }\n}\n\n/**\n * 读取 systemd unit 注入的环境变量。\n *\n * 服务进程拿得到这些变量,但运维在交互 shell 里手动执行 `zqagent update`\n * 时拿不到——公钥缺失会让自更新被判为不可用。这里按需从 unit 补齐,\n * 取不到(非 systemd、无 systemctl、超时)时返回 undefined。\n */\nexport function serviceEnvValue(key: string): string | undefined {\n if (process.platform === \"darwin\") {\n if (serviceManager() !== \"launchd\") return undefined;\n cachedLaunchdEnv ??= readLaunchdEnvironment();\n return cachedLaunchdEnv[key];\n }\n if (process.platform !== \"linux\") return undefined;\n if (serviceManager() !== \"systemd\") return undefined;\n cachedServiceEnv ??= readServiceEnvironment();\n return cachedServiceEnv[key];\n}\n\nlet cachedLaunchdEnv: Record<string, string> | null = null;\n\n/**\n * 读取 launchd plist 注入的环境变量。\n *\n * 与 systemd 的 `systemctl show -p Environment` 对应:干净 shell 里执行\n * `zqagent update --status` 时,公钥/通道/清单地址只能从服务定义里取回。\n */\nfunction readLaunchdEnvironment(): Record<string, string> {\n try {\n return parseLaunchdEnvironment(fs.readFileSync(launchdPlistPath(), \"utf8\"));\n } catch {\n return {};\n }\n}\n\nexport interface Capability {\n ok: boolean;\n /** 不满足时的可读原因(用于日志/`update --status`) */\n reason?: string;\n}\n\n/**\n * 自更新能力判定。`hasPublicKey` 由调用方传入(公钥只在验签时解析一次)。\n */\nexport function selfUpdateCapability(hasPublicKey: boolean): Capability {\n if (!isSeaRuntime()) {\n return {\n ok: false,\n reason: \"当前不是单文件(SEA)形态:请用 npm 升级(npm i -g @zqagent/cli@latest)\",\n };\n }\n if (!serviceManager()) {\n return {\n ok: false,\n reason: \"未被 systemd/launchd 托管:请用 deploy/service/install.sh 安装,或手动升级\",\n };\n }\n if (!hasPublicKey) {\n return { ok: false, reason: \"未配置 ZQAGENT_UPDATE_PUBKEY,无法验签,已禁用自动更新\" };\n }\n return { ok: true };\n}\n","/**\n * 原子替换与回滚。\n *\n * 替换策略:\n * - Unix:先复制到 exe 同目录的 `.new`,再 rename 覆盖(同目录内 rename 是原子的)。\n * 不能直接跨目录 rename:那样会把 inode 连同源目录的 SELinux 上下文(如 `var_lib_t`)\n * 一起搬进 `/usr/local/bin`,systemd 执行时会因类型不允许而 `203/EXEC`。\n * 在目标目录新建文件则可继承父目录的 `bin_t`,与 install.sh 的 `cp` 行为一致。\n * - Windows:运行中的 exe 不能覆盖,但允许改名——先把旧文件改成 `.old`,再落新文件。\n *\n * 备份统一放在 `<dataDir>/updates/backup/`(该目录一定可写),供 `update --rollback` 使用。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { currentVersion } from \"./platform.js\";\nimport { patchState, updatesDir, type UpdateState } from \"./state.js\";\n\nexport interface InstallOptions {\n dataDir: string;\n /** 当前正在运行的可执行文件(SEA 的 process.execPath) */\n execPath: string;\n /** 已下载并通过自检的新文件 */\n stagedPath: string;\n version: string;\n log?: (msg: string) => void;\n}\n\nfunction backupDir(dataDir: string): string {\n return path.join(updatesDir(dataDir), \"backup\");\n}\n\n/** 备份当前可执行文件,返回备份路径 */\nexport function backupCurrent(dataDir: string, execPath: string, version: string): string {\n const dir = backupDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n const target = path.join(dir, `zqagent-${version}${process.platform === \"win32\" ? \".exe\" : \"\"}`);\n fs.copyFileSync(execPath, target);\n fs.chmodSync(target, 0o755);\n return target;\n}\n\n/**\n * 用 `stagedPath` 替换 `execPath`。\n * 调用方负责在这之后退出进程,让服务管理器拉起新版本。\n */\nexport function replaceExecutable(execPath: string, stagedPath: string): void {\n if (process.platform === \"win32\") {\n const oldPath = `${execPath}.old`;\n fs.rmSync(oldPath, { force: true });\n fs.renameSync(execPath, oldPath);\n fs.copyFileSync(stagedPath, execPath);\n fs.chmodSync(execPath, 0o755);\n return;\n }\n\n // 始终在目标目录落一个新文件:跨目录 rename 会保留源端的 SELinux 上下文\n const tmp = `${execPath}.new`;\n fs.rmSync(tmp, { force: true });\n try {\n fs.copyFileSync(stagedPath, tmp);\n fs.chmodSync(tmp, 0o755);\n fs.renameSync(tmp, execPath);\n } catch (err) {\n fs.rmSync(tmp, { force: true });\n throw err;\n }\n fs.rmSync(stagedPath, { force: true });\n}\n\n/**\n * 执行安装(备份 → 替换 → 记录状态)。不负责退出进程。\n * 失败时抛错,调用方保留 staged 文件以便下次重试。\n */\nexport function installStaged(opts: InstallOptions): UpdateState {\n const log = opts.log ?? (() => {});\n const from = currentVersion();\n\n log(`正在安装 v${opts.version}(当前 v${from})`);\n const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);\n log(`已备份当前版本到 ${path.basename(backupPath)}`);\n\n replaceExecutable(opts.execPath, opts.stagedPath);\n\n return patchState(opts.dataDir, {\n stagedVersion: undefined,\n stagedPath: undefined,\n pendingVersion: opts.version,\n previousVersion: from,\n backupVersion: from,\n backupPath,\n installedExecPath: opts.execPath,\n lastError: undefined,\n failureCount: 0,\n failedVersion: undefined,\n });\n}\n\n/** 用备份覆盖当前可执行文件并返回被恢复的版本;无备份时返回 null */\nexport function rollback(dataDir: string, execPath: string, log: (msg: string) => void = () => {}): string | null {\n const state = readRollbackTarget(dataDir);\n if (!state?.backupPath || !state.backupVersion) return null;\n if (!fs.existsSync(state.backupPath)) return null;\n\n log(`正在回滚到 v${state.backupVersion}`);\n if (process.platform === \"win32\") {\n fs.rmSync(`${execPath}.old`, { force: true });\n fs.renameSync(execPath, `${execPath}.old`);\n fs.copyFileSync(state.backupPath, execPath);\n fs.chmodSync(execPath, 0o755);\n } else {\n const tmp = `${execPath}.rollback`;\n fs.copyFileSync(state.backupPath, tmp);\n fs.chmodSync(tmp, 0o755);\n fs.renameSync(tmp, execPath);\n }\n\n patchState(dataDir, {\n pendingVersion: state.backupVersion,\n previousVersion: currentVersion(),\n // 不让自动更新立刻把刚回滚掉的版本再装回来\n skippedVersion: currentVersion(),\n backupPath: undefined,\n backupVersion: undefined,\n });\n return state.backupVersion;\n}\n\nfunction readRollbackTarget(dataDir: string): Pick<UpdateState, \"backupPath\" | \"backupVersion\"> | null {\n try {\n const raw = fs.readFileSync(path.join(updatesDir(dataDir), \"state.json\"), \"utf-8\");\n const s = JSON.parse(raw) as UpdateState;\n if (s.backupPath && s.backupVersion) return s;\n } catch {\n // 无状态\n }\n return null;\n}\n","/**\n * 更新状态持久化:`<dataDir>/updates/state.json`\n *\n * 记录最近一次检查、已暂存待安装的版本、失败计数与回滚用的备份路径。\n * 文件损坏或不可读时按「无状态」处理,不阻塞节点启动。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport interface UpdateState {\n lastCheckAt?: string;\n /** 最近一次失败原因(成功检查后清空) */\n lastError?: string;\n /** 同一版本的连续失败次数;达到阈值后放弃该版本 */\n failureCount?: number;\n failedVersion?: string;\n /** 已下载并通过校验、等待空闲安装的版本 */\n stagedVersion?: string;\n stagedPath?: string;\n /** 已替换文件、等待新进程启动确认的版本 */\n pendingVersion?: string;\n /** 回滚备份 */\n backupVersion?: string;\n backupPath?: string;\n /** 上次成功启动的版本(用于判断更新是否生效) */\n runningVersion?: string;\n /** 上次更新前运行的版本 */\n previousVersion?: string;\n /** 被替换的可执行文件路径(回滚时用,避免从 node 进程误推断成 node 自身) */\n installedExecPath?: string;\n /** 被回滚掉的版本:自动更新会跳过它,直到发布更新的版本(手动 --now 不受限) */\n skippedVersion?: string;\n}\n\nexport function updatesDir(dataDir: string): string {\n return path.join(dataDir, \"updates\");\n}\n\nfunction statePath(dataDir: string): string {\n return path.join(updatesDir(dataDir), \"state.json\");\n}\n\nexport function loadState(dataDir: string): UpdateState {\n try {\n const raw = fs.readFileSync(statePath(dataDir), \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null) return parsed as UpdateState;\n } catch {\n // 不存在或损坏:按空状态处理\n }\n return {};\n}\n\nexport function saveState(dataDir: string, state: UpdateState): void {\n const dir = updatesDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n const file = statePath(dataDir);\n const tmp = `${file}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(state, null, 2), \"utf-8\");\n fs.renameSync(tmp, file);\n}\n\n/** 合并写入(读 → 改 → 写),避免并发覆盖 */\nexport function patchState(dataDir: string, patch: Partial<UpdateState>): UpdateState {\n const next = { ...loadState(dataDir), ...patch };\n saveState(dataDir, next);\n return next;\n}\n"],"mappings":"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","names":["fs","path","import_node_crypto","fs","path","import_node_child_process","fs","path","fs","path","currentVersion","resolve","verify"]}
@@ -253,7 +253,7 @@ import * as path2 from "path";
253
253
  import { spawnSync as spawnSync2 } from "child_process";
254
254
  import { createRequire } from "module";
255
255
  function currentVersion() {
256
- const injected = "0.3.10"?.trim();
256
+ const injected = "0.3.11"?.trim();
257
257
  if (injected) return injected;
258
258
  try {
259
259
  const req = createRequire(import.meta.url);
@@ -43,7 +43,7 @@ __export(platform_exports, {
43
43
  });
44
44
  module.exports = __toCommonJS(platform_exports);
45
45
 
46
- // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.12_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
46
+ // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.3.23_jiti@1.21.7_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.12_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
47
47
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
48
48
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
49
49
 
@@ -54,7 +54,7 @@ var path = __toESM(require("path"), 1);
54
54
  var import_node_child_process = require("child_process");
55
55
  var import_node_module = require("module");
56
56
  function currentVersion() {
57
- const injected = "0.3.10"?.trim();
57
+ const injected = "0.3.11"?.trim();
58
58
  if (injected) return injected;
59
59
  try {
60
60
  const req = (0, import_node_module.createRequire)(importMetaUrl);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/update/platform.ts","../../../../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.12_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"],"sourcesContent":["/**\n * 运行形态与自更新能力判定。\n *\n * 只有「SEA 单文件 + 由 systemd/launchd 托管 + 配置了公钥」三者同时满足,\n * 才允许自动替换可执行文件并重启——否则退出后没人把它拉起来。\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\n/**\n * 当前版本:构建期注入(SEA/tsup define)优先,其次读包内 package.json,\n * 最后回退 \"dev\"。后两条让源码运行(tsx)也能显示真实版本。\n */\nexport function currentVersion(): string {\n const injected = process.env.ZQAGENT_VERSION?.trim();\n if (injected) return injected;\n try {\n const req = createRequire(import.meta.url);\n const pkg = req(\"../../package.json\") as { version?: string };\n if (pkg?.version) return pkg.version;\n } catch {\n // SEA 内取不到包信息,忽略\n }\n return \"dev\";\n}\n\n/**\n * 是否为 SEA 单文件运行。\n *\n * 判据优先取 execPath 的文件名:SEA 产物是 `zqagent`,经 node 运行的则是 `node`。\n * 构建期还会注入 `ZQAGENT_SEA=1`(见 tsup.sea.config.ts)作为权威标记。\n */\nexport function isSeaRuntime(): boolean {\n if (process.env.ZQAGENT_SEA === \"1\") return true;\n const base = path.basename(process.execPath).toLowerCase().replace(/\\.exe$/, \"\");\n return base !== \"node\" && base !== \"nodejs\";\n}\n\nexport type ServiceManager = \"systemd\" | \"launchd\";\n\n/** 未指定 ZQAGENT_SERVICE_NAME 时的默认 systemd unit 名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_NAME = \"zqagent\";\n\n/** 未指定 ZQAGENT_SERVICE_LABEL 时的默认 launchd 作业名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_LABEL = \"com.zqagent.node\";\n\n/**\n * 从 `/proc/self/cgroup` 的内容判定进程是否属于某个 systemd unit。\n *\n * 抽取为纯函数以便测试。兼容 cgroup v1(`1:name=systemd:/system.slice/x.service`)\n * 与 v2(`0::/system.slice/x.service`)两种行格式。\n *\n * 只认路径中**独立成段**的 `<serviceName>.service`:`my-zqagent.service`、\n * `zqagent.service.bak` 这类名字相近的 unit 不会被误判成 `zqagent.service`。\n */\nexport function serviceManagerFromCgroup(\n cgroup: string,\n serviceName: string = DEFAULT_SERVICE_NAME,\n): ServiceManager | null {\n const unitName = `${serviceName}.service`;\n for (const line of cgroup.split(\"\\n\")) {\n // 路径在最后一个冒号之后;v1 的 `name=systemd` 不会干扰\n const idx = line.lastIndexOf(\":\");\n if (idx < 0) continue;\n const unitPath = line.slice(idx + 1).trim();\n if (unitPath.split(\"/\").includes(unitName)) return \"systemd\";\n }\n return null;\n}\n\n/** launchd 作业所在域:root 装的是 LaunchDaemon(system),普通用户是 LaunchAgent(gui/<uid>) */\nfunction launchdDomain(): string {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n return uid === 0 ? \"system\" : `gui/${uid}`;\n}\n\nfunction launchdLabel(): string {\n return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;\n}\n\n/** 与 install.sh 写入的位置保持一致 */\nfunction launchdPlistPath(): string {\n const label = launchdLabel();\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;\n return path.join(os.homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\n/**\n * 从 `launchctl print <domain>/<label>` 的输出判定是否为 launchd 托管。\n *\n * macOS 没有 cgroup 可用,改用作业描述里的两处信息:\n * - `pid = N` 与当前进程一致 → 本进程就是该作业拉起的;\n * - `program = /path` 与 execPath 相同 → 服务运行的正是这个可执行文件,\n * 在交互 shell 里执行 `zqagent update` 时靠这条判定(Linux 靠 cgroup 归属)。\n *\n * `sea` 为 false(npm/source 形态)时作业存在即算托管:这种形态本来就不允许\n * 自替换二进制(见 selfUpdateCapability),这里只影响 `update --status` 的显示。\n */\nexport function serviceManagerFromLaunchctl(\n output: string,\n pid: number,\n execPath: string,\n sea = true,\n): ServiceManager | null {\n const pidMatch = /^\\s*pid = (\\d+)\\s*$/m.exec(output);\n if (pidMatch && Number(pidMatch[1]) === pid) return \"launchd\";\n\n const program = /^\\s*program = (.+?)\\s*$/m.exec(output);\n if (program) {\n const shown = program[1].replace(/^\"|\"$/g, \"\");\n if (path.resolve(shown) === path.resolve(execPath)) return \"launchd\";\n }\n\n if (!sea && /^\\s*state = /m.test(output)) return \"launchd\";\n return null;\n}\n\n/**\n * 解析 launchd plist 中 `EnvironmentVariables` 字典的键值。\n *\n * 只处理本脚本会写出的 `<key>K</key><string>V</string>` 形式;`<true/>` 之类的\n * 非字符串值忽略(更新配置用不到)。\n */\nexport function parseLaunchdEnvironment(xml: string): Record<string, string> {\n const out: Record<string, string> = {};\n const marker = xml.indexOf(\"<key>EnvironmentVariables</key>\");\n if (marker < 0) return out;\n const open = xml.indexOf(\"<dict>\", marker);\n if (open < 0) return out;\n const close = xml.indexOf(\"</dict>\", open);\n if (close < 0) return out;\n const body = xml.slice(open + \"<dict>\".length, close);\n for (const m of body.matchAll(/<key>([^<]+)<\\/key>\\s*<string>([^<]*)<\\/string>/g)) {\n out[m[1]] = m[2];\n }\n return out;\n}\n\n/**\n * 未注入 ZQAGENT_SERVICE_MANAGER 时的兜底检测。\n *\n * 服务进程的环境变量由 unit 注入,但运维在交互 shell 里手动执行\n * `zqagent update --status/--check/--now` 时没有这些变量——此前会被判成\n * 「未托管」,导致诊断结论错误、`--now` 直接拒绝执行。这里用 cgroup 归属\n * 兜底:Linux 上进程落在 `/<service>.service` 里,说明 systemd 确实会拉起它。\n *\n * 刻意不使用「/run/systemd/system 存在即视为托管」这类判据:那样任何在\n * systemd 机器上裸跑的进程都会通过自检,替换二进制后 `exit(70)` 却没人拉起,\n * 节点会直接下线。\n */\nfunction detectServiceManager(): ServiceManager | null {\n if (process.platform === \"darwin\") return detectLaunchd();\n if (process.platform !== \"linux\") return null;\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n return serviceManagerFromCgroup(\n fs.readFileSync(\"/proc/self/cgroup\", \"utf8\"),\n serviceName,\n );\n } catch {\n return null;\n }\n}\n\n/**\n * macOS 兜底:读取 launchd 作业描述,按 pid / program 判定(见\n * `serviceManagerFromLaunchctl`)。作业不存在、launchctl 不可用时返回 null。\n */\nfunction detectLaunchd(): ServiceManager | null {\n try {\n const res = spawnSync(\n \"launchctl\",\n [\"print\", `${launchdDomain()}/${launchdLabel()}`],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return null;\n return serviceManagerFromLaunchctl(\n res.stdout,\n process.pid,\n process.execPath,\n isSeaRuntime(),\n );\n } catch {\n return null;\n }\n}\n\n/**\n * 进程由哪个服务管理器托管;无托管返回 null。\n *\n * 优先采信 `deploy/service/install.sh` 注入的环境变量,缺失时在 Linux 上\n * 按 cgroup 归属兜底检测(见 `detectServiceManager`)。\n */\nexport function serviceManager(): ServiceManager | null {\n const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();\n if (v === \"systemd\" || v === \"launchd\") return v;\n return detectServiceManager();\n}\n\nexport function autoUpdateDisabled(): boolean {\n return process.env.ZQAGENT_AUTO_UPDATE?.trim() === \"0\";\n}\n\n/**\n * 解析 `systemctl show -p Environment --value` 的输出为键值表。\n *\n * 输出形如 `NODE_ENV=production PATH=/usr/local/bin:/usr/bin KEY=abc`。\n * 值里若含空格会被拆开——本函数只用于取公钥/通道/清单地址这类不含空格的\n * 变量,够用且无需处理 systemd 的引号规则。\n */\nexport function parseServiceEnvironment(raw: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const token of raw.trim().split(/\\s+/)) {\n const idx = token.indexOf(\"=\");\n if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);\n }\n return out;\n}\n\nlet cachedServiceEnv: Record<string, string> | null = null;\n\nfunction readServiceEnvironment(): Record<string, string> {\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n const res = spawnSync(\n \"systemctl\",\n [\"show\", serviceName, \"--property=Environment\", \"--value\", \"--no-pager\"],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return {};\n return parseServiceEnvironment(res.stdout);\n } catch {\n return {};\n }\n}\n\n/**\n * 读取 systemd unit 注入的环境变量。\n *\n * 服务进程拿得到这些变量,但运维在交互 shell 里手动执行 `zqagent update`\n * 时拿不到——公钥缺失会让自更新被判为不可用。这里按需从 unit 补齐,\n * 取不到(非 systemd、无 systemctl、超时)时返回 undefined。\n */\nexport function serviceEnvValue(key: string): string | undefined {\n if (process.platform === \"darwin\") {\n if (serviceManager() !== \"launchd\") return undefined;\n cachedLaunchdEnv ??= readLaunchdEnvironment();\n return cachedLaunchdEnv[key];\n }\n if (process.platform !== \"linux\") return undefined;\n if (serviceManager() !== \"systemd\") return undefined;\n cachedServiceEnv ??= readServiceEnvironment();\n return cachedServiceEnv[key];\n}\n\nlet cachedLaunchdEnv: Record<string, string> | null = null;\n\n/**\n * 读取 launchd plist 注入的环境变量。\n *\n * 与 systemd 的 `systemctl show -p Environment` 对应:干净 shell 里执行\n * `zqagent update --status` 时,公钥/通道/清单地址只能从服务定义里取回。\n */\nfunction readLaunchdEnvironment(): Record<string, string> {\n try {\n return parseLaunchdEnvironment(fs.readFileSync(launchdPlistPath(), \"utf8\"));\n } catch {\n return {};\n }\n}\n\nexport interface Capability {\n ok: boolean;\n /** 不满足时的可读原因(用于日志/`update --status`) */\n reason?: string;\n}\n\n/**\n * 自更新能力判定。`hasPublicKey` 由调用方传入(公钥只在验签时解析一次)。\n */\nexport function selfUpdateCapability(hasPublicKey: boolean): Capability {\n if (!isSeaRuntime()) {\n return {\n ok: false,\n reason: \"当前不是单文件(SEA)形态:请用 npm 升级(npm i -g @zqagent/cli@latest)\",\n };\n }\n if (!serviceManager()) {\n return {\n ok: false,\n reason: \"未被 systemd/launchd 托管:请用 deploy/service/install.sh 安装,或手动升级\",\n };\n }\n if (!hasPublicKey) {\n return { ok: false, reason: \"未配置 ZQAGENT_UPDATE_PUBKEY,无法验签,已禁用自动更新\" };\n }\n return { ok: true };\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/update/platform.ts","../../../../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.3.23_jiti@1.21.7_postcss@8.5.26_supports-color@7.2.0_tsx@4.23.12_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"],"sourcesContent":["/**\n * 运行形态与自更新能力判定。\n *\n * 只有「SEA 单文件 + 由 systemd/launchd 托管 + 配置了公钥」三者同时满足,\n * 才允许自动替换可执行文件并重启——否则退出后没人把它拉起来。\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\n/**\n * 当前版本:构建期注入(SEA/tsup define)优先,其次读包内 package.json,\n * 最后回退 \"dev\"。后两条让源码运行(tsx)也能显示真实版本。\n */\nexport function currentVersion(): string {\n const injected = process.env.ZQAGENT_VERSION?.trim();\n if (injected) return injected;\n try {\n const req = createRequire(import.meta.url);\n const pkg = req(\"../../package.json\") as { version?: string };\n if (pkg?.version) return pkg.version;\n } catch {\n // SEA 内取不到包信息,忽略\n }\n return \"dev\";\n}\n\n/**\n * 是否为 SEA 单文件运行。\n *\n * 判据优先取 execPath 的文件名:SEA 产物是 `zqagent`,经 node 运行的则是 `node`。\n * 构建期还会注入 `ZQAGENT_SEA=1`(见 tsup.sea.config.ts)作为权威标记。\n */\nexport function isSeaRuntime(): boolean {\n if (process.env.ZQAGENT_SEA === \"1\") return true;\n const base = path.basename(process.execPath).toLowerCase().replace(/\\.exe$/, \"\");\n return base !== \"node\" && base !== \"nodejs\";\n}\n\nexport type ServiceManager = \"systemd\" | \"launchd\";\n\n/** 未指定 ZQAGENT_SERVICE_NAME 时的默认 systemd unit 名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_NAME = \"zqagent\";\n\n/** 未指定 ZQAGENT_SERVICE_LABEL 时的默认 launchd 作业名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_LABEL = \"com.zqagent.node\";\n\n/**\n * 从 `/proc/self/cgroup` 的内容判定进程是否属于某个 systemd unit。\n *\n * 抽取为纯函数以便测试。兼容 cgroup v1(`1:name=systemd:/system.slice/x.service`)\n * 与 v2(`0::/system.slice/x.service`)两种行格式。\n *\n * 只认路径中**独立成段**的 `<serviceName>.service`:`my-zqagent.service`、\n * `zqagent.service.bak` 这类名字相近的 unit 不会被误判成 `zqagent.service`。\n */\nexport function serviceManagerFromCgroup(\n cgroup: string,\n serviceName: string = DEFAULT_SERVICE_NAME,\n): ServiceManager | null {\n const unitName = `${serviceName}.service`;\n for (const line of cgroup.split(\"\\n\")) {\n // 路径在最后一个冒号之后;v1 的 `name=systemd` 不会干扰\n const idx = line.lastIndexOf(\":\");\n if (idx < 0) continue;\n const unitPath = line.slice(idx + 1).trim();\n if (unitPath.split(\"/\").includes(unitName)) return \"systemd\";\n }\n return null;\n}\n\n/** launchd 作业所在域:root 装的是 LaunchDaemon(system),普通用户是 LaunchAgent(gui/<uid>) */\nfunction launchdDomain(): string {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n return uid === 0 ? \"system\" : `gui/${uid}`;\n}\n\nfunction launchdLabel(): string {\n return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;\n}\n\n/** 与 install.sh 写入的位置保持一致 */\nfunction launchdPlistPath(): string {\n const label = launchdLabel();\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;\n return path.join(os.homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\n/**\n * 从 `launchctl print <domain>/<label>` 的输出判定是否为 launchd 托管。\n *\n * macOS 没有 cgroup 可用,改用作业描述里的两处信息:\n * - `pid = N` 与当前进程一致 → 本进程就是该作业拉起的;\n * - `program = /path` 与 execPath 相同 → 服务运行的正是这个可执行文件,\n * 在交互 shell 里执行 `zqagent update` 时靠这条判定(Linux 靠 cgroup 归属)。\n *\n * `sea` 为 false(npm/source 形态)时作业存在即算托管:这种形态本来就不允许\n * 自替换二进制(见 selfUpdateCapability),这里只影响 `update --status` 的显示。\n */\nexport function serviceManagerFromLaunchctl(\n output: string,\n pid: number,\n execPath: string,\n sea = true,\n): ServiceManager | null {\n const pidMatch = /^\\s*pid = (\\d+)\\s*$/m.exec(output);\n if (pidMatch && Number(pidMatch[1]) === pid) return \"launchd\";\n\n const program = /^\\s*program = (.+?)\\s*$/m.exec(output);\n if (program) {\n const shown = program[1].replace(/^\"|\"$/g, \"\");\n if (path.resolve(shown) === path.resolve(execPath)) return \"launchd\";\n }\n\n if (!sea && /^\\s*state = /m.test(output)) return \"launchd\";\n return null;\n}\n\n/**\n * 解析 launchd plist 中 `EnvironmentVariables` 字典的键值。\n *\n * 只处理本脚本会写出的 `<key>K</key><string>V</string>` 形式;`<true/>` 之类的\n * 非字符串值忽略(更新配置用不到)。\n */\nexport function parseLaunchdEnvironment(xml: string): Record<string, string> {\n const out: Record<string, string> = {};\n const marker = xml.indexOf(\"<key>EnvironmentVariables</key>\");\n if (marker < 0) return out;\n const open = xml.indexOf(\"<dict>\", marker);\n if (open < 0) return out;\n const close = xml.indexOf(\"</dict>\", open);\n if (close < 0) return out;\n const body = xml.slice(open + \"<dict>\".length, close);\n for (const m of body.matchAll(/<key>([^<]+)<\\/key>\\s*<string>([^<]*)<\\/string>/g)) {\n out[m[1]] = m[2];\n }\n return out;\n}\n\n/**\n * 未注入 ZQAGENT_SERVICE_MANAGER 时的兜底检测。\n *\n * 服务进程的环境变量由 unit 注入,但运维在交互 shell 里手动执行\n * `zqagent update --status/--check/--now` 时没有这些变量——此前会被判成\n * 「未托管」,导致诊断结论错误、`--now` 直接拒绝执行。这里用 cgroup 归属\n * 兜底:Linux 上进程落在 `/<service>.service` 里,说明 systemd 确实会拉起它。\n *\n * 刻意不使用「/run/systemd/system 存在即视为托管」这类判据:那样任何在\n * systemd 机器上裸跑的进程都会通过自检,替换二进制后 `exit(70)` 却没人拉起,\n * 节点会直接下线。\n */\nfunction detectServiceManager(): ServiceManager | null {\n if (process.platform === \"darwin\") return detectLaunchd();\n if (process.platform !== \"linux\") return null;\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n return serviceManagerFromCgroup(\n fs.readFileSync(\"/proc/self/cgroup\", \"utf8\"),\n serviceName,\n );\n } catch {\n return null;\n }\n}\n\n/**\n * macOS 兜底:读取 launchd 作业描述,按 pid / program 判定(见\n * `serviceManagerFromLaunchctl`)。作业不存在、launchctl 不可用时返回 null。\n */\nfunction detectLaunchd(): ServiceManager | null {\n try {\n const res = spawnSync(\n \"launchctl\",\n [\"print\", `${launchdDomain()}/${launchdLabel()}`],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return null;\n return serviceManagerFromLaunchctl(\n res.stdout,\n process.pid,\n process.execPath,\n isSeaRuntime(),\n );\n } catch {\n return null;\n }\n}\n\n/**\n * 进程由哪个服务管理器托管;无托管返回 null。\n *\n * 优先采信 `deploy/service/install.sh` 注入的环境变量,缺失时在 Linux 上\n * 按 cgroup 归属兜底检测(见 `detectServiceManager`)。\n */\nexport function serviceManager(): ServiceManager | null {\n const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();\n if (v === \"systemd\" || v === \"launchd\") return v;\n return detectServiceManager();\n}\n\nexport function autoUpdateDisabled(): boolean {\n return process.env.ZQAGENT_AUTO_UPDATE?.trim() === \"0\";\n}\n\n/**\n * 解析 `systemctl show -p Environment --value` 的输出为键值表。\n *\n * 输出形如 `NODE_ENV=production PATH=/usr/local/bin:/usr/bin KEY=abc`。\n * 值里若含空格会被拆开——本函数只用于取公钥/通道/清单地址这类不含空格的\n * 变量,够用且无需处理 systemd 的引号规则。\n */\nexport function parseServiceEnvironment(raw: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const token of raw.trim().split(/\\s+/)) {\n const idx = token.indexOf(\"=\");\n if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);\n }\n return out;\n}\n\nlet cachedServiceEnv: Record<string, string> | null = null;\n\nfunction readServiceEnvironment(): Record<string, string> {\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n const res = spawnSync(\n \"systemctl\",\n [\"show\", serviceName, \"--property=Environment\", \"--value\", \"--no-pager\"],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return {};\n return parseServiceEnvironment(res.stdout);\n } catch {\n return {};\n }\n}\n\n/**\n * 读取 systemd unit 注入的环境变量。\n *\n * 服务进程拿得到这些变量,但运维在交互 shell 里手动执行 `zqagent update`\n * 时拿不到——公钥缺失会让自更新被判为不可用。这里按需从 unit 补齐,\n * 取不到(非 systemd、无 systemctl、超时)时返回 undefined。\n */\nexport function serviceEnvValue(key: string): string | undefined {\n if (process.platform === \"darwin\") {\n if (serviceManager() !== \"launchd\") return undefined;\n cachedLaunchdEnv ??= readLaunchdEnvironment();\n return cachedLaunchdEnv[key];\n }\n if (process.platform !== \"linux\") return undefined;\n if (serviceManager() !== \"systemd\") return undefined;\n cachedServiceEnv ??= readServiceEnvironment();\n return cachedServiceEnv[key];\n}\n\nlet cachedLaunchdEnv: Record<string, string> | null = null;\n\n/**\n * 读取 launchd plist 注入的环境变量。\n *\n * 与 systemd 的 `systemctl show -p Environment` 对应:干净 shell 里执行\n * `zqagent update --status` 时,公钥/通道/清单地址只能从服务定义里取回。\n */\nfunction readLaunchdEnvironment(): Record<string, string> {\n try {\n return parseLaunchdEnvironment(fs.readFileSync(launchdPlistPath(), \"utf8\"));\n } catch {\n return {};\n }\n}\n\nexport interface Capability {\n ok: boolean;\n /** 不满足时的可读原因(用于日志/`update --status`) */\n reason?: string;\n}\n\n/**\n * 自更新能力判定。`hasPublicKey` 由调用方传入(公钥只在验签时解析一次)。\n */\nexport function selfUpdateCapability(hasPublicKey: boolean): Capability {\n if (!isSeaRuntime()) {\n return {\n ok: false,\n reason: \"当前不是单文件(SEA)形态:请用 npm 升级(npm i -g @zqagent/cli@latest)\",\n };\n }\n if (!serviceManager()) {\n return {\n ok: false,\n reason: \"未被 systemd/launchd 托管:请用 deploy/service/install.sh 安装,或手动升级\",\n };\n }\n if (!hasPublicKey) {\n return { ok: false, reason: \"未配置 ZQAGENT_UPDATE_PUBKEY,无法验签,已禁用自动更新\" };\n }\n return { ok: true };\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n"],"mappings":"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","names":[]}
@@ -5,7 +5,7 @@ import * as path from "path";
5
5
  import { spawnSync } from "child_process";
6
6
  import { createRequire } from "module";
7
7
  function currentVersion() {
8
- const injected = "0.3.10"?.trim();
8
+ const injected = "0.3.11"?.trim();
9
9
  if (injected) return injected;
10
10
  try {
11
11
  const req = createRequire(import.meta.url);