feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,94 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { LocalDeploymentConfigurationError } from './contract';
const MAX_DOCKER_OUTPUT_BYTES = 256 * 1024;
export interface LocalDeploymentDockerRequest {
readonly executable: string;
readonly socketPath: string;
readonly args: readonly string[];
readonly timeoutMs?: number;
}
export type LocalDeploymentDockerRunner = (
request: Readonly<LocalDeploymentDockerRequest>,
) => string;
export function runLocalDeploymentDockerCommand(
request: Readonly<LocalDeploymentDockerRequest>,
): string {
const configRoot = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-docker-command-')),
);
fs.chmodSync(configRoot, 0o700);
try {
const executableDirectory = path.dirname(request.executable);
const result = spawnSync(
request.executable,
[
'--host',
`unix://${request.socketPath}`,
'--config',
configRoot,
...request.args,
],
{
encoding: 'utf8',
maxBuffer: MAX_DOCKER_OUTPUT_BYTES,
timeout: request.timeoutMs ?? 30_000,
killSignal: 'SIGKILL',
env: {
PATH: `${executableDirectory}:/usr/local/bin:/usr/bin:/bin`,
HOME: configRoot,
DOCKER_CONFIG: configRoot,
NO_PROXY: '*',
no_proxy: '*',
},
},
);
if (
result.error ||
result.status !== 0 ||
typeof result.stdout !== 'string' ||
Buffer.byteLength(result.stdout, 'utf8') > MAX_DOCKER_OUTPUT_BYTES
) {
throw new LocalDeploymentConfigurationError(
'Docker command failed closed',
{ cause: result.error },
);
}
return result.stdout;
} finally {
fs.rmSync(configRoot, { recursive: true, force: true });
}
}
export function validateLocalDeploymentDockerSocket(
socketPath: string,
uid: number,
): void {
let stat: fs.Stats;
try {
stat = fs.lstatSync(socketPath);
} catch (error) {
throw new LocalDeploymentConfigurationError(
'dockerSocketPath is unavailable',
{ cause: error },
);
}
if (
!stat.isSocket() ||
stat.isSymbolicLink() ||
fs.realpathSync(socketPath) !== socketPath ||
(stat.uid !== 0 && stat.uid !== uid) ||
(stat.mode & 0o002) !== 0
) {
throw new LocalDeploymentConfigurationError(
'dockerSocketPath must be a canonical trusted Unix socket',
);
}
}
@@ -0,0 +1,310 @@
import fs from 'node:fs';
import path from 'node:path';
import { LocalDeploymentConfigurationError } from './contract';
const MAX_PUBLISHED_FILE_BYTES = 64 * 1024;
export function validatePrivateDirectory(
directory: string,
uid: number,
label: string,
): void {
let stat: fs.Stats;
try {
stat = fs.lstatSync(directory);
} catch (error) {
throw new LocalDeploymentConfigurationError(`${label} is unavailable`, {
cause: error,
});
}
if (
!stat.isDirectory() ||
stat.isSymbolicLink() ||
stat.uid !== uid ||
(stat.mode & 0o777) !== 0o700 ||
fs.realpathSync(directory) !== directory
) {
throw new LocalDeploymentConfigurationError(
`${label} must be a canonical current-UID 0700 directory`,
);
}
}
export function ensurePrivateDirectory(
directory: string,
uid: number,
label: string,
): 'prepared' | 'existing' {
let created = false;
try {
fs.mkdirSync(directory, { mode: 0o700 });
created = true;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
throw new LocalDeploymentConfigurationError(
`${label} cannot be created`,
{ cause: error },
);
}
}
validatePrivateDirectory(directory, uid, label);
return created ? 'prepared' : 'existing';
}
function boundedBytes(contents: string, label: string): Buffer {
const bytes = Buffer.from(contents, 'utf8');
if (bytes.byteLength < 2 || bytes.byteLength > MAX_PUBLISHED_FILE_BYTES) {
throw new LocalDeploymentConfigurationError(`${label} has an invalid size`);
}
return bytes;
}
function fileStat(
filePath: string,
bytes: Buffer,
mode: number,
uid: number,
allowedLinks: readonly number[],
label: string,
): fs.Stats {
let stat: fs.Stats;
try {
stat = fs.lstatSync(filePath);
} catch (error) {
throw new LocalDeploymentConfigurationError(`${label} is unavailable`, {
cause: error,
});
}
if (
!stat.isFile() ||
stat.isSymbolicLink() ||
stat.uid !== uid ||
(stat.mode & 0o777) !== mode ||
!allowedLinks.includes(stat.nlink) ||
stat.size !== bytes.byteLength
) {
throw new LocalDeploymentConfigurationError(`${label} identity is invalid`);
}
const actual = fs.readFileSync(filePath);
if (!bytes.equals(actual)) {
throw new LocalDeploymentConfigurationError(`${label} content drifted`);
}
return stat;
}
function stagePathFor(targetPath: string): string {
return path.join(
path.dirname(targetPath),
`.${path.basename(targetPath)}.ql3-deploy-stage`,
);
}
export function preflightPublishedFile(
targetPath: string,
contents: string,
mode: number,
uid: number,
label: string,
): void {
const bytes = boundedBytes(contents, label);
const stagePath = stagePathFor(targetPath);
const targetExists = fs.existsSync(targetPath);
const stageExists = fs.existsSync(stagePath);
const targetStat = targetExists
? fileStat(targetPath, bytes, mode, uid, [1, 2], label)
: null;
const stageStat = stageExists
? fileStat(stagePath, bytes, mode, uid, [1, 2], `${label} stage`)
: null;
if (
(targetStat?.nlink === 2 || stageStat?.nlink === 2) &&
(!targetStat ||
!stageStat ||
targetStat.dev !== stageStat.dev ||
targetStat.ino !== stageStat.ino)
) {
throw new LocalDeploymentConfigurationError(
`${label} stage identity drifted`,
);
}
}
function fsyncDirectory(directory: string): void {
const descriptor = fs.openSync(directory, fs.constants.O_RDONLY);
try {
fs.fsyncSync(descriptor);
} finally {
fs.closeSync(descriptor);
}
}
function writeStage(
stagePath: string,
bytes: Buffer,
mode: number,
uid: number,
label: string,
): void {
let descriptor: number | undefined;
let created = false;
try {
descriptor = fs.openSync(
stagePath,
fs.constants.O_WRONLY |
fs.constants.O_CREAT |
fs.constants.O_EXCL |
fs.constants.O_NOFOLLOW,
mode,
);
created = true;
fs.fchmodSync(descriptor, mode);
const stat = fs.fstatSync(descriptor);
if (!stat.isFile() || stat.uid !== uid || stat.nlink !== 1) {
throw new LocalDeploymentConfigurationError(
`${label} stage identity is invalid`,
);
}
let offset = 0;
while (offset < bytes.byteLength) {
const written = fs.writeSync(
descriptor,
bytes,
offset,
bytes.byteLength - offset,
);
if (written < 1) {
throw new LocalDeploymentConfigurationError(
`${label} stage write stalled`,
);
}
offset += written;
}
fs.fsyncSync(descriptor);
} catch (error) {
if (created) {
try {
fs.unlinkSync(stagePath);
} catch {
// A failed cleanup leaves a deterministic fail-closed stage.
}
}
if (error instanceof LocalDeploymentConfigurationError) throw error;
throw new LocalDeploymentConfigurationError(
`${label} stage cannot be written`,
{ cause: error },
);
} finally {
if (descriptor !== undefined) fs.closeSync(descriptor);
}
}
export function publishExactFile(
targetPath: string,
contents: string,
mode: number,
uid: number,
label: string,
): 'prepared' | 'existing' {
const bytes = boundedBytes(contents, label);
const directory = path.dirname(targetPath);
const stagePath = stagePathFor(targetPath);
preflightPublishedFile(targetPath, contents, mode, uid, label);
const existed = fs.existsSync(targetPath);
if (!fs.existsSync(stagePath) && !existed) {
writeStage(stagePath, bytes, mode, uid, label);
}
if (!fs.existsSync(targetPath)) {
try {
fs.linkSync(stagePath, targetPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
throw new LocalDeploymentConfigurationError(
`${label} cannot be published`,
{ cause: error },
);
}
}
fsyncDirectory(directory);
}
fileStat(targetPath, bytes, mode, uid, [1, 2], label);
if (fs.existsSync(stagePath)) {
const targetStat = fs.lstatSync(targetPath);
const stageStat = fileStat(
stagePath,
bytes,
mode,
uid,
[1, 2],
`${label} stage`,
);
if (
stageStat.nlink === 2 &&
(targetStat.dev !== stageStat.dev || targetStat.ino !== stageStat.ino)
) {
throw new LocalDeploymentConfigurationError(
`${label} stage identity drifted`,
);
}
fs.unlinkSync(stagePath);
fsyncDirectory(directory);
}
fileStat(targetPath, bytes, mode, uid, [1], label);
return existed ? 'existing' : 'prepared';
}
export function replaceExactFile(
targetPath: string,
expectedContents: string,
nextContents: string,
mode: number,
uid: number,
label: string,
): 'prepared' | 'existing' {
const expectedBytes = boundedBytes(expectedContents, `${label} expected`);
const nextBytes = boundedBytes(nextContents, `${label} next`);
if (expectedBytes.equals(nextBytes)) {
throw new LocalDeploymentConfigurationError(
`${label} replacement must change content`,
);
}
const directory = path.dirname(targetPath);
const stagePath = stagePathFor(targetPath);
let targetBytes: Buffer;
try {
targetBytes = fs.readFileSync(targetPath);
} catch (error) {
throw new LocalDeploymentConfigurationError(`${label} is unavailable`, {
cause: error,
});
}
if (targetBytes.equals(nextBytes)) {
fileStat(targetPath, nextBytes, mode, uid, [1], label);
if (fs.existsSync(stagePath)) {
fileStat(stagePath, nextBytes, mode, uid, [1], `${label} stage`);
fs.unlinkSync(stagePath);
fsyncDirectory(directory);
}
return 'existing';
}
if (!targetBytes.equals(expectedBytes)) {
throw new LocalDeploymentConfigurationError(
`${label} content does not match the expected revision`,
);
}
fileStat(targetPath, expectedBytes, mode, uid, [1], label);
if (fs.existsSync(stagePath)) {
fileStat(stagePath, nextBytes, mode, uid, [1], `${label} stage`);
} else {
writeStage(stagePath, nextBytes, mode, uid, label);
}
fileStat(targetPath, expectedBytes, mode, uid, [1], label);
fs.renameSync(stagePath, targetPath);
fsyncDirectory(directory);
fileStat(targetPath, nextBytes, mode, uid, [1], label);
return 'prepared';
}
export function syncPublishedDirectory(directory: string): void {
fsyncDirectory(directory);
}
@@ -0,0 +1,316 @@
import crypto from 'node:crypto';
import path from 'node:path';
import type { LocalSetupCommand } from '../../lifecycle/localSetup';
import type {
LocalDeploymentPrepareCommand,
LocalDeploymentProcessService,
} from './contract';
const CONTAINER_ROOT = '/var/lib/qinglong3';
export function composeProjectName(instanceId: string): string {
const slug = instanceId.replaceAll('.', '-').slice(0, 32);
const suffix = crypto
.createHash('sha256')
.update('qinglong:local-compose-project:v1\0', 'utf8')
.update(instanceId, 'utf8')
.digest('hex')
.slice(0, 12);
return `ql3-${slug}-${suffix}`;
}
export interface LocalDeploymentPaths {
readonly database: string;
readonly ownerPepperKeyring: string;
readonly ownerPepperBackup: string;
readonly localSecretKeyring: string;
readonly receipts: string;
readonly artifacts: string;
readonly pluginStaging: string;
readonly pluginActivation: string;
readonly service: string;
readonly applicationConfig: string;
readonly composeSelection: string;
readonly composeRevisions: string;
readonly composeRevisionLock: string;
readonly composeRollouts: string;
readonly composeRolloutLock: string;
readonly composeRolloutBackups: string;
readonly composeRestores: string;
readonly composeRestoreLock: string;
readonly composeRestoreSafeguards: string;
readonly composeEvidenceCollections: string;
readonly composeEvidenceCollectionLock: string;
readonly composeCollectedEvidence: string;
readonly composeCollectedRolloutBackups: string;
readonly composeCollectedRestoreSafeguards: string;
}
export function deploymentPaths(root: string): Readonly<LocalDeploymentPaths> {
return Object.freeze({
database: path.join(root, 'qinglong3.sqlite'),
ownerPepperKeyring: path.join(root, 'owner-peppers'),
ownerPepperBackup: path.join(root, 'owner-pepper-backup'),
localSecretKeyring: path.join(root, 'local-secret-keyring.json'),
receipts: path.join(root, 'receipts'),
artifacts: path.join(root, 'artifacts'),
pluginStaging: path.join(root, 'plugin-staging'),
pluginActivation: path.join(root, 'plugin-activation'),
service: path.join(root, 'service'),
applicationConfig: path.join(root, 'local-application.json'),
composeSelection: path.join(root, 'service', 'compose.image.yaml'),
composeRevisions: path.join(root, 'service', 'revisions'),
composeRevisionLock: path.join(root, 'service', '.compose-revision.lock'),
composeRollouts: path.join(root, 'service', 'rollouts'),
composeRolloutLock: path.join(root, 'service', '.compose-rollout.lock'),
composeRolloutBackups: path.join(root, 'service', 'rollout-backups'),
composeRestores: path.join(root, 'service', 'restores'),
composeRestoreLock: path.join(root, 'service', '.compose-restore.lock'),
composeRestoreSafeguards: path.join(root, 'service', 'restore-safeguards'),
composeEvidenceCollections: path.join(
root,
'service',
'evidence-collections',
),
composeEvidenceCollectionLock: path.join(
root,
'service',
'.compose-evidence-collection.lock',
),
composeCollectedEvidence: path.join(root, 'service', 'collected-evidence'),
composeCollectedRolloutBackups: path.join(
root,
'service',
'collected-evidence',
'rollout-backups',
),
composeCollectedRestoreSafeguards: path.join(
root,
'service',
'collected-evidence',
'restore-safeguards',
),
});
}
export function applicationConfiguration(
command: Readonly<LocalDeploymentPrepareCommand>,
paths: Readonly<LocalDeploymentPaths>,
): string {
const runtimeRoot =
command.options.service.kind === 'compose'
? CONTAINER_ROOT
: command.options.deploymentRoot;
const pageSize = command.options.profile === 'edge' ? 4 : 16;
return `${JSON.stringify(
{
schema: 'qinglong/local-application-process@v2',
instanceId: command.options.instanceId,
profile: command.options.profile,
storage: {
mode: 'fresh',
databasePath: path.join(runtimeRoot, path.basename(paths.database)),
...(command.options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: command.options.busyTimeoutMs }),
},
runtime: {
receiptRoot: path.join(runtimeRoot, path.basename(paths.receipts)),
artifactRoot: path.join(runtimeRoot, path.basename(paths.artifacts)),
secretKeyringPath: path.join(
runtimeRoot,
path.basename(paths.localSecretKeyring),
),
},
pluginPackages: {
stagingRoot: path.join(runtimeRoot, path.basename(paths.pluginStaging)),
activationRoot: path.join(
runtimeRoot,
path.basename(paths.pluginActivation),
),
recoverySource: {
mode: 'disabled',
},
pageSize,
maxPages: pageSize,
taskPublicationPageSize: pageSize,
taskPublicationMaxPages: pageSize,
},
ai: {
deployment: 'excluded',
},
},
null,
2,
)}\n`;
}
function systemdDescriptor(
command: Readonly<LocalDeploymentPrepareCommand>,
configPath: string,
uid: number,
gid: number,
): string {
const service = command.options.service as LocalDeploymentProcessService;
const edge = command.options.profile === 'edge';
return [
'[Unit]',
'Description=QingLong 3.0 local automation runtime',
'After=local-fs.target',
'',
'[Service]',
'Type=simple',
`User=${uid}`,
`Group=${gid}`,
`WorkingDirectory=${command.options.deploymentRoot}`,
`ExecStart=${service.nodeExecutable} ${service.applicationEntrypoint} --config ${configPath}`,
'Environment=NODE_ENV=production',
'UMask=0077',
'KillSignal=SIGTERM',
'TimeoutStopSec=30s',
'Restart=on-failure',
'RestartSec=5s',
'RestartPreventExitStatus=64',
'NoNewPrivileges=yes',
'PrivateTmp=yes',
'ProtectSystem=strict',
`ReadWritePaths=${command.options.deploymentRoot}`,
'ProtectKernelTunables=yes',
'ProtectKernelModules=yes',
'ProtectControlGroups=yes',
'RestrictSUIDSGID=yes',
'LockPersonality=yes',
`LimitNOFILE=${edge ? 1024 : 4096}`,
`TasksMax=${edge ? 64 : 256}`,
`MemoryMax=${edge ? '128M' : '256M'}`,
'',
'[Install]',
'WantedBy=multi-user.target',
'',
].join('\n');
}
function openrcDescriptor(
command: Readonly<LocalDeploymentPrepareCommand>,
configPath: string,
uid: number,
gid: number,
): string {
const service = command.options.service as LocalDeploymentProcessService;
const edge = command.options.profile === 'edge';
return [
'#!/sbin/openrc-run',
'',
'name="qinglong3"',
'description="QingLong 3.0 local automation runtime"',
`command="${service.nodeExecutable}"`,
`command_args="${service.applicationEntrypoint} --config ${configPath}"`,
`command_user="${uid}:${gid}"`,
`directory="${command.options.deploymentRoot}"`,
'supervisor="supervise-daemon"',
'respawn_delay=5',
'respawn_max=5',
'respawn_period=60',
'retry="TERM/30/KILL/5"',
'umask=0077',
`rc_ulimit="-n ${edge ? 1024 : 4096}"`,
'',
'depend() {',
' need localmount',
' after bootmisc',
'}',
'',
].join('\n');
}
function composeDescriptor(
command: Readonly<LocalDeploymentPrepareCommand>,
uid: number,
gid: number,
): string {
const edge = command.options.profile === 'edge';
return [
`name: ${composeProjectName(command.options.instanceId)}`,
'',
'services:',
' qinglong3:',
` user: "${uid}:${gid}"`,
' read_only: true',
' init: true',
' network_mode: none',
' command:',
' - --config',
` - ${CONTAINER_ROOT}/local-application.json`,
' volumes:',
' - type: bind',
` source: ${command.options.deploymentRoot}`,
` target: ${CONTAINER_ROOT}`,
' tmpfs:',
' - /tmp:rw,noexec,nosuid,nodev,size=16m',
' cap_drop:',
' - ALL',
' security_opt:',
' - no-new-privileges:true',
' restart: unless-stopped',
' stop_grace_period: 30s',
` mem_limit: ${edge ? '128m' : '256m'}`,
` pids_limit: ${edge ? 64 : 256}`,
'',
].join('\n');
}
export function descriptor(
command: Readonly<LocalDeploymentPrepareCommand>,
configPath: string,
uid: number,
gid: number,
): Readonly<{ fileName: string; contents: string; mode: number }> {
if (command.options.service.kind === 'systemd') {
return Object.freeze({
fileName: 'qinglong3.service',
contents: systemdDescriptor(command, configPath, uid, gid),
mode: 0o600,
});
}
if (command.options.service.kind === 'openrc') {
return Object.freeze({
fileName: 'qinglong3.openrc',
contents: openrcDescriptor(command, configPath, uid, gid),
mode: 0o700,
});
}
return Object.freeze({
fileName: 'compose.yaml',
contents: composeDescriptor(command, uid, gid),
mode: 0o600,
});
}
export function setupCommand(
command: Readonly<LocalDeploymentPrepareCommand>,
paths: Readonly<LocalDeploymentPaths>,
): Readonly<LocalSetupCommand> {
return Object.freeze({
schemaVersion: 1 as const,
operation: 'local.setup.prepare' as const,
options: Object.freeze({
deploymentRoot: command.options.deploymentRoot,
databasePath: paths.database,
profile: command.options.profile,
ownerPepperKeyringDirectory: paths.ownerPepperKeyring,
ownerPepperBackupDirectory: paths.ownerPepperBackup,
ownerPepperKeyId: command.request.ownerPepperKeyId,
localSecretKeyringPath: paths.localSecretKeyring,
...(command.options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: command.options.busyTimeoutMs }),
}),
request: Object.freeze({
registerMutationId: command.request.registerMutationId,
activateMutationId: command.request.activateMutationId,
registeredAtMs: command.request.registeredAtMs,
activatedAtMs: command.request.activatedAtMs,
}),
});
}