#!/usr/bin/env node 'use strict'; const crypto = require('node:crypto'); const path = require('node:path'); const { spawnSync } = require('node:child_process'); class QingLong3ServiceManagerBridgeDockerGateError extends Error { constructor(message) { super(message); this.name = 'QingLong3ServiceManagerBridgeDockerGateError'; } } function fail(message) { throw new QingLong3ServiceManagerBridgeDockerGateError(message); } function run(args, options = {}) { const result = spawnSync('docker', args, { cwd: options.cwd, encoding: 'utf8', timeout: options.timeoutMs ?? 600_000, maxBuffer: 4 * 1024 * 1024, }); if (result.error || result.signal !== null || result.status !== 0) { fail( `docker ${args.join(' ')} failed: ${String(result.stderr ?? '').slice( 0, 2_048, )}`, ); } return String(result.stdout ?? '').trim(); } function waitReady(name, kind) { for (let attempt = 0; attempt < 60; attempt += 1) { const result = spawnSync( 'docker', [ 'exec', name, ...(kind === 'systemd' ? ['/usr/bin/systemctl', 'is-system-running'] : ['/bin/true']), ], { encoding: 'utf8', timeout: 5_000 }, ); if ( kind === 'systemd' ? result.status === 0 || String(result.stdout ?? '').trim() === 'degraded' : result.status === 0 ) { return; } Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250); } fail(`${kind} container did not become ready`); } function actorReport(name, kind, identityMode, scenario = 'success') { const stdout = run([ 'exec', name, 'node', '/workspace/scripts/ql3-service-manager-bridge-live-actor.cjs', kind, identityMode, scenario, ]); const report = JSON.parse(stdout); const requiredGates = [ 'rootCommandFile', 'descriptorInstalled', 'enabledAndStarted', 'exactReplay', 'stopped', 'ownerOutcomeVerified', 'serviceProcessIdentity', 'adoptedCutoverActive', 'adoptedCutoverStopped', 'adoptedRollbackPrepared', 'adoptedRollbackReplay', 'adoptedRollbackCandidate', 'adoptedRollbackAuthorized', 'adoptedRollbackAuthorizationReplay', 'adoptedLegacyStartReplay', 'adoptedLegacyConsumed', 'adoptedTargetRemainedStopped', ...(scenario === 'success' ? [ 'adoptedLegacyStarted', 'adoptedLegacyProcessIdentity', 'adoptedLegacyResponseLossInspected', 'adoptedLegacyReady', 'adoptedLegacyReadinessReplay', ] : [ 'adoptedLegacyBarrierCrash', 'adoptedLegacyInspectOnlyConvergence', 'adoptedLegacyRemainedStopped', 'adoptedLegacyManualConsumed', ]), ]; if ( report.managerKind !== kind || report.identityMode !== identityMode || report.scenario !== scenario || report.serviceUid !== (identityMode === 'root' ? 0 : 10001) || !requiredGates.every((gate) => report.gates?.[gate] === true) || (report.gates?.restartRequired === true && report.gates?.restarted !== true) || !/^[0-9a-f]{64}$/.test(report.sha256 ?? '') ) { fail(`${kind}/${identityMode} actor report is invalid`); } return report; } function main() { const root = path.resolve(__dirname, '..'); const suffix = `${process.pid}-${Date.now()}`; const targets = [ { kind: 'systemd', image: `ql3-service-manager-systemd-gate:${suffix}`, dockerfile: 'deploy/containers/ql3-service-manager-gate/systemd.Dockerfile', }, { kind: 'openrc', image: `ql3-service-manager-openrc-gate:${suffix}`, dockerfile: 'deploy/containers/ql3-service-manager-gate/openrc.Dockerfile', }, ]; const reports = []; const containers = []; try { for (const target of targets) { run(['build', '--file', target.dockerfile, '--tag', target.image, '.'], { cwd: root, }); for (const identityMode of ['root', 'nonroot']) { const name = `ql3-service-manager-${target.kind}-${identityMode}-${suffix}`; containers.push(name); const common = [ 'run', '--detach', '--privileged', '--name', name, '--mount', `type=bind,src=${root},dst=/workspace,readonly`, ]; run( target.kind === 'systemd' ? [ ...common, '--cgroupns=host', '--tmpfs', '/run', '--tmpfs', '/run/lock', '--volume', '/sys/fs/cgroup:/sys/fs/cgroup:rw', target.image, ] : [...common, '--tmpfs', '/run', target.image], ); waitReady(name, target.kind); reports.push(actorReport(name, target.kind, identityMode)); if (identityMode === 'root') { reports.push( actorReport(name, target.kind, identityMode, 'barrier-crash'), ); } run(['rm', '--force', name]); containers.splice(containers.indexOf(name), 1); } } const successReports = reports.filter( (report) => report.scenario === 'success', ); const crashReports = reports.filter( (report) => report.scenario === 'barrier-crash', ); const payload = { schemaVersion: 1, evidenceClass: 'qinglong3_service_manager_bridge_docker_gate', reports, gates: { systemdRoot: reports.some( (report) => report.managerKind === 'systemd' && report.identityMode === 'root', ), systemdNonRoot: reports.some( (report) => report.managerKind === 'systemd' && report.identityMode === 'nonroot', ), openrcRoot: reports.some( (report) => report.managerKind === 'openrc' && report.identityMode === 'root', ), openrcNonRoot: reports.some( (report) => report.managerKind === 'openrc' && report.identityMode === 'nonroot', ), rootCommandFile: successReports.every( (report) => report.gates.rootCommandFile, ), exactReplay: successReports.every((report) => report.gates.exactReplay), ownerOutcomeVerified: successReports.every( (report) => report.gates.ownerOutcomeVerified, ), serviceProcessIdentity: successReports.every( (report) => report.gates.serviceProcessIdentity, ), adoptedCutoverActive: successReports.every( (report) => report.gates.adoptedCutoverActive, ), adoptedCutoverStopped: successReports.every( (report) => report.gates.adoptedCutoverStopped, ), adoptedRollbackPrepared: successReports.every( (report) => report.gates.adoptedRollbackPrepared, ), adoptedRollbackReplay: successReports.every( (report) => report.gates.adoptedRollbackReplay, ), adoptedRollbackCandidate: successReports.every( (report) => report.gates.adoptedRollbackCandidate, ), adoptedRollbackAuthorized: successReports.every( (report) => report.gates.adoptedRollbackAuthorized, ), adoptedRollbackAuthorizationReplay: successReports.every( (report) => report.gates.adoptedRollbackAuthorizationReplay, ), adoptedLegacyStarted: successReports.every( (report) => report.gates.adoptedLegacyStarted, ), adoptedLegacyStartReplay: successReports.every( (report) => report.gates.adoptedLegacyStartReplay, ), adoptedLegacyConsumed: successReports.every( (report) => report.gates.adoptedLegacyConsumed, ), adoptedTargetRemainedStopped: successReports.every( (report) => report.gates.adoptedTargetRemainedStopped, ), adoptedLegacyProcessIdentity: successReports.every( (report) => report.gates.adoptedLegacyProcessIdentity, ), adoptedLegacyResponseLossInspected: successReports.every( (report) => report.gates.adoptedLegacyResponseLossInspected, ), adoptedLegacyReady: successReports.every( (report) => report.gates.adoptedLegacyReady, ), adoptedLegacyReadinessReplay: successReports.every( (report) => report.gates.adoptedLegacyReadinessReplay, ), adoptedLegacyBarrierCrash: crashReports.length === 2, adoptedLegacyInspectOnlyConvergence: crashReports.every( (report) => report.gates.adoptedLegacyInspectOnlyConvergence, ), adoptedLegacyRemainedStopped: crashReports.every( (report) => report.gates.adoptedLegacyRemainedStopped, ), adoptedLegacyManualConsumed: crashReports.every( (report) => report.gates.adoptedLegacyManualConsumed, ), systemdRestart: reports.some( (report) => report.managerKind === 'systemd' && report.gates.restarted, ), openrcRestart: reports.some( (report) => report.managerKind === 'openrc' && report.gates.restarted, ), }, }; process.stdout.write( `${JSON.stringify({ ...payload, sha256: crypto .createHash('sha256') .update(JSON.stringify(payload)) .digest('hex'), })}\n`, ); } finally { for (const name of containers.reverse()) { spawnSync('docker', ['rm', '--force', name], { encoding: 'utf8' }); } } } try { main(); } catch (error) { process.stderr.write( `${JSON.stringify({ code: 'QL3_SERVICE_MANAGER_BRIDGE_DOCKER_GATE_FAILED', name: error?.name ?? 'Error', message: error?.message ?? 'Docker gate failed', })}\n`, ); process.exitCode = 1; }