feat(hololake): bind release chain to public path namespace

This commit is contained in:
冰朔 2026-08-13 19:11:26 +08:00
commit 9006075310
13 changed files with 223 additions and 29 deletions

View file

@ -5,7 +5,7 @@ import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { isMainModule, loadRuntimeState } from './server.mjs'
import { DEFAULT_PUBLIC_PREFIX, isMainModule, loadRuntimeState, normalizePublicPrefix } from './server.mjs'
const MAX_CONTROL_BYTES = 1024 * 1024
const SHA256_PATTERN = /^[a-f0-9]{64}$/
@ -54,7 +54,7 @@ function directFile(directory, name, code) {
return resolved
}
function packageNameFromUrl(value) {
function packageNameFromUrl(value, publicPrefix) {
const url = new URL(requireText(value, 'HOLOLAKE_RELEASE_OPERATOR_PACKAGE_URL_REQUIRED'))
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {
fail('HOLOLAKE_RELEASE_OPERATOR_PACKAGE_URL_INVALID')
@ -66,6 +66,9 @@ function packageNameFromUrl(value) {
fail('HOLOLAKE_RELEASE_OPERATOR_PACKAGE_URL_INVALID')
}
if (!packageName || packageName === '.' || packageName === '..') fail('HOLOLAKE_RELEASE_OPERATOR_PACKAGE_NAME_INVALID')
if (!url.pathname.startsWith(`${publicPrefix}/`) || url.pathname === `${publicPrefix}/latest.json` || url.pathname === `${publicPrefix}/health`) {
fail('HOLOLAKE_RELEASE_OPERATOR_PACKAGE_PUBLIC_PREFIX_MISMATCH')
}
return packageName
}
@ -75,8 +78,13 @@ function addArtifact(artifacts, source, name, type) {
artifacts.set(name, { source, name, type })
}
export function inspectReleaseBundle(sourceDirectory, humanApprovalPath, { enforceRootOwner = false } = {}) {
export function inspectReleaseBundle(
sourceDirectory,
humanApprovalPath,
{ enforceRootOwner = false, publicPrefix = DEFAULT_PUBLIC_PREFIX } = {},
) {
const source = path.resolve(sourceDirectory)
const normalizedPublicPrefix = normalizePublicPrefix(publicPrefix)
requireTrustedDirectory(source, { enforceRootOwner })
const latestPath = directFile(source, 'latest.json', 'HOLOLAKE_RELEASE_OPERATOR_BROADCAST_PATH_INVALID')
const pipelinePath = directFile(source, 'pipeline-receipt.json', 'HOLOLAKE_RELEASE_OPERATOR_PIPELINE_PATH_INVALID')
@ -125,7 +133,7 @@ export function inspectReleaseBundle(sourceDirectory, humanApprovalPath, { enfor
addArtifact(artifacts, pipelinePath, 'pipeline-receipt.json', 'control')
addArtifact(artifacts, approvalPath, 'human-approval.json', 'control')
for (const platform of Object.values(broadcast.platforms)) {
const packageName = packageNameFromUrl(platform?.url)
const packageName = packageNameFromUrl(platform?.url, normalizedPublicPrefix)
const packagePath = directFile(source, packageName, 'HOLOLAKE_RELEASE_OPERATOR_PACKAGE_PATH_INVALID')
const packageInfo = requireTrustedFile(packagePath, { enforceRootOwner })
if (!Number.isSafeInteger(platform.size) || platform.size <= 0 || platform.size !== packageInfo.size) {
@ -166,6 +174,7 @@ export function inspectReleaseBundle(sourceDirectory, humanApprovalPath, { enfor
sourceCommit: pipelineReceipt.sourceCommit,
broadcastSha256,
approvalId,
publicPrefix: normalizedPublicPrefix,
artifacts: [...artifacts.values()],
}
}
@ -235,7 +244,7 @@ function prepareCandidate(candidateRoot, plan, { enforceRootOwner, trustedGroupI
}
fs.chownSync(activationPath, 0, trustedGroupId)
}
const state = loadRuntimeState(candidateRoot, { enforceRootOwner })
const state = loadRuntimeState(candidateRoot, { enforceRootOwner, publicPrefix: plan.publicPrefix })
if (state.state !== 'READY_SIGNED_NOTARIZED_BROADCAST') {
fail(`HOLOLAKE_RELEASE_OPERATOR_CANDIDATE_INVALID:${state.reasonCode || state.state}`)
}
@ -253,12 +262,12 @@ function requireExpected(plan, expected) {
}
}
function replaceActivation(stateRoot, candidateActivation, previousBytes, enforceRootOwner, trustedGroupId) {
function replaceActivation(stateRoot, candidateActivation, previousBytes, enforceRootOwner, trustedGroupId, publicPrefix) {
const tempActivation = path.join(stateRoot, `.ACTIVE.${crypto.randomUUID()}.tmp`)
copyFileNoFollow(candidateActivation, tempActivation, enforceRootOwner ? 0o640 : 0o600)
if (enforceRootOwner) fs.chownSync(tempActivation, 0, trustedGroupId)
fs.renameSync(tempActivation, path.join(stateRoot, 'ACTIVE.json'))
const committed = loadRuntimeState(stateRoot, { enforceRootOwner })
const committed = loadRuntimeState(stateRoot, { enforceRootOwner, publicPrefix })
if (committed.state === 'READY_SIGNED_NOTARIZED_BROADCAST') return committed
const failedActive = path.join(stateRoot, 'ACTIVE.json')
if (previousBytes === null) {
@ -312,7 +321,14 @@ export function verifyReleaseBundle(sourceDirectory, humanApprovalPath) {
}
}
export function activateRelease({ sourceDirectory, humanApprovalPath, stateRoot, expected, enforceRootOwner = true }) {
export function activateRelease({
sourceDirectory,
humanApprovalPath,
stateRoot,
expected,
enforceRootOwner = true,
publicPrefix = DEFAULT_PUBLIC_PREFIX,
}) {
if (enforceRootOwner && process.getuid?.() !== 0) fail('HOLOLAKE_RELEASE_OPERATOR_ROOT_REQUIRED')
const root = path.resolve(stateRoot)
const rootInfo = requireTrustedDirectory(root, { enforceRootOwner })
@ -320,7 +336,7 @@ export function activateRelease({ sourceDirectory, humanApprovalPath, stateRoot,
const lockDirectory = acquireOperatorLock(root, enforceRootOwner, trustedGroupId)
let candidateRoot = null
try {
const plan = inspectReleaseBundle(sourceDirectory, humanApprovalPath, { enforceRootOwner })
const plan = inspectReleaseBundle(sourceDirectory, humanApprovalPath, { enforceRootOwner, publicPrefix })
requireExpected(plan, expected)
candidateRoot = fs.mkdtempSync(path.join(root, '.candidate-'))
const candidate = prepareCandidate(candidateRoot, plan, { enforceRootOwner, trustedGroupId })
@ -338,7 +354,14 @@ export function activateRelease({ sourceDirectory, humanApprovalPath, stateRoot,
fs.renameSync(candidate.releaseDirectory, finalRelease)
let committed
try {
committed = replaceActivation(root, candidate.activationPath, previousBytes, enforceRootOwner, trustedGroupId)
committed = replaceActivation(
root,
candidate.activationPath,
previousBytes,
enforceRootOwner,
trustedGroupId,
plan.publicPrefix,
)
} catch (error) {
fs.rmSync(finalRelease, { recursive: true, force: true })
throw error
@ -351,6 +374,7 @@ export function activateRelease({ sourceDirectory, humanApprovalPath, stateRoot,
sourceCommit: plan.sourceCommit,
broadcastSha256: plan.broadcastSha256,
approvalId: plan.approvalId,
publicPrefix: plan.publicPrefix,
automaticUpload: false,
automaticActivation: false,
automaticRestart: false,
@ -386,6 +410,7 @@ export function main(argv = process.argv.slice(2)) {
sourceDirectory,
humanApprovalPath,
stateRoot: requireText(values['state-root'], 'HOLOLAKE_RELEASE_OPERATOR_STATE_ROOT_REQUIRED'),
publicPrefix: values['public-prefix'] || DEFAULT_PUBLIC_PREFIX,
expected: {
releaseId: values['expect-release-id'],
version: values['expect-version'],