feat: add CentOS7 single executable build

This commit is contained in:
shiyue
2026-06-24 10:36:03 +08:00
parent a794607817
commit 67914ba10f
8 changed files with 365 additions and 26 deletions

203
scripts/build-single-exe.js Normal file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env node
'use strict';
const { spawnSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const DEFAULT_TARGET = 'bun-linux-x64-baseline';
const DEFAULT_OUTDIR = 'dist-exe';
const DEFAULT_NAME = 'cc-web';
function parseArgs(argv) {
const options = {
target: DEFAULT_TARGET,
outdir: DEFAULT_OUTDIR,
name: DEFAULT_NAME,
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const readValue = (name) => {
const inlinePrefix = `${name}=`;
if (arg.startsWith(inlinePrefix)) return arg.slice(inlinePrefix.length);
if (arg === name && index + 1 < argv.length) {
index += 1;
return argv[index];
}
return null;
};
const target = readValue('--target');
if (target !== null) {
options.target = target;
continue;
}
const outdir = readValue('--outdir');
if (outdir !== null) {
options.outdir = outdir;
continue;
}
const name = readValue('--name');
if (name !== null) {
options.name = name;
continue;
}
if (arg === '-h' || arg === '--help') {
options.help = true;
continue;
}
throw new Error(`未知参数: ${arg}`);
}
return options;
}
function printHelp() {
console.log(`用法:
npm run build:single-exe
node scripts/build-single-exe.js --target bun-linux-x64-baseline --outdir dist-exe --name cc-web
说明:
默认 target 是 ${DEFAULT_TARGET},用于兼容 CentOS 7 这类老 glibc Linux x64 系统。
该命令只打包 cc-web 服务本体Claude/Codex CLI 仍在运行时从宿主机 PATH 或 CLAUDE_PATH/CODEX_PATH 调用。`);
}
function isWindowsTarget(target) {
return /^bun-windows(?:-|$)/.test(String(target || ''));
}
function resolveBinaryName(target, name) {
return isWindowsTarget(target) && !name.endsWith('.exe') ? `${name}.exe` : name;
}
function copyFileIfExists(source, destination) {
if (!fs.existsSync(source)) return false;
fs.mkdirSync(path.dirname(destination), { recursive: true });
fs.copyFileSync(source, destination);
return true;
}
function copyDirIfExists(source, destination) {
if (!fs.existsSync(source)) return false;
fs.cpSync(source, destination, { recursive: true, force: true });
return true;
}
function writeRunningGuide(releaseDir, target, binaryName) {
const runCommand = isWindowsTarget(target) ? binaryName : `./${binaryName}`;
const content = `# cc-web single executable 运行说明
这个目录是 Bun single executable 发布包,面向 CentOS 7 / 老 glibc Linux 运行。
## 直接运行
\`\`\`bash
chmod +x ${binaryName}
PORT=8002 CC_WEB_PASSWORD='请改成强密码' ${runCommand}
\`\`\`
## 调用宿主机 Claude/Codex CLI
cc-web 不内置 Claude/Codex CLI。运行时会继续从宿主机 PATH 查找:
\`\`\`bash
export CLAUDE_PATH=/usr/local/bin/claude
export CODEX_PATH=/usr/local/bin/codex
${runCommand}
\`\`\`
如果不设置上述变量,默认命令名仍是 \`claude\`\`codex\`
## 目录说明
- \`public/\`:前端静态资源
- \`config/\`:运行时配置,首次启动会写入登录和通知配置
- \`sessions/\`:会话数据
- \`logs/\`:运行日志
不要把已有生产环境的 \`config/\`\`sessions/\` 误删或覆盖。
`;
fs.writeFileSync(path.join(releaseDir, 'RUNNING.md'), content, 'utf8');
}
function copyRuntimeAssets(projectRoot, releaseDir, target, binaryName) {
copyDirIfExists(path.join(projectRoot, 'public'), path.join(releaseDir, 'public'));
for (const file of ['.env.example', 'README.md', 'README.en.md', 'CHANGELOG.md', 'package.json']) {
copyFileIfExists(path.join(projectRoot, file), path.join(releaseDir, file));
}
for (const dir of ['.codex/skills', '.codex/prompts', '.agents/skills', '.agents/prompts']) {
copyDirIfExists(path.join(projectRoot, dir), path.join(releaseDir, dir));
}
for (const dir of ['config', 'sessions', 'logs']) {
fs.mkdirSync(path.join(releaseDir, dir), { recursive: true });
}
writeRunningGuide(releaseDir, target, binaryName);
}
function runBunBuild(projectRoot, target, outfile) {
const bunCommand = process.versions.bun ? process.execPath : (process.env.BUN_BIN || 'bun');
const args = [
'build',
'--compile',
'--no-compile-autoload-dotenv',
`--target=${target}`,
`--outfile=${outfile}`,
path.join(projectRoot, 'server.js'),
];
console.log(`[build:single-exe] ${bunCommand} ${args.join(' ')}`);
const result = spawnSync(bunCommand, args, {
cwd: projectRoot,
env: process.env,
stdio: 'inherit',
});
if (result.error && result.error.code === 'ENOENT') {
throw new Error('未找到 bun。请先在构建机安装 Bun或通过 BUN_BIN 指定 bun 可执行文件。');
}
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(`Bun 编译失败,退出码: ${result.status}`);
}
}
function main() {
const options = parseArgs(process.argv.slice(2));
if (options.help) {
printHelp();
return;
}
const projectRoot = path.resolve(__dirname, '..');
const target = options.target || DEFAULT_TARGET;
const binaryName = resolveBinaryName(target, options.name || DEFAULT_NAME);
const outdir = path.resolve(projectRoot, options.outdir || DEFAULT_OUTDIR);
const releaseDir = path.join(outdir, target);
const outfile = path.join(releaseDir, binaryName);
fs.rmSync(releaseDir, { recursive: true, force: true });
fs.mkdirSync(releaseDir, { recursive: true });
runBunBuild(projectRoot, target, outfile);
if (!isWindowsTarget(target)) fs.chmodSync(outfile, 0o755);
copyRuntimeAssets(projectRoot, releaseDir, target, binaryName);
console.log(`[build:single-exe] 发布目录: ${releaseDir}`);
console.log(`[build:single-exe] 可执行文件: ${outfile}`);
}
try {
main();
} catch (err) {
console.error(`[build:single-exe] ERROR: ${err.message || err}`);
process.exit(1);
}