#!/usr/bin/env node import { createHash, createPrivateKey, createPublicKey, sign } from 'node:crypto' import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises' import { basename, join } from 'node:path' import process from 'node:process' const root = new URL('..', import.meta.url).pathname const args = Object.fromEntries(process.argv.slice(2).map((item) => { const [key, ...rest] = item.replace(/^--/, '').split('=') return [key, rest.join('=')] })) const output = args.out const epoch = Number(args.epoch) const sourceRevision = args['source-revision'] const privateKeyBase64 = process.env.HOLOLAKE_ZERO_ORIGIN_PKCS8_BASE64 if (!output || !Number.isSafeInteger(epoch) || epoch < 1 || !/^[a-f0-9]{40}$/.test(sourceRevision || '')) { throw new Error('usage: --out=/absolute/path --epoch=1 --source-revision=<40 hex git revision>') } if (!privateKeyBase64) throw new Error('HOLOLAKE_ZERO_ORIGIN_PKCS8_BASE64 is required') const privateKey = createPrivateKey({ key: Buffer.from(privateKeyBase64, 'base64'), format: 'der', type: 'pkcs8' }) const publicSpki = createPublicKey(privateKey).export({ format: 'der', type: 'spki' }) const publicKeyBase64 = Buffer.from(publicSpki).subarray(-32).toString('base64') const releaseId = `public-distribution-${epoch}-${sourceRevision.slice(0, 12)}` const generatedAt = new Date().toISOString() function sha256(bytes) { return createHash('sha256').update(bytes).digest('hex') } function canonical(value) { if (Array.isArray(value)) return value.map(canonical) if (value && typeof value === 'object') { return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])])) } return value } function jsonBytes(value) { return Buffer.from(`${JSON.stringify(value, null, 2)}\n`) } function exactSignature(bytes) { return sign(null, bytes, privateKey).toString('base64') } await rm(output, { recursive: true, force: true }) await mkdir(join(output, 'zero-core'), { recursive: true }) await mkdir(join(output, 'marketplace', 'artifacts'), { recursive: true }) const artifact = { schema: 'hololake.public-zero-core-artifact/v1', planeNumber: 'HLP-DIST-PLANE-0001', epoch, version: '1.0.0', minimumHostVersion: '0.5.0', protocol: { gracePeriodDays: 7, lighthouseAnchorUrl: 'https://guanghu.chat/api/hololake/zero-core/lamp', lighthouseResolveUrl: 'https://guanghulab.com/api/ai/v1/resolve?id=', enterpriseResolveUrl: 'https://guanghu.chat/api/hololake/enterprise/resolve', coreChannelSource: 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart', origin: 'HLP-PUBLIC-ZERO-CORE-TRUST-001 · public scope only', }, } const artifactRaw = jsonBytes(artifact) const lamp = { schema: 'hololake.public-zero-core-lamp/v1', planeNumber: 'HLP-DIST-PLANE-0001', epoch, version: '1.0.0', contentRootSha256: sha256(artifactRaw), artifactManifestUrl: 'https://guanghu.chat/api/hololake/zero-core/artifact', signatureUrl: 'https://guanghu.chat/api/hololake/zero-core/lamp.sig', publishedAt: generatedAt, minimumHostVersion: '0.5.0', } const lampRaw = jsonBytes(lamp) await writeFile(join(output, 'zero-core', 'artifact.json'), artifactRaw) await writeFile(join(output, 'zero-core', 'lamp.json'), lampRaw) const physicalDefinitions = [ { file: 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001-0.1.0.ghmod', summary: '把当前账号的知识目录投影为只读结构视图;不改写知识原件。', }, { file: 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod', summary: '教育文档、表格、作业导入与需本人确认的自动化工作台。', }, ] const entries = [] for (const definition of physicalDefinitions) { const source = join(root, 'fixtures', 'module-packages', definition.file) const signatureSource = `${source}.sig` const raw = await readFile(source) const packageValue = JSON.parse(raw) const manifest = packageValue.manifest const artifactSha256 = sha256(raw) const artifactFile = `${artifactSha256}.ghmod` const signatureFile = `${artifactSha256}.ghmod.sig` await copyFile(source, join(output, 'marketplace', 'artifacts', artifactFile)) await copyFile(signatureSource, join(output, 'marketplace', 'artifacts', signatureFile)) entries.push({ itemNumber: manifest.moduleNumber, artifactKind: 'PHYSICAL_MODULE', displayName: manifest.displayName, summary: definition.summary, version: manifest.version, minimumHostVersion: manifest.minimumHostVersion, sourceRepository: 'https://guanghulab.com/code/bingshuo/hololake-system-architecture', sourceRevision, sourcePath: `product-source/hololake-native-desktop/fixtures/module-packages/${definition.file}`, artifactUrl: `https://guanghu.chat/api/hololake/marketplace/artifacts/${artifactFile}`, artifactSha256, signatureUrl: `https://guanghu.chat/api/hololake/marketplace/artifacts/${signatureFile}`, adapter: manifest.adapter, permissions: manifest.permissions, executionAuthority: false, skillReadonlyGuarantee: false, }) } const skillFiles = [ 'HLP-SKILL-OFFICIAL-DELIVERY-VERIFICATION-0001-0.1.0.ghskill', 'HLP-SKILL-OFFICIAL-MODULE-BOUNDARY-REVIEW-0001-0.1.0.ghskill', ] for (const file of skillFiles) { const source = join(root, 'marketplace', 'skills', file) const raw = await readFile(source) const skill = JSON.parse(raw) const artifactSha256 = sha256(raw) const artifactFile = `${artifactSha256}.ghskill` await copyFile(source, join(output, 'marketplace', 'artifacts', artifactFile)) entries.push({ itemNumber: skill.manifest.skillNumber, artifactKind: 'COGNITIVE_SKILL', displayName: skill.manifest.displayName, summary: skill.payload.purpose, version: skill.manifest.version, minimumHostVersion: skill.manifest.minimumHostVersion, sourceRepository: 'https://guanghulab.com/code/bingshuo/hololake-system-architecture', sourceRevision, sourcePath: `product-source/hololake-native-desktop/marketplace/skills/${file}`, artifactUrl: `https://guanghu.chat/api/hololake/marketplace/artifacts/${artifactFile}`, artifactSha256, signatureUrl: null, adapter: null, permissions: [], executionAuthority: false, skillReadonlyGuarantee: true, }) } entries.sort((left, right) => left.itemNumber.localeCompare(right.itemNumber)) const catalog = { schema: 'hololake.marketplace.catalog/v1', planeNumber: 'HLP-DIST-PLANE-0003', epoch, generatedAt, minimumHostVersion: '0.5.0', contentRootSha256: sha256(Buffer.from(JSON.stringify(canonical(entries)))), entries, } const catalogRaw = jsonBytes(catalog) await writeFile(join(output, 'marketplace', 'catalog.json'), catalogRaw) const authorization = { schema: 'hololake.origin-release-authorization/v1', releaseId, sourceRevision, epoch, signer: { signerId: 'HLP-SIGNER-ZERO-POINT-ORIGIN-PUBLIC-0001', signerClass: 'ZERO_POINT_ORIGIN_PUBLIC_SCOPE_SIGNER', algorithm: 'Ed25519', publicKeyBase64, }, signedObjects: [ { name: 'zero-core/lamp.json', sha256: sha256(lampRaw), signatureBase64: exactSignature(lampRaw) }, { name: 'marketplace/catalog.json', sha256: sha256(catalogRaw), signatureBase64: exactSignature(catalogRaw) }, ], } await writeFile(join(output, 'origin-authorization.json'), jsonBytes(authorization)) await writeFile(join(output, 'release-metadata.json'), jsonBytes({ schema: 'hololake.public-distribution-release/v1', releaseId, epoch, sourceRevision, generatedAt, originPublicKeyBase64: publicKeyBase64, artifacts: entries.map((entry) => ({ name: basename(entry.artifactUrl), sha256: entry.artifactSha256 })) })) process.stdout.write(`${JSON.stringify({ releaseId, epoch, sourceRevision, itemCount: entries.length, originPublicKeyBase64: publicKeyBase64 })}\n`)