fix(canvas-agent): preserve server disk reserve for sandbox tasks

This commit is contained in:
134 2026-10-20 17:43:00 +08:00
parent 20651ab231
commit 1e2f76a79f
2 changed files with 9 additions and 3 deletions

View File

@ -1,5 +1,5 @@
import { randomBytes, randomInt, createHash } from 'node:crypto'; import { randomBytes, randomInt, createHash } from 'node:crypto';
import { mkdir, readFile, writeFile, chmod, chown, readdir, lstat } from 'node:fs/promises'; import { mkdir, readFile, writeFile, chmod, chown, readdir, lstat, statfs } from 'node:fs/promises';
import { join, resolve } from 'node:path'; import { join, resolve } from 'node:path';
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline'; import { createInterface } from 'node:readline';
@ -35,6 +35,9 @@ export async function runNativeZcode(options) {
if (!/^[a-zA-Z0-9-]{1,80}$/.test(options.runId)) throw new Error('无效的 Agent 任务标识'); if (!/^[a-zA-Z0-9-]{1,80}$/.test(options.runId)) throw new Error('无效的 Agent 任务标识');
const manifest = await verifyRuntime(options.runtimeDir); const manifest = await verifyRuntime(options.runtimeDir);
const base = resolve(options.stateDir); await mkdir(base, { recursive: true, mode: 0o711 }); await chmod(base, 0o711); const base = resolve(options.stateDir); await mkdir(base, { recursive: true, mode: 0o711 }); await chmod(base, 0o711);
const disk = await statfs(base);
if (Number(disk.bavail) * Number(disk.bsize) < 512 * 1024 * 1024)
throw new Error('服务器可用磁盘空间不足,未启动 Agent 任务');
const root = join(base, options.runId); await ensurePrivate(root); const root = join(base, options.runId); await ensurePrivate(root);
const home = join(root, 'home'); const workspace = join(root, 'workspace'); const temp = join(root, 'tmp'); const home = join(root, 'home'); const workspace = join(root, 'workspace'); const temp = join(root, 'tmp');
for (const path of [home, workspace, temp, join(home, '.zcode/v2'), join(home, '.zcode/cli')]) await ensurePrivate(path); for (const path of [home, workspace, temp, join(home, '.zcode/v2'), join(home, '.zcode/cli')]) await ensurePrivate(path);
@ -143,10 +146,13 @@ export async function runNativeZcode(options) {
const deadline = setTimeout(() => stop('timeout'), options.timeoutMs || 20 * 60_000); const deadline = setTimeout(() => stop('timeout'), options.timeoutMs || 20 * 60_000);
const diskCheck = setInterval(async () => { const diskCheck = setInterval(async () => {
try { try {
const disk = await statfs(base);
if (Number(disk.bavail) * Number(disk.bsize) < 256 * 1024 * 1024) throw new Error('disk reserve');
let bytes = 0; let bytes = 0;
async function scan(directory) { async function scan(directory) {
for (const entry of await readdir(directory, { withFileTypes: true })) { for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name); const info = await lstat(path); const path = join(directory, entry.name); let info;
try { info = await lstat(path); } catch (error) { if (error.code === 'ENOENT') continue; throw error; }
if (info.isSymbolicLink()) continue; if (info.isSymbolicLink()) continue;
if (info.isDirectory()) await scan(path); else bytes += info.size; if (info.isDirectory()) await scan(path); else bytes += info.size;
if (bytes > 512 * 1024 * 1024) throw new Error('disk budget'); if (bytes > 512 * 1024 * 1024) throw new Error('disk budget');

View File

@ -14,7 +14,7 @@ export async function sandboxCommand(options) {
for (const file of ['/usr/bin/bwrap', '/usr/bin/setpriv', '/usr/bin/systemd-run']) for (const file of ['/usr/bin/bwrap', '/usr/bin/setpriv', '/usr/bin/systemd-run'])
if (!existsSync(file)) throw new Error('缺少 Linux Agent 沙箱组件,禁止宿主直接执行'); if (!existsSync(file)) throw new Error('缺少 Linux Agent 沙箱组件,禁止宿主直接执行');
const args = ['--quiet', '--scope', '--collect', `--unit=yedir-canvas-${options.runId}`, const args = ['--quiet', '--scope', '--collect', `--unit=yedir-canvas-${options.runId}`,
'--property=MemoryMax=1073741824', '--property=TasksMax=128', '--property=CPUQuota=200%', '--property=LimitCORE=0', '--property=MemoryMax=1073741824', '--property=TasksMax=128', '--property=CPUQuota=200%',
'/usr/bin/setpriv', `--reuid=${uid}`, `--regid=${uid}`, '--clear-groups', '/usr/bin/setpriv', `--reuid=${uid}`, `--regid=${uid}`, '--clear-groups',
'/usr/bin/bwrap', '--unshare-all', '--new-session', '--die-with-parent', '--cap-drop', 'ALL', '--clearenv', '/usr/bin/bwrap', '--unshare-all', '--new-session', '--die-with-parent', '--cap-drop', 'ALL', '--clearenv',
'--ro-bind', '/usr', '/usr', '--ro-bind', '/lib64', '/lib64', '--symlink', 'usr/bin', '/bin', '--symlink', 'usr/lib', '/lib', '--ro-bind', '/usr', '/usr', '--ro-bind', '/lib64', '/lib64', '--symlink', 'usr/bin', '/bin', '--symlink', 'usr/lib', '/lib',