From f73e11b3648902610cdbf6105ffd6cde178be71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Thu, 13 Aug 2026 18:32:38 +0800 Subject: [PATCH] feat(hololake): add fail-closed JD release broadcast origin --- .../hololake-native-desktop/foundation.json | 5 +- .../scripts/foundation.test.mjs | 4 +- .../scripts/release-broadcast-server.test.mjs | 129 ++++++++++ .../server/release-broadcast/README.md | 16 ++ .../hololake-release-broadcast.service | 35 +++ .../server/release-broadcast/server.mjs | 236 ++++++++++++++++++ 6 files changed, 423 insertions(+), 2 deletions(-) create mode 100644 product-source/hololake-native-desktop/scripts/release-broadcast-server.test.mjs create mode 100644 product-source/hololake-native-desktop/server/release-broadcast/README.md create mode 100644 product-source/hololake-native-desktop/server/release-broadcast/hololake-release-broadcast.service create mode 100644 product-source/hololake-native-desktop/server/release-broadcast/server.mjs diff --git a/product-source/hololake-native-desktop/foundation.json b/product-source/hololake-native-desktop/foundation.json index 997e3ef..5d1d26b 100644 --- a/product-source/hololake-native-desktop/foundation.json +++ b/product-source/hololake-native-desktop/foundation.json @@ -20,8 +20,11 @@ "release_package_signature_size_sha256_verification_implemented": true, "release_persistent_rollback_executor_implemented": true, "release_signed_notarized_pipeline_implemented": true, + "release_broadcast_candidate_service_implemented": true, + "release_broadcast_candidate_service_default_state": "EMPTY_FAIL_CLOSED_LOOPBACK_ONLY", + "release_broadcast_candidate_service_source": "server/release-broadcast/server.mjs", "release_pipeline_automatic_upload_allowed": false, - "release_production_activation_state": "BLOCKED_PENDING_JD_TRUST_SIGNED_PIPELINE_AND_APPLE_NOTARIZATION", + "release_production_activation_state": "BLOCKED_PENDING_PUBLIC_HTTPS_TRUST_UPDATER_KEY_PIPELINE_EXECUTION_AND_APPLE_NOTARIZATION", "tauri_update_artifacts_enabled": false, "tauri_update_artifacts_enablement_gate": "JD_CONTROLLER_PUBLIC_KEY_AND_SIGNED_RELEASE_PIPELINE_REQUIRED", "automatic_update_check_on_startup": false, diff --git a/product-source/hololake-native-desktop/scripts/foundation.test.mjs b/product-source/hololake-native-desktop/scripts/foundation.test.mjs index 3604cbc..db13cd2 100644 --- a/product-source/hololake-native-desktop/scripts/foundation.test.mjs +++ b/product-source/hololake-native-desktop/scripts/foundation.test.mjs @@ -45,10 +45,12 @@ test('release activation remains explicitly human controlled', () => { assert.equal(foundation.release_package_signature_size_sha256_verification_implemented, true) assert.equal(foundation.release_persistent_rollback_executor_implemented, true) assert.equal(foundation.release_signed_notarized_pipeline_implemented, true) + assert.equal(foundation.release_broadcast_candidate_service_implemented, true) + assert.equal(foundation.release_broadcast_candidate_service_default_state, 'EMPTY_FAIL_CLOSED_LOOPBACK_ONLY') assert.equal(foundation.release_pipeline_automatic_upload_allowed, false) assert.equal( foundation.release_production_activation_state, - 'BLOCKED_PENDING_JD_TRUST_SIGNED_PIPELINE_AND_APPLE_NOTARIZATION', + 'BLOCKED_PENDING_PUBLIC_HTTPS_TRUST_UPDATER_KEY_PIPELINE_EXECUTION_AND_APPLE_NOTARIZATION', ) }) diff --git a/product-source/hololake-native-desktop/scripts/release-broadcast-server.test.mjs b/product-source/hololake-native-desktop/scripts/release-broadcast-server.test.mjs new file mode 100644 index 0000000..e7cd050 --- /dev/null +++ b/product-source/hololake-native-desktop/scripts/release-broadcast-server.test.mjs @@ -0,0 +1,129 @@ +import assert from 'node:assert/strict' +import crypto from 'node:crypto' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' + +import { createReleaseServer, loadRuntimeState } from '../server/release-broadcast/server.mjs' + +const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex') + +const writeJson = (file, value) => fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 }) + +async function withServer(state, callback) { + const server = createReleaseServer(state) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + try { + return await callback(`http://127.0.0.1:${address.port}`) + } finally { + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())) + } +} + +function buildReleaseRoot() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-broadcast-')) + const releaseDirectory = path.join(root, 'releases', '0.2.0') + fs.mkdirSync(releaseDirectory, { recursive: true }) + const packageBytes = Buffer.from('signed-notarized-updater-placeholder') + const packageName = 'HoloLake.app.tar.gz' + fs.writeFileSync(path.join(releaseDirectory, packageName), packageBytes, { mode: 0o600 }) + writeJson(path.join(releaseDirectory, 'HOLOLAKE-CODESIGN.json'), { + schema: 'hololake.platform-code-signature-receipt/v1', + state: 'DEVELOPER_ID_SIGNATURE_STRICT_AND_GATEKEEPER_ACCEPTED', + }) + writeJson(path.join(releaseDirectory, 'HOLOLAKE-NOTARIZATION.json'), { + schema: 'hololake.apple-notarization-receipt/v1', + state: 'APPLE_NOTARIZATION_ACCEPTED_AND_STAPLED', + }) + const broadcast = { + schema: 'hololake.release-broadcast/v1', + releaseId: 'GH-HOLOLAKE-RELEASE-0.2.0', + version: '0.2.0', + pub_date: '2026-08-13T10:00:00.000Z', + notes: 'Signed release', + platforms: { + 'darwin-aarch64': { + url: `https://release.guanghu.test/releases/0.2.0/${packageName}`, + signature: 'trusted-updater-signature', + size: packageBytes.length, + sha256: sha256(packageBytes), + platformCodeSignatureReceipt: 'HOLOLAKE-CODESIGN', + notarizationReceipt: 'HOLOLAKE-NOTARIZATION', + }, + }, + hololake: { + features: ['Stage one'], + fixes: [], + compatibility: { minimumVersion: '0.1.0', dataMigrationRequired: false }, + restart: { required: true, automaticAllowed: false }, + rollback: { supported: true, healthReceiptRequired: true, previousVersion: '0.1.0' }, + }, + } + const broadcastPath = path.join(releaseDirectory, 'latest.json') + writeJson(broadcastPath, broadcast) + const broadcastSha256 = sha256(fs.readFileSync(broadcastPath)) + writeJson(path.join(releaseDirectory, 'pipeline-receipt.json'), { + schema: 'hololake.signed-release-pipeline-receipt/v1', + state: 'SIGNED_NOTARIZED_RELEASE_BROADCAST_READY_FOR_JD_CONTROLLER_UPLOAD', + broadcastSha256, + automaticUpload: false, + automaticActivation: false, + }) + writeJson(path.join(root, 'ACTIVE.json'), { + schema: 'hololake.release-broadcast-activation/v1', + state: 'HUMAN_APPROVED_SIGNED_NOTARIZED_RELEASE_ACTIVE', + releaseId: broadcast.releaseId, + version: broadcast.version, + broadcastRelativePath: 'releases/0.2.0/latest.json', + broadcastSha256, + pipelineReceiptRelativePath: 'releases/0.2.0/pipeline-receipt.json', + humanApprovalReceipt: 'GH-HUMAN-RELEASE-APPROVAL-001', + }) + return { root, packageBytes } +} + +test('empty release root stays healthy but returns Tauri-compatible 204 no update', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-empty-')) + const state = loadRuntimeState(root) + assert.equal(state.state, 'EMPTY_FAIL_CLOSED') + await withServer(state, async (base) => { + const health = await fetch(`${base}/health`) + assert.equal(health.status, 200) + assert.deepEqual((await health.json()).upstreamUpdateSources, []) + assert.equal((await fetch(`${base}/latest.json`)).status, 204) + }) +}) + +test('invalid activation locks the endpoint instead of falling back to an update', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-invalid-')) + writeJson(path.join(root, 'ACTIVE.json'), { schema: 'wrong', state: 'ACTIVE' }) + const state = loadRuntimeState(root) + assert.equal(state.state, 'LOCKED_INVALID_RELEASE_EVIDENCE') + await withServer(state, async (base) => { + assert.equal((await fetch(`${base}/health`)).status, 503) + assert.equal((await fetch(`${base}/latest.json`)).status, 503) + }) +}) + +test('only an exact human-approved signed notarized evidence chain becomes readable', async () => { + const fixture = buildReleaseRoot() + const state = loadRuntimeState(fixture.root) + assert.equal(state.state, 'READY_SIGNED_NOTARIZED_BROADCAST') + await withServer(state, async (base) => { + const latest = await fetch(`${base}/latest.json`) + assert.equal(latest.status, 200) + assert.equal((await latest.json()).releaseId, 'GH-HOLOLAKE-RELEASE-0.2.0') + const updater = await fetch(`${base}/releases/0.2.0/HoloLake.app.tar.gz`) + assert.equal(updater.status, 200) + assert.deepEqual(Buffer.from(await updater.arrayBuffer()), fixture.packageBytes) + assert.equal((await fetch(`${base}/unknown`)).status, 404) + }) +}) + +test('package tampering after pipeline output locks the whole release at startup', () => { + const fixture = buildReleaseRoot() + fs.appendFileSync(path.join(fixture.root, 'releases', '0.2.0', 'HoloLake.app.tar.gz'), 'tampered') + assert.equal(loadRuntimeState(fixture.root).state, 'LOCKED_INVALID_RELEASE_EVIDENCE') +}) diff --git a/product-source/hololake-native-desktop/server/release-broadcast/README.md b/product-source/hololake-native-desktop/server/release-broadcast/README.md new file mode 100644 index 0000000..d8af9a5 --- /dev/null +++ b/product-source/hololake-native-desktop/server/release-broadcast/README.md @@ -0,0 +1,16 @@ +# HoloLake release broadcast candidate + +This is the HoloLake-owned, loopback-only origin for signed update broadcasts. +It has no upstream software feed and never uploads or activates a release. + +Without `ACTIVE.json`, `/health` reports `EMPTY_FAIL_CLOSED` and `/latest.json` +returns HTTP 204, the updater protocol's explicit no-update result. An active +release is accepted only when the human activation record, immutable broadcast, +pipeline receipt, package bytes, Developer ID receipt, and Apple notarization +receipt form one exact evidence chain. Invalid evidence locks the whole release +endpoint until an operator fixes the evidence and explicitly restarts the +service. + +The service listens only on `127.0.0.1`. Public HTTPS routing, updater trust-key +provisioning, artifact upload, activation, and desktop rollout are independent +deployment gates. diff --git a/product-source/hololake-native-desktop/server/release-broadcast/hololake-release-broadcast.service b/product-source/hololake-native-desktop/server/release-broadcast/hololake-release-broadcast.service new file mode 100644 index 0000000..9e187f1 --- /dev/null +++ b/product-source/hololake-native-desktop/server/release-broadcast/hololake-release-broadcast.service @@ -0,0 +1,35 @@ +[Unit] +Description=HoloLake signed release broadcast candidate +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=hololake-release +Group=hololake-release +WorkingDirectory=/opt/guanghu/hololake-release-broadcast-candidate/current +Environment=HOLOLAKE_RELEASE_HOST=127.0.0.1 +Environment=HOLOLAKE_RELEASE_PORT=3940 +Environment=HOLOLAKE_RELEASE_STATE_ROOT=/var/lib/guanghu/hololake-release-broadcast +Environment=HOLOLAKE_RELEASE_ENFORCE_ROOT_OWNER=1 +ExecStart=/usr/bin/node /opt/guanghu/hololake-release-broadcast-candidate/current/server.mjs +Restart=on-failure +RestartSec=5 +UMask=0027 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +RestrictRealtime=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +IPAddressDeny=any +IPAddressAllow=localhost +ReadOnlyPaths=/opt/guanghu/hololake-release-broadcast-candidate +ReadOnlyPaths=/var/lib/guanghu/hololake-release-broadcast + +[Install] +WantedBy=multi-user.target diff --git a/product-source/hololake-native-desktop/server/release-broadcast/server.mjs b/product-source/hololake-native-desktop/server/release-broadcast/server.mjs new file mode 100644 index 0000000..818fca3 --- /dev/null +++ b/product-source/hololake-native-desktop/server/release-broadcast/server.mjs @@ -0,0 +1,236 @@ +#!/usr/bin/env node + +import crypto from 'node:crypto' +import fs from 'node:fs' +import http from 'node:http' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const MAX_CONTROL_BYTES = 1024 * 1024 +const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/ +const SHA256_PATTERN = /^[a-f0-9]{64}$/ + +const fail = (code) => { + throw new Error(code) +} + +const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex') + +const requireText = (value, code) => { + if (typeof value !== 'string' || value.trim() === '') fail(code) + return value.trim() +} + +function trustedRegularFile(file, { enforceRootOwner, maxBytes = null }) { + const info = fs.lstatSync(file) + if (!info.isFile() || info.isSymbolicLink()) fail('HOLOLAKE_RELEASE_FILE_NOT_REGULAR') + if ((info.mode & 0o022) !== 0) fail('HOLOLAKE_RELEASE_FILE_WRITABLE_BY_NON_OWNER') + if (enforceRootOwner && info.uid !== 0) fail('HOLOLAKE_RELEASE_FILE_NOT_ROOT_OWNED') + if (maxBytes !== null && info.size > maxBytes) fail('HOLOLAKE_RELEASE_CONTROL_FILE_TOO_LARGE') + return info +} + +function safeRelative(root, value, code) { + const relative = requireText(value, code) + if (path.isAbsolute(relative) || relative.includes('\0')) fail(code) + const resolved = path.resolve(root, relative) + if (!resolved.startsWith(`${root}${path.sep}`)) fail(code) + return resolved +} + +function readTrustedJson(file, options) { + trustedRegularFile(file, { ...options, maxBytes: MAX_CONTROL_BYTES }) + return { bytes: fs.readFileSync(file), value: JSON.parse(fs.readFileSync(file, 'utf8')) } +} + +function validatePlatform(platform, releaseDirectory, enforceRootOwner) { + const url = new URL(requireText(platform?.url, 'HOLOLAKE_RELEASE_PACKAGE_URL_REQUIRED')) + if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) { + fail('HOLOLAKE_RELEASE_PACKAGE_URL_INVALID') + } + const packageName = decodeURIComponent(path.posix.basename(url.pathname)) + if (!packageName || packageName === '.' || packageName === '..') fail('HOLOLAKE_RELEASE_PACKAGE_NAME_INVALID') + const packagePath = safeRelative(releaseDirectory, packageName, 'HOLOLAKE_RELEASE_PACKAGE_PATH_INVALID') + const packageInfo = trustedRegularFile(packagePath, { enforceRootOwner }) + if (!Number.isSafeInteger(platform.size) || platform.size <= 0 || platform.size !== packageInfo.size) { + fail('HOLOLAKE_RELEASE_PACKAGE_SIZE_MISMATCH') + } + if (!SHA256_PATTERN.test(platform.sha256 || '') || sha256(fs.readFileSync(packagePath)) !== platform.sha256) { + fail('HOLOLAKE_RELEASE_PACKAGE_SHA256_MISMATCH') + } + requireText(platform.signature, 'HOLOLAKE_RELEASE_PACKAGE_SIGNATURE_REQUIRED') + const codeReceiptId = requireText(platform.platformCodeSignatureReceipt, 'HOLOLAKE_RELEASE_CODESIGN_RECEIPT_REQUIRED') + const notarizationReceiptId = requireText(platform.notarizationReceipt, 'HOLOLAKE_RELEASE_NOTARIZATION_RECEIPT_REQUIRED') + const codeReceipt = readTrustedJson( + safeRelative(releaseDirectory, `${codeReceiptId}.json`, 'HOLOLAKE_RELEASE_CODESIGN_RECEIPT_PATH_INVALID'), + { enforceRootOwner }, + ).value + const notarizationReceipt = readTrustedJson( + safeRelative(releaseDirectory, `${notarizationReceiptId}.json`, 'HOLOLAKE_RELEASE_NOTARIZATION_RECEIPT_PATH_INVALID'), + { enforceRootOwner }, + ).value + if ( + codeReceipt?.schema !== 'hololake.platform-code-signature-receipt/v1' || + codeReceipt?.state !== 'DEVELOPER_ID_SIGNATURE_STRICT_AND_GATEKEEPER_ACCEPTED' + ) fail('HOLOLAKE_RELEASE_CODESIGN_RECEIPT_INVALID') + if ( + notarizationReceipt?.schema !== 'hololake.apple-notarization-receipt/v1' || + notarizationReceipt?.state !== 'APPLE_NOTARIZATION_ACCEPTED_AND_STAPLED' + ) fail('HOLOLAKE_RELEASE_NOTARIZATION_RECEIPT_INVALID') + return { pathname: url.pathname, file: packagePath, size: packageInfo.size } +} + +function loadActiveRelease(stateRoot, enforceRootOwner) { + const activationPath = path.join(stateRoot, 'ACTIVE.json') + if (!fs.existsSync(activationPath)) { + return { state: 'EMPTY_FAIL_CLOSED', release: null, reasonCode: null } + } + const activation = readTrustedJson(activationPath, { enforceRootOwner }).value + if ( + activation?.schema !== 'hololake.release-broadcast-activation/v1' || + activation?.state !== 'HUMAN_APPROVED_SIGNED_NOTARIZED_RELEASE_ACTIVE' + ) fail('HOLOLAKE_RELEASE_ACTIVATION_INVALID') + const releaseId = requireText(activation.releaseId, 'HOLOLAKE_RELEASE_ID_REQUIRED') + const version = requireText(activation.version, 'HOLOLAKE_RELEASE_VERSION_REQUIRED') + if (!VERSION_PATTERN.test(version)) fail('HOLOLAKE_RELEASE_VERSION_INVALID') + if (!SHA256_PATTERN.test(activation.broadcastSha256 || '')) fail('HOLOLAKE_RELEASE_BROADCAST_SHA256_INVALID') + requireText(activation.humanApprovalReceipt, 'HOLOLAKE_RELEASE_HUMAN_APPROVAL_REQUIRED') + const broadcastPath = safeRelative(stateRoot, activation.broadcastRelativePath, 'HOLOLAKE_RELEASE_BROADCAST_PATH_INVALID') + const pipelinePath = safeRelative(stateRoot, activation.pipelineReceiptRelativePath, 'HOLOLAKE_RELEASE_PIPELINE_PATH_INVALID') + const broadcastDocument = readTrustedJson(broadcastPath, { enforceRootOwner }) + const pipelineReceipt = readTrustedJson(pipelinePath, { enforceRootOwner }).value + if (sha256(broadcastDocument.bytes) !== activation.broadcastSha256) fail('HOLOLAKE_RELEASE_BROADCAST_SHA256_MISMATCH') + if ( + pipelineReceipt?.schema !== 'hololake.signed-release-pipeline-receipt/v1' || + pipelineReceipt?.state !== 'SIGNED_NOTARIZED_RELEASE_BROADCAST_READY_FOR_JD_CONTROLLER_UPLOAD' || + pipelineReceipt?.broadcastSha256 !== activation.broadcastSha256 || + pipelineReceipt?.automaticUpload !== false || + pipelineReceipt?.automaticActivation !== false + ) fail('HOLOLAKE_RELEASE_PIPELINE_RECEIPT_INVALID') + const broadcast = broadcastDocument.value + if ( + broadcast?.schema !== 'hololake.release-broadcast/v1' || + broadcast?.releaseId !== releaseId || + broadcast?.version !== version || + broadcast?.hololake?.restart?.required !== true || + broadcast?.hololake?.restart?.automaticAllowed !== false || + broadcast?.hololake?.rollback?.supported !== true || + broadcast?.hololake?.rollback?.healthReceiptRequired !== true || + !broadcast.platforms || typeof broadcast.platforms !== 'object' || Array.isArray(broadcast.platforms) || + Object.keys(broadcast.platforms).length === 0 + ) fail('HOLOLAKE_RELEASE_BROADCAST_INVALID') + const releaseDirectory = path.dirname(broadcastPath) + const packages = new Map() + for (const platform of Object.values(broadcast.platforms)) { + const accepted = validatePlatform(platform, releaseDirectory, enforceRootOwner) + if (packages.has(accepted.pathname)) fail('HOLOLAKE_RELEASE_PACKAGE_ROUTE_COLLISION') + packages.set(accepted.pathname, accepted) + } + return { + state: 'READY_SIGNED_NOTARIZED_BROADCAST', + release: { releaseId, version, broadcastBytes: broadcastDocument.bytes, packages }, + reasonCode: null, + } +} + +export function loadRuntimeState(stateRoot, { enforceRootOwner = false } = {}) { + const resolvedRoot = path.resolve(stateRoot) + try { + return loadActiveRelease(resolvedRoot, enforceRootOwner) + } catch (error) { + return { + state: 'LOCKED_INVALID_RELEASE_EVIDENCE', + release: null, + reasonCode: error instanceof Error ? error.message : 'HOLOLAKE_RELEASE_UNKNOWN_FAILURE', + } + } +} + +function jsonResponse(response, status, value) { + const body = Buffer.from(`${JSON.stringify(value)}\n`) + response.writeHead(status, { + 'Content-Type': 'application/json; charset=utf-8', + 'Content-Length': body.length, + 'Cache-Control': 'no-store', + 'X-Content-Type-Options': 'nosniff', + }) + response.end(body) +} + +export function createReleaseServer(runtimeState) { + return http.createServer((request, response) => { + response.setHeader('X-Content-Type-Options', 'nosniff') + if (!['GET', 'HEAD'].includes(request.method || '')) { + response.writeHead(405, { Allow: 'GET, HEAD', 'Cache-Control': 'no-store' }) + return response.end() + } + const pathname = new URL(request.url || '/', 'http://127.0.0.1').pathname + if (pathname === '/health') { + const status = runtimeState.state === 'LOCKED_INVALID_RELEASE_EVIDENCE' ? 503 : 200 + return jsonResponse(response, status, { + schema: 'hololake.release-broadcast-health/v1', + state: runtimeState.state, + updateAvailable: runtimeState.release !== null, + automaticUpload: false, + automaticActivation: false, + upstreamUpdateSources: [], + ...(runtimeState.release ? { releaseId: runtimeState.release.releaseId, version: runtimeState.release.version } : {}), + ...(runtimeState.reasonCode ? { reasonCode: runtimeState.reasonCode } : {}), + }) + } + if (pathname === '/latest.json') { + if (runtimeState.state === 'EMPTY_FAIL_CLOSED') { + response.writeHead(204, { 'Cache-Control': 'no-store' }) + return response.end() + } + if (!runtimeState.release) { + response.writeHead(503, { 'Cache-Control': 'no-store' }) + return response.end() + } + response.writeHead(200, { + 'Content-Type': 'application/json; charset=utf-8', + 'Content-Length': runtimeState.release.broadcastBytes.length, + 'Cache-Control': 'no-store', + 'X-Content-Type-Options': 'nosniff', + }) + return response.end(request.method === 'HEAD' ? undefined : runtimeState.release.broadcastBytes) + } + const packageFile = runtimeState.release?.packages.get(pathname) + if (packageFile) { + response.writeHead(200, { + 'Content-Type': 'application/octet-stream', + 'Content-Length': packageFile.size, + 'Cache-Control': 'public, max-age=31536000, immutable', + 'X-Content-Type-Options': 'nosniff', + }) + if (request.method === 'HEAD') return response.end() + return fs.createReadStream(packageFile.file).on('error', () => response.destroy()).pipe(response) + } + response.writeHead(404, { 'Cache-Control': 'no-store' }) + response.end() + }) +} + +export function main(env = process.env) { + const host = env.HOLOLAKE_RELEASE_HOST || '127.0.0.1' + if (host !== '127.0.0.1') fail('HOLOLAKE_RELEASE_LOOPBACK_BIND_REQUIRED') + const port = Number(env.HOLOLAKE_RELEASE_PORT || 3940) + if (!Number.isInteger(port) || port < 1024 || port > 65535) fail('HOLOLAKE_RELEASE_PORT_INVALID') + const stateRoot = path.resolve(env.HOLOLAKE_RELEASE_STATE_ROOT || '/var/lib/guanghu/hololake-release-broadcast') + const runtimeState = loadRuntimeState(stateRoot, { enforceRootOwner: env.HOLOLAKE_RELEASE_ENFORCE_ROOT_OWNER === '1' }) + const server = createReleaseServer(runtimeState) + server.on('clientError', (_error, socket) => socket.end('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n')) + server.listen(port, host, () => { + process.stdout.write(`${JSON.stringify({ schema: 'hololake.release-broadcast-startup/v1', host, port, state: runtimeState.state })}\n`) + }) + return server +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + try { + main() + } catch (error) { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`) + process.exitCode = 1 + } +}