@xcanwin/manyoyo 3.2.0 → 3.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/bin/manyoyo.js +236 -40
- package/docker/manyoyo.Dockerfile +88 -26
- package/docs/README_EN.md +16 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[English](docs/README_EN.md) |
|
|
1
|
+
[English](docs/README_EN.md) | << [中文](README.md) >>
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
@@ -49,17 +49,21 @@ npm install -g .
|
|
|
49
49
|
# 拉取基础镜像
|
|
50
50
|
podman pull ubuntu:24.04
|
|
51
51
|
|
|
52
|
-
# 使用 manyoyo
|
|
53
|
-
manyoyo --ib
|
|
54
|
-
manyoyo --ib common
|
|
55
|
-
manyoyo --ib go,codex,java,gemini
|
|
52
|
+
# 使用 manyoyo 构建镜像(推荐,自动使用缓存加速)
|
|
53
|
+
manyoyo --ib # 默认构建 full 版本(推荐)
|
|
54
|
+
manyoyo --ib --iba TOOL=common # 构建常见组件版本
|
|
55
|
+
manyoyo --ib --iba TOOL=go,codex,java,gemini # 构建自定义组件版本
|
|
56
|
+
manyoyo --ib --iba --iba GIT_SSL_NO_VERIFY=true # 构建 full 版本且git跳过ssl认证
|
|
57
|
+
manyoyo --ib full --in myimage --iv 2.0.0 # 自定义镜像名称和版本,得到 myimage:2.0.0-full
|
|
58
|
+
manyoyo --ip # 清理悬空镜像和 <none> 镜像
|
|
56
59
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
+
# 工作原理:
|
|
61
|
+
# - 首次构建:自动下载 Node.js、JDT LSP、gopls 等到 docker/cache/
|
|
62
|
+
# - 2天内再次构建:直接使用本地缓存,速度提升约 5 倍
|
|
63
|
+
# - 缓存过期后:自动重新下载最新版本
|
|
60
64
|
|
|
61
65
|
# 或手动构建(不推荐)
|
|
62
|
-
iv=1.4.0 && podman build -t localhost/xcanwin/manyoyo:$iv-
|
|
66
|
+
iv=1.4.0 && podman build -t localhost/xcanwin/manyoyo:$iv-full -f docker/manyoyo.Dockerfile . --build-arg TOOL=full --no-cache
|
|
63
67
|
podman image prune -f
|
|
64
68
|
```
|
|
65
69
|
|
|
@@ -207,7 +211,9 @@ docker ps -a
|
|
|
207
211
|
| `-x CMD` | `--sf`, `--shell-full` | 完整命令(替代 --sp, -s 和 --) |
|
|
208
212
|
| `-y CLI` | `--yolo` | 无需确认运行 AI 智能体 |
|
|
209
213
|
| `-m MODE` | `--cm`, `--cont-mode` | 设置容器模式(common, dind, mdsock) |
|
|
210
|
-
| `--ib
|
|
214
|
+
| `--ib` | `--image-build` | 构建镜像 |
|
|
215
|
+
| `--iba XXX=YYY` | `--image-build-arg` | 构建镜像时传参给dockerfile |
|
|
216
|
+
| `--ip` | `--image-prune` | 清理悬空镜像和 `<none>` 镜像 |
|
|
211
217
|
| `--install NAME` | | 安装 manyoyo 命令 |
|
|
212
218
|
| `-V` | `--version` | 显示版本 |
|
|
213
219
|
| `-h` | `--help` | 显示帮助 |
|
package/bin/manyoyo.js
CHANGED
|
@@ -25,14 +25,14 @@ let CONTAINER_NAME = `myy-${formatDate()}`;
|
|
|
25
25
|
let HOST_PATH = process.cwd();
|
|
26
26
|
let CONTAINER_PATH = HOST_PATH;
|
|
27
27
|
let IMAGE_NAME = "localhost/xcanwin/manyoyo";
|
|
28
|
-
let IMAGE_VERSION = `${IMAGE_VERSION_BASE}-
|
|
28
|
+
let IMAGE_VERSION = `${IMAGE_VERSION_BASE}-full`;
|
|
29
29
|
let EXEC_COMMAND = "";
|
|
30
30
|
let EXEC_COMMAND_PREFIX = "";
|
|
31
31
|
let EXEC_COMMAND_SUFFIX = "";
|
|
32
32
|
let ENV_FILE = "";
|
|
33
33
|
let SHOULD_REMOVE = false;
|
|
34
|
-
let
|
|
35
|
-
let
|
|
34
|
+
let IMAGE_BUILD_NEED = false;
|
|
35
|
+
let IMAGE_BUILD_ARGS = [];
|
|
36
36
|
let CONTAINER_ENVS = [];
|
|
37
37
|
let CONTAINER_VOLUMES = [];
|
|
38
38
|
let MANYOYO_NAME = "manyoyo";
|
|
@@ -67,33 +67,34 @@ function showHelp() {
|
|
|
67
67
|
console.log(` ${MANYOYO_NAME} [--hp HOST_PATH] [-n CONTAINER_NAME] [--cp CONTAINER_PATH] [--ef ENV_FILE] [--sp COMMAND] [-s COMMAND] [-- COMMAND]`);
|
|
68
68
|
console.log("");
|
|
69
69
|
console.log(`${BLUE}Options:${NC}`);
|
|
70
|
-
console.log(" -l|--ls|--list
|
|
71
|
-
console.log(" --hp|--host-path PATH
|
|
72
|
-
console.log(" -n|--cn|--cont-name NAME
|
|
73
|
-
console.log(" --cp|--cont-path PATH
|
|
74
|
-
console.log(" --in|--image-name NAME
|
|
75
|
-
console.log(" --iv|--image-ver VERSION
|
|
76
|
-
console.log(" -e|--env
|
|
77
|
-
console.log(" --ef|--env-file ENV_FILE
|
|
78
|
-
console.log(" -v|--volume
|
|
79
|
-
console.log(" --rm|--remove-cont
|
|
80
|
-
console.log(" --sp|--shell-prefix COMMAND
|
|
81
|
-
console.log(" -s|--shell COMMAND
|
|
82
|
-
console.log(" --|--shell-suffix COMMAND
|
|
83
|
-
console.log(" -x|--shell-full COMMAND
|
|
84
|
-
console.log(" -y|--yolo CLI
|
|
85
|
-
console.log("
|
|
86
|
-
console.log(" -m|--cm|--cont-mode STRING
|
|
87
|
-
console.log("
|
|
88
|
-
console.log(" --ib|--image-build
|
|
89
|
-
console.log("
|
|
90
|
-
console.log(" --
|
|
91
|
-
console.log("
|
|
92
|
-
console.log("
|
|
93
|
-
console.log(" -
|
|
70
|
+
console.log(" -l|--ls|--list 列举容器");
|
|
71
|
+
console.log(" --hp|--host-path PATH 设置宿主机工作目录 (默认当前路径)");
|
|
72
|
+
console.log(" -n|--cn|--cont-name NAME 设置容器名称");
|
|
73
|
+
console.log(" --cp|--cont-path PATH 设置容器工作目录");
|
|
74
|
+
console.log(" --in|--image-name NAME 指定镜像名称");
|
|
75
|
+
console.log(" --iv|--image-ver VERSION 指定镜像版本");
|
|
76
|
+
console.log(" -e|--env XXX=YYY 设置环境变量");
|
|
77
|
+
console.log(" --ef|--env-file ENV_FILE 设置环境变量通过文件");
|
|
78
|
+
console.log(" -v|--volume XXX:YYY 绑定挂载卷");
|
|
79
|
+
console.log(" --rm|--remove-cont 删除-n容器");
|
|
80
|
+
console.log(" --sp|--shell-prefix COMMAND 临时环境变量 (作为-s前缀)");
|
|
81
|
+
console.log(" -s|--shell COMMAND 指定命令执行");
|
|
82
|
+
console.log(" --|--shell-suffix COMMAND 指定命令参数, --后面全部直传 (作为-s后缀)");
|
|
83
|
+
console.log(" -x|--shell-full COMMAND 指定完整命令执行, -x后面全部直传 (代替--sp和-s和--命令)");
|
|
84
|
+
console.log(" -y|--yolo CLI 使AGENT无需确认 (代替-s命令)");
|
|
85
|
+
console.log(" 例如 claude / c, gemini / gm, codex / cx, opencode / oc");
|
|
86
|
+
console.log(" -m|--cm|--cont-mode STRING 设置容器嵌套容器模式");
|
|
87
|
+
console.log(" 例如 common, dind, mdsock");
|
|
88
|
+
console.log(" --ib|--image-build 构建镜像");
|
|
89
|
+
console.log(" --iba|--image-build-arg XXX=YYY 构建镜像时传参给dockerfile");
|
|
90
|
+
console.log(" --ip|--image-prune 清理悬空镜像和 <none> 镜像");
|
|
91
|
+
console.log(" --install NAME 安装manyoyo命令");
|
|
92
|
+
console.log(" 例如 docker-cli-plugin");
|
|
93
|
+
console.log(" -V|--version 显示版本");
|
|
94
|
+
console.log(" -h|--help 显示帮助");
|
|
94
95
|
console.log("");
|
|
95
96
|
console.log(`${BLUE}Example:${NC}`);
|
|
96
|
-
console.log(` ${MANYOYO_NAME} --ib
|
|
97
|
+
console.log(` ${MANYOYO_NAME} --ib 构建镜像`);
|
|
97
98
|
console.log(` ${MANYOYO_NAME} -n test --ef ./xxx.env -y c 设置环境变量并运行无需确认的AGENT`);
|
|
98
99
|
console.log(` ${MANYOYO_NAME} -n test -- -c 恢复之前会话`);
|
|
99
100
|
console.log(` ${MANYOYO_NAME} -x echo 123 指定命令执行`);
|
|
@@ -300,13 +301,199 @@ function getContList() {
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
|
|
303
|
-
|
|
304
|
+
function pruneDanglingImages() {
|
|
305
|
+
console.log(`\n${YELLOW}清理悬空镜像...${NC}`);
|
|
306
|
+
execSync(`${DOCKER_CMD} image prune -f`, { stdio: 'inherit' });
|
|
307
|
+
|
|
308
|
+
// Remove remaining <none> images
|
|
309
|
+
try {
|
|
310
|
+
const imagesOutput = execSync(`${DOCKER_CMD} images -a --format "{{.ID}} {{.Repository}}"`, { encoding: 'utf-8' });
|
|
311
|
+
const noneImages = imagesOutput
|
|
312
|
+
.split('\n')
|
|
313
|
+
.filter(line => line.includes('<none>'))
|
|
314
|
+
.map(line => line.split(' ')[0])
|
|
315
|
+
.filter(id => id);
|
|
316
|
+
|
|
317
|
+
if (noneImages.length > 0) {
|
|
318
|
+
console.log(`${YELLOW}清理剩余的 <none> 镜像 (${noneImages.length} 个)...${NC}`);
|
|
319
|
+
execSync(`${DOCKER_CMD} rmi -f ${noneImages.join(' ')}`, { stdio: 'inherit' });
|
|
320
|
+
}
|
|
321
|
+
} catch (e) {
|
|
322
|
+
// Ignore errors if no <none> images found
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log(`${GREEN}✅ 清理完成${NC}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function prepareBuildCache(imageTool) {
|
|
329
|
+
const cacheDir = path.join(__dirname, '../docker/cache');
|
|
330
|
+
const timestampFile = path.join(cacheDir, '.timestamps.json');
|
|
331
|
+
const cacheTTLDays = 2;
|
|
332
|
+
|
|
333
|
+
console.log(`\n${CYAN}准备构建缓存...${NC}`);
|
|
334
|
+
|
|
335
|
+
// Create cache directory
|
|
336
|
+
if (!fs.existsSync(cacheDir)) {
|
|
337
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Load timestamps
|
|
341
|
+
let timestamps = {};
|
|
342
|
+
if (fs.existsSync(timestampFile)) {
|
|
343
|
+
try {
|
|
344
|
+
timestamps = JSON.parse(fs.readFileSync(timestampFile, 'utf-8'));
|
|
345
|
+
} catch (e) {
|
|
346
|
+
timestamps = {};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const now = new Date();
|
|
351
|
+
const isExpired = (key) => {
|
|
352
|
+
if (!timestamps[key]) return true;
|
|
353
|
+
const cachedTime = new Date(timestamps[key]);
|
|
354
|
+
const diffDays = (now - cachedTime) / (1000 * 60 * 60 * 24);
|
|
355
|
+
return diffDays > cacheTTLDays;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Determine architecture
|
|
359
|
+
const arch = process.arch === 'x64' ? 'amd64' : process.arch === 'arm64' ? 'arm64' : process.arch;
|
|
360
|
+
const archNode = arch === 'amd64' ? 'x64' : 'arm64';
|
|
361
|
+
|
|
362
|
+
// Prepare Node.js cache
|
|
363
|
+
const nodeCacheDir = path.join(cacheDir, 'node');
|
|
364
|
+
const nodeVersion = 24;
|
|
365
|
+
const nodeKey = 'node/'; // 使用目录级别的相对路径
|
|
366
|
+
|
|
367
|
+
if (!fs.existsSync(nodeCacheDir)) {
|
|
368
|
+
fs.mkdirSync(nodeCacheDir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const hasNodeCache = fs.existsSync(nodeCacheDir) && fs.readdirSync(nodeCacheDir).some(f => f.startsWith('node-') && f.includes(`linux-${archNode}`));
|
|
372
|
+
if (!hasNodeCache || isExpired(nodeKey)) {
|
|
373
|
+
console.log(`${YELLOW}下载 Node.js ${nodeVersion} (${archNode})...${NC}`);
|
|
374
|
+
const mirror = 'https://mirrors.tencent.com/nodejs-release';
|
|
375
|
+
try {
|
|
376
|
+
const shasum = execSync(`curl -sL ${mirror}/latest-v${nodeVersion}.x/SHASUMS256.txt | grep linux-${archNode}.tar.gz | awk '{print $2}'`, { encoding: 'utf-8' }).trim();
|
|
377
|
+
const nodeUrl = `${mirror}/latest-v${nodeVersion}.x/${shasum}`;
|
|
378
|
+
const nodeTargetPath = path.join(nodeCacheDir, shasum);
|
|
379
|
+
execSync(`curl -fsSL "${nodeUrl}" -o "${nodeTargetPath}"`, { stdio: 'inherit' });
|
|
380
|
+
timestamps[nodeKey] = now.toISOString();
|
|
381
|
+
fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 2));
|
|
382
|
+
console.log(`${GREEN}✓ Node.js 下载完成${NC}`);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.error(`${RED}错误: Node.js 下载失败${NC}`);
|
|
385
|
+
throw e;
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
console.log(`${GREEN}✓ Node.js 缓存已存在${NC}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Prepare JDT LSP cache (for java variant)
|
|
392
|
+
if (imageTool === 'full' || imageTool.includes('java')) {
|
|
393
|
+
const jdtlsCacheDir = path.join(cacheDir, 'jdtls');
|
|
394
|
+
const jdtlsKey = 'jdtls/jdt-language-server-latest.tar.gz'; // 使用相对路径
|
|
395
|
+
const jdtlsPath = path.join(cacheDir, jdtlsKey);
|
|
396
|
+
|
|
397
|
+
if (!fs.existsSync(jdtlsCacheDir)) {
|
|
398
|
+
fs.mkdirSync(jdtlsCacheDir, { recursive: true });
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!fs.existsSync(jdtlsPath) || isExpired(jdtlsKey)) {
|
|
402
|
+
console.log(`${YELLOW}下载 JDT Language Server...${NC}`);
|
|
403
|
+
const jdtUrl = 'https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz';
|
|
404
|
+
try {
|
|
405
|
+
execSync(`curl -fsSL "${jdtUrl}" -o "${jdtlsPath}"`, { stdio: 'inherit' });
|
|
406
|
+
timestamps[jdtlsKey] = now.toISOString();
|
|
407
|
+
fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 2));
|
|
408
|
+
console.log(`${GREEN}✓ JDT LSP 下载完成${NC}`);
|
|
409
|
+
} catch (e) {
|
|
410
|
+
console.error(`${RED}错误: JDT LSP 下载失败${NC}`);
|
|
411
|
+
throw e;
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
console.log(`${GREEN}✓ JDT LSP 缓存已存在${NC}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Prepare gopls cache (for go variant)
|
|
419
|
+
if (imageTool === 'full' || imageTool.includes('go')) {
|
|
420
|
+
const goplsCacheDir = path.join(cacheDir, 'gopls');
|
|
421
|
+
const goplsKey = `gopls/gopls-linux-${arch}`; // 使用相对路径
|
|
422
|
+
const goplsPath = path.join(cacheDir, goplsKey);
|
|
423
|
+
|
|
424
|
+
if (!fs.existsSync(goplsCacheDir)) {
|
|
425
|
+
fs.mkdirSync(goplsCacheDir, { recursive: true });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!fs.existsSync(goplsPath) || isExpired(goplsKey)) {
|
|
429
|
+
console.log(`${YELLOW}下载 gopls (${arch})...${NC}`);
|
|
430
|
+
try {
|
|
431
|
+
// Download using go install in temporary environment
|
|
432
|
+
const tmpGoPath = path.join(cacheDir, '.tmp-go');
|
|
433
|
+
|
|
434
|
+
// Clean up existing temp directory (with go clean for mod cache)
|
|
435
|
+
if (fs.existsSync(tmpGoPath)) {
|
|
436
|
+
try {
|
|
437
|
+
execSync(`GOPATH="${tmpGoPath}" go clean -modcache 2>/dev/null || true`, { stdio: 'inherit' });
|
|
438
|
+
execSync(`chmod -R u+w "${tmpGoPath}" 2>/dev/null || true`, { stdio: 'inherit' });
|
|
439
|
+
execSync(`rm -rf "${tmpGoPath}"`, { stdio: 'inherit' });
|
|
440
|
+
} catch (e) {
|
|
441
|
+
// Ignore cleanup errors
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
fs.mkdirSync(tmpGoPath, { recursive: true });
|
|
445
|
+
|
|
446
|
+
execSync(`GOPATH="${tmpGoPath}" GOOS=linux GOARCH=${arch} go install golang.org/x/tools/gopls@latest`, { stdio: 'inherit' });
|
|
447
|
+
execSync(`cp "${tmpGoPath}/bin/linux_${arch}/gopls" "${goplsPath}" || cp "${tmpGoPath}/bin/gopls" "${goplsPath}"`, { stdio: 'inherit' });
|
|
448
|
+
execSync(`chmod +x "${goplsPath}"`, { stdio: 'inherit' });
|
|
449
|
+
|
|
450
|
+
// Save timestamp immediately after successful download
|
|
451
|
+
timestamps[goplsKey] = now.toISOString();
|
|
452
|
+
fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 2));
|
|
453
|
+
console.log(`${GREEN}✓ gopls 下载完成${NC}`);
|
|
454
|
+
|
|
455
|
+
// Clean up temp directory (with go clean for mod cache)
|
|
456
|
+
try {
|
|
457
|
+
execSync(`GOPATH="${tmpGoPath}" go clean -modcache 2>/dev/null || true`, { stdio: 'inherit' });
|
|
458
|
+
execSync(`chmod -R u+w "${tmpGoPath}" 2>/dev/null || true`, { stdio: 'inherit' });
|
|
459
|
+
execSync(`rm -rf "${tmpGoPath}"`, { stdio: 'inherit' });
|
|
460
|
+
} catch (e) {
|
|
461
|
+
console.log(`${YELLOW}提示: 临时目录清理失败,可手动删除 ${tmpGoPath}${NC}`);
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {
|
|
464
|
+
console.error(`${RED}错误: gopls 下载失败${NC}`);
|
|
465
|
+
throw e;
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
console.log(`${GREEN}✓ gopls 缓存已存在${NC}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Save timestamps
|
|
473
|
+
fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 2));
|
|
474
|
+
console.log(`${GREEN}✅ 构建缓存准备完成${NC}\n`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function addImageBuildArg(string) {
|
|
478
|
+
IMAGE_BUILD_ARGS.push("--build-arg", string);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function buildImage(IMAGE_BUILD_ARGS, imageName, imageVersion) {
|
|
482
|
+
let imageTool = "full";
|
|
483
|
+
if (IMAGE_BUILD_ARGS.length === 0) {
|
|
484
|
+
IMAGE_BUILD_ARGS = ["--build-arg", `TOOL=${imageTool}`];
|
|
485
|
+
} else {
|
|
486
|
+
imageTool = IMAGE_BUILD_ARGS.filter(v => v.startsWith("TOOL=")).at(-1)?.slice("TOOL=".length) ?? imageTool;
|
|
487
|
+
}
|
|
304
488
|
// Use package.json imageVersion if not specified
|
|
305
489
|
const version = imageVersion || IMAGE_VERSION_BASE;
|
|
306
|
-
const fullImageTag = `${imageName}:${version}-${
|
|
490
|
+
const fullImageTag = `${imageName}:${version}-${imageTool}`;
|
|
307
491
|
|
|
308
492
|
console.log(`${CYAN}🔨 正在构建镜像: ${YELLOW}${fullImageTag}${NC}`);
|
|
309
|
-
console.log(`${BLUE}
|
|
493
|
+
console.log(`${BLUE}构建组件类型: ${imageTool}${NC}\n`);
|
|
494
|
+
|
|
495
|
+
// Prepare cache (自动检测并下载缺失的文件)
|
|
496
|
+
await prepareBuildCache(imageTool);
|
|
310
497
|
|
|
311
498
|
// Find Dockerfile path
|
|
312
499
|
const dockerfilePath = path.join(__dirname, '../docker/manyoyo.Dockerfile');
|
|
@@ -316,7 +503,8 @@ async function buildImage(ext, imageName, imageVersion) {
|
|
|
316
503
|
}
|
|
317
504
|
|
|
318
505
|
// Build command
|
|
319
|
-
const
|
|
506
|
+
const imageBuildArgs = IMAGE_BUILD_ARGS.join(' ');
|
|
507
|
+
const buildCmd = `${DOCKER_CMD} build -t "${fullImageTag}" -f "${dockerfilePath}" "${path.join(__dirname, '..')}" ${imageBuildArgs} --load --progress=plain --no-cache`;
|
|
320
508
|
|
|
321
509
|
console.log(`${BLUE}准备执行命令:${NC}`);
|
|
322
510
|
console.log(`${buildCmd}\n`);
|
|
@@ -328,12 +516,10 @@ async function buildImage(ext, imageName, imageVersion) {
|
|
|
328
516
|
execSync(buildCmd, { stdio: 'inherit' });
|
|
329
517
|
console.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
|
|
330
518
|
console.log(`${BLUE}使用镜像:${NC}`);
|
|
331
|
-
console.log(` manyoyo -n test --in ${imageName} --iv ${version}-${
|
|
519
|
+
console.log(` manyoyo -n test --in ${imageName} --iv ${version}-${imageTool} -y c`);
|
|
332
520
|
|
|
333
521
|
// Prune dangling images
|
|
334
|
-
|
|
335
|
-
execSync(`${DOCKER_CMD} image prune -f`, { stdio: 'inherit' });
|
|
336
|
-
console.log(`${GREEN}✅ 清理完成${NC}`);
|
|
522
|
+
pruneDanglingImages();
|
|
337
523
|
} catch (e) {
|
|
338
524
|
console.error(`${RED}错误: 镜像构建失败${NC}`);
|
|
339
525
|
process.exit(1);
|
|
@@ -486,11 +672,21 @@ function parseArguments(argv) {
|
|
|
486
672
|
|
|
487
673
|
case '--ib':
|
|
488
674
|
case '--image-build':
|
|
489
|
-
|
|
490
|
-
|
|
675
|
+
IMAGE_BUILD_NEED = true;
|
|
676
|
+
i += 1;
|
|
677
|
+
break;
|
|
678
|
+
|
|
679
|
+
case '--iba':
|
|
680
|
+
case '--image-build-arg':
|
|
681
|
+
addImageBuildArg(args[i + 1]);
|
|
491
682
|
i += 2;
|
|
492
683
|
break;
|
|
493
684
|
|
|
685
|
+
case '--ip':
|
|
686
|
+
case '--image-prune':
|
|
687
|
+
pruneDanglingImages();
|
|
688
|
+
process.exit(0);
|
|
689
|
+
|
|
494
690
|
case '--install':
|
|
495
691
|
installManyoyo(args[i + 1]);
|
|
496
692
|
process.exit(0);
|
|
@@ -686,8 +882,8 @@ async function main() {
|
|
|
686
882
|
parseArguments(process.argv);
|
|
687
883
|
|
|
688
884
|
// 3. Handle image build operation
|
|
689
|
-
if (
|
|
690
|
-
await buildImage(
|
|
885
|
+
if (IMAGE_BUILD_NEED) {
|
|
886
|
+
await buildImage(IMAGE_BUILD_ARGS, IMAGE_NAME, IMAGE_VERSION.split('-')[0]);
|
|
691
887
|
process.exit(0);
|
|
692
888
|
}
|
|
693
889
|
|
|
@@ -1,8 +1,65 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Stage 1: 缓存准备阶段 - 智能检测缓存或下载
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
FROM ubuntu:24.04 AS cache-stage
|
|
5
|
+
|
|
6
|
+
ARG TARGETARCH
|
|
7
|
+
|
|
8
|
+
# 复制缓存目录(可能为空)
|
|
9
|
+
COPY ./docker/cache/ /cache/
|
|
10
|
+
|
|
11
|
+
RUN <<EOX
|
|
12
|
+
# 确定架构
|
|
13
|
+
case "$TARGETARCH" in
|
|
14
|
+
amd64) ARCH_NODE="x64"; ARCH_GO="amd64" ;;
|
|
15
|
+
arm64) ARCH_NODE="arm64"; ARCH_GO="arm64" ;;
|
|
16
|
+
*) ARCH_NODE="$TARGETARCH"; ARCH_GO="$TARGETARCH" ;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
# Node.js: 检测缓存,不存在则下载
|
|
20
|
+
mkdir -p /opt/node
|
|
21
|
+
if ls /cache/node/node-*-linux-${ARCH_NODE}.tar.gz 1> /dev/null 2>&1; then
|
|
22
|
+
echo "使用 Node.js 缓存"
|
|
23
|
+
NODE_TAR=$(ls /cache/node/node-*-linux-${ARCH_NODE}.tar.gz | head -1)
|
|
24
|
+
tar -xzf ${NODE_TAR} -C /opt/node --strip-components=1 --exclude='*.md' --exclude='LICENSE' --no-same-owner
|
|
25
|
+
else
|
|
26
|
+
echo "下载 Node.js"
|
|
27
|
+
NVM_NODEJS_ORG_MIRROR=https://mirrors.tencent.com/nodejs-release/
|
|
28
|
+
NODE_TAR=$(curl -sL ${NVM_NODEJS_ORG_MIRROR}/latest-v24.x/SHASUMS256.txt | grep linux-${ARCH_NODE}.tar.gz | awk '{print $2}')
|
|
29
|
+
curl -fsSL ${NVM_NODEJS_ORG_MIRROR}/latest-v24.x/${NODE_TAR} | tar -xz -C /opt/node --strip-components=1 --exclude='*.md' --exclude='LICENSE'
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# JDT LSP: 检测缓存,不存在则下载
|
|
33
|
+
mkdir -p /opt/jdtls
|
|
34
|
+
if [ -f /cache/jdtls/jdt-language-server-latest.tar.gz ]; then
|
|
35
|
+
echo "使用 JDT LSP 缓存"
|
|
36
|
+
tar -xzf /cache/jdtls/jdt-language-server-latest.tar.gz -C /opt/jdtls --no-same-owner
|
|
37
|
+
else
|
|
38
|
+
echo "下载 JDT LSP"
|
|
39
|
+
curl -fsSL https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz | tar -xz -C /opt/jdtls
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# gopls: 检测缓存,不存在则下载
|
|
43
|
+
mkdir -p /opt/gopls
|
|
44
|
+
if [ -f /cache/gopls/gopls-linux-${ARCH_GO} ]; then
|
|
45
|
+
echo "使用 gopls 缓存"
|
|
46
|
+
cp /cache/gopls/gopls-linux-${ARCH_GO} /opt/gopls/gopls
|
|
47
|
+
chmod +x /opt/gopls/gopls
|
|
48
|
+
else
|
|
49
|
+
echo "下载 gopls (需要 go 环境)"
|
|
50
|
+
# gopls 需要编译,这里跳过,在最终阶段处理
|
|
51
|
+
touch /opt/gopls/.no-cache
|
|
52
|
+
fi
|
|
53
|
+
EOX
|
|
54
|
+
|
|
55
|
+
# ==============================================================================
|
|
56
|
+
# Stage 2: 最终镜像
|
|
57
|
+
# ==============================================================================
|
|
1
58
|
FROM ubuntu:24.04
|
|
2
59
|
|
|
3
60
|
ARG TARGETARCH
|
|
4
61
|
ARG NODE_VERSION=24
|
|
5
|
-
ARG
|
|
62
|
+
ARG TOOL="full"
|
|
6
63
|
|
|
7
64
|
RUN <<EOX
|
|
8
65
|
# 部署 system
|
|
@@ -23,7 +80,7 @@ RUN <<EOX
|
|
|
23
80
|
supervisor
|
|
24
81
|
|
|
25
82
|
# 安装 docker
|
|
26
|
-
case ",$
|
|
83
|
+
case ",$TOOL," in *,full,*|*,docker,*)
|
|
27
84
|
apt-get install -y --no-install-recommends docker.io
|
|
28
85
|
;; esac
|
|
29
86
|
|
|
@@ -45,16 +102,12 @@ RUN <<EOX
|
|
|
45
102
|
rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/* ~/.cache ~/.npm ~/go/pkg/mod/cache
|
|
46
103
|
EOX
|
|
47
104
|
|
|
105
|
+
# 从 cache-stage 复制 Node.js(缓存或下载)
|
|
106
|
+
COPY --from=cache-stage /opt/node /usr/local
|
|
107
|
+
ARG GIT_SSL_NO_VERIFY=false
|
|
108
|
+
|
|
48
109
|
RUN <<EOX
|
|
49
|
-
#
|
|
50
|
-
case "$TARGETARCH" in
|
|
51
|
-
amd64) ARCH_NODE="x64" ;;
|
|
52
|
-
arm64) ARCH_NODE="arm64" ;;
|
|
53
|
-
*) ARCH_NODE="$TARGETARCH" ;;
|
|
54
|
-
esac
|
|
55
|
-
NVM_NODEJS_ORG_MIRROR=https://mirrors.tencent.com/nodejs-release/
|
|
56
|
-
NODE_TAR=$(curl -sL ${NVM_NODEJS_ORG_MIRROR}/latest-v${NODE_VERSION}.x/SHASUMS256.txt | grep linux-${ARCH_NODE}.tar.gz | awk '{print $2}')
|
|
57
|
-
curl -fsSL ${NVM_NODEJS_ORG_MIRROR}/latest-v${NODE_VERSION}.x/${NODE_TAR} | tar -xz -C /usr/local --strip-components=1 --exclude='*.md' --exclude='LICENSE'
|
|
110
|
+
# 配置 node.js
|
|
58
111
|
npm config set registry=https://mirrors.tencent.com/npm/
|
|
59
112
|
npm install -g npm
|
|
60
113
|
|
|
@@ -75,7 +128,7 @@ RUN <<EOX
|
|
|
75
128
|
}
|
|
76
129
|
}
|
|
77
130
|
EOF
|
|
78
|
-
claude plugin marketplace add anthropics/claude-plugins-official
|
|
131
|
+
GIT_SSL_NO_VERIFY=$GIT_SSL_NO_VERIFY claude plugin marketplace add anthropics/claude-plugins-official
|
|
79
132
|
claude plugin install ralph-loop@claude-plugins-official
|
|
80
133
|
claude plugin install typescript-lsp@claude-plugins-official
|
|
81
134
|
claude plugin install pyright-lsp@claude-plugins-official
|
|
@@ -83,7 +136,7 @@ EOF
|
|
|
83
136
|
claude plugin install jdtls-lsp@claude-plugins-official
|
|
84
137
|
|
|
85
138
|
# 安装 Gemini CLI
|
|
86
|
-
case ",$
|
|
139
|
+
case ",$TOOL," in *,full,*|*,gemini,*)
|
|
87
140
|
npm install -g @google/gemini-cli
|
|
88
141
|
mkdir -p ~/.gemini/
|
|
89
142
|
cat > ~/.gemini/settings.json <<EOF
|
|
@@ -109,12 +162,12 @@ EOF
|
|
|
109
162
|
;; esac
|
|
110
163
|
|
|
111
164
|
# 安装 Codex CLI
|
|
112
|
-
case ",$
|
|
165
|
+
case ",$TOOL," in *,full,*|*,codex,*)
|
|
113
166
|
npm install -g @openai/codex
|
|
114
167
|
;; esac
|
|
115
168
|
|
|
116
169
|
# 安装 Copilot CLI
|
|
117
|
-
case ",$
|
|
170
|
+
case ",$TOOL," in *,full,*|*,copilot,*)
|
|
118
171
|
npm install -g @github/copilot
|
|
119
172
|
mkdir -p ~/.copilot/
|
|
120
173
|
cat > ~/.copilot/config.json <<EOF
|
|
@@ -129,7 +182,7 @@ EOF
|
|
|
129
182
|
;; esac
|
|
130
183
|
|
|
131
184
|
# 安装 OpenCode CLI
|
|
132
|
-
case ",$
|
|
185
|
+
case ",$TOOL," in *,full,*|*,opencode,*)
|
|
133
186
|
npm install -g opencode-ai
|
|
134
187
|
mkdir -p ~/.config/opencode/
|
|
135
188
|
cat > ~/.config/opencode/opencode.json <<EOF
|
|
@@ -164,18 +217,16 @@ EOF
|
|
|
164
217
|
rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/* ~/.cache ~/.npm ~/go/pkg/mod/cache
|
|
165
218
|
EOX
|
|
166
219
|
|
|
167
|
-
|
|
220
|
+
# 从 cache-stage 复制 JDT LSP(缓存或下载)
|
|
221
|
+
COPY --from=cache-stage /opt/jdtls /root/.local/share/jdtls
|
|
222
|
+
|
|
168
223
|
RUN <<EOX
|
|
169
224
|
# 安装 java
|
|
170
|
-
case ",$
|
|
225
|
+
case ",$TOOL," in *,full,*|*,java,*)
|
|
171
226
|
apt-get update -y
|
|
172
227
|
apt-get install -y --no-install-recommends openjdk-21-jdk maven
|
|
173
228
|
|
|
174
|
-
#
|
|
175
|
-
mkdir -p ~/.local/share/jdtls
|
|
176
|
-
# wget -O /tmp/jdt-language-server-latest.tar.gz https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz
|
|
177
|
-
tar -xzf /tmp/jdt-language-server-latest.tar.gz -C ~/.local/share/jdtls
|
|
178
|
-
rm -f /tmp/jdt-language-server-latest.tar.gz
|
|
229
|
+
# 配置 LSP服务(java)
|
|
179
230
|
ln -sf ~/.local/share/jdtls/bin/jdtls /usr/local/bin/jdtls
|
|
180
231
|
|
|
181
232
|
# 清理
|
|
@@ -184,16 +235,27 @@ RUN <<EOX
|
|
|
184
235
|
;; esac
|
|
185
236
|
EOX
|
|
186
237
|
|
|
238
|
+
# 从 cache-stage 复制 gopls(缓存或下载)
|
|
239
|
+
COPY --from=cache-stage /opt/gopls /tmp/gopls-cache
|
|
240
|
+
|
|
187
241
|
RUN <<EOX
|
|
188
242
|
# 安装 go
|
|
189
|
-
case ",$
|
|
243
|
+
case ",$TOOL," in *,full,*|*,go,*)
|
|
190
244
|
apt-get update -y
|
|
191
245
|
apt-get install -y --no-install-recommends golang golang-src gcc
|
|
192
246
|
go env -w GOPROXY=https://mirrors.tencent.com/go
|
|
193
247
|
|
|
194
248
|
# 安装 LSP服务(go)
|
|
195
|
-
|
|
196
|
-
|
|
249
|
+
if [ -f /tmp/gopls-cache/gopls ] && [ ! -f /tmp/gopls-cache/.no-cache ]; then
|
|
250
|
+
# 使用缓存
|
|
251
|
+
cp /tmp/gopls-cache/gopls /usr/local/bin/gopls
|
|
252
|
+
chmod +x /usr/local/bin/gopls
|
|
253
|
+
else
|
|
254
|
+
# 下载编译
|
|
255
|
+
go install golang.org/x/tools/gopls@latest
|
|
256
|
+
ln -sf ~/go/bin/gopls /usr/local/bin/gopls
|
|
257
|
+
fi
|
|
258
|
+
rm -rf /tmp/gopls-cache
|
|
197
259
|
|
|
198
260
|
# 清理
|
|
199
261
|
apt-get clean
|
package/docs/README_EN.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<< [English](README_EN.md) >> | [中文](../README.md)
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
@@ -49,17 +49,21 @@ After installing manyoyo, use the built-in command to build images:
|
|
|
49
49
|
# Pull base image
|
|
50
50
|
podman pull ubuntu:24.04
|
|
51
51
|
|
|
52
|
-
# Build using manyoyo (Recommended)
|
|
53
|
-
manyoyo --ib
|
|
54
|
-
manyoyo --ib common
|
|
55
|
-
manyoyo --ib go,codex,java,gemini
|
|
52
|
+
# Build using manyoyo (Recommended, auto-cache enabled)
|
|
53
|
+
manyoyo --ib # Build full version by default (Recommended)
|
|
54
|
+
manyoyo --ib --iba TOOL=common # Build common version
|
|
55
|
+
manyoyo --ib --iba TOOL=go,codex,java,gemini # Build custom combination
|
|
56
|
+
manyoyo --ib --iba --iba GIT_SSL_NO_VERIFY=true # Build the full version and skip Git SSL verification
|
|
57
|
+
manyoyo --ib all --in myimage --iv 2.0.0 # Customize the image name and version to produce myimage:2.0.0-all
|
|
58
|
+
manyoyo --ip # Clean dangling images and <none> images
|
|
56
59
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
+
# How it works:
|
|
61
|
+
# - First build: Auto-downloads Node.js, JDT LSP, gopls etc. to docker/cache/
|
|
62
|
+
# - Rebuild within 2 days: Uses local cache, ~5x faster
|
|
63
|
+
# - After cache expires: Auto-downloads latest versions
|
|
60
64
|
|
|
61
65
|
# Or build manually (Not recommended)
|
|
62
|
-
iv=1.4.0 && podman build -t localhost/xcanwin/manyoyo:$iv-all -f docker/manyoyo.Dockerfile . --build-arg
|
|
66
|
+
iv=1.4.0 && podman build -t localhost/xcanwin/manyoyo:$iv-all -f docker/manyoyo.Dockerfile . --build-arg TOOL=all --no-cache
|
|
63
67
|
podman image prune -f
|
|
64
68
|
```
|
|
65
69
|
|
|
@@ -207,7 +211,9 @@ docker ps -a
|
|
|
207
211
|
| `-x CMD` | `--sf`, `--shell-full` | Full command (replaces --sp, -s, and --) |
|
|
208
212
|
| `-y CLI` | `--yolo` | Run AI agent without confirmation |
|
|
209
213
|
| `-m MODE` | `--cm`, `--cont-mode` | Set container mode (common, dind, mdsock) |
|
|
210
|
-
| `--ib
|
|
214
|
+
| `--ib` | `--image-build` | Build image |
|
|
215
|
+
| `--iba` | `--image-build-arg` | Pass arguments to a Dockerfile during image build |
|
|
216
|
+
| `--ip` | `--image-prune` | Clean dangling images and `<none>` images |
|
|
211
217
|
| `--install NAME` | | Install manyoyo command |
|
|
212
218
|
| `-V` | `--version` | Show version |
|
|
213
219
|
| `-h` | `--help` | Show help |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"imageVersion": "1.
|
|
3
|
+
"version": "3.4.5",
|
|
4
|
+
"imageVersion": "1.6.2",
|
|
5
5
|
"description": "AI Agent CLI Security Sandbox",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai", "agent", "sandbox", "docker", "cli", "container", "development"
|