@xcanwin/manyoyo 3.8.7 → 3.9.3
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 +2 -0
- package/bin/manyoyo.js +129 -8
- package/config.example.json +2 -0
- package/docker/manyoyo.Dockerfile +3 -0
- package/lib/web/server.js +677 -0
- package/lib/web/static/app.css +315 -0
- package/lib/web/static/app.html +40 -0
- package/lib/web/static/app.js +276 -0
- package/lib/web/static/login.css +81 -0
- package/lib/web/static/login.html +22 -0
- package/lib/web/static/login.js +28 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -120,6 +120,8 @@ manyoyo -y oc # OpenCode(或 opencode)
|
|
|
120
120
|
manyoyo -l
|
|
121
121
|
manyoyo -n myy-dev -x /bin/bash
|
|
122
122
|
manyoyo -n myy-dev --crm
|
|
123
|
+
manyoyo --server 3000
|
|
124
|
+
manyoyo --server 3000 --server-user admin --server-pass 123456
|
|
123
125
|
|
|
124
126
|
# 调试配置与命令拼装
|
|
125
127
|
manyoyo --show-config
|
package/bin/manyoyo.js
CHANGED
|
@@ -12,6 +12,7 @@ const crypto = require('crypto');
|
|
|
12
12
|
const readline = require('readline');
|
|
13
13
|
const { Command } = require('commander');
|
|
14
14
|
const JSON5 = require('json5');
|
|
15
|
+
const { startWebServer } = require('../lib/web/server');
|
|
15
16
|
const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_BASE } = require('../package.json');
|
|
16
17
|
|
|
17
18
|
// Helper function to format date like bash $(date +%m%d-%H%M)
|
|
@@ -57,6 +58,12 @@ let QUIET = {};
|
|
|
57
58
|
let SHOW_COMMAND = false;
|
|
58
59
|
let YES_MODE = false;
|
|
59
60
|
let RM_ON_EXIT = false;
|
|
61
|
+
let SERVER_MODE = false;
|
|
62
|
+
let SERVER_PORT = 3000;
|
|
63
|
+
let SERVER_AUTH_USER = "";
|
|
64
|
+
let SERVER_AUTH_PASS = "";
|
|
65
|
+
let SERVER_AUTH_PASS_AUTO = false;
|
|
66
|
+
const SAFE_CONTAINER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
60
67
|
|
|
61
68
|
// Color definitions using ANSI codes
|
|
62
69
|
const RED = '\x1b[0;31m';
|
|
@@ -83,6 +90,37 @@ function normalizeCommandSuffix(suffix) {
|
|
|
83
90
|
return trimmed ? ` ${trimmed}` : "";
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
function parseServerPort(rawPort) {
|
|
94
|
+
if (rawPort === true || rawPort === undefined || rawPort === null || rawPort === '') {
|
|
95
|
+
return 3000;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const value = String(rawPort).trim();
|
|
99
|
+
if (!/^\d+$/.test(value)) {
|
|
100
|
+
console.error(`${RED}⚠️ 错误: --server 端口必须是 1-65535 的整数: ${rawPort}${NC}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const port = Number(value);
|
|
105
|
+
if (port < 1 || port > 65535) {
|
|
106
|
+
console.error(`${RED}⚠️ 错误: --server 端口超出范围 (1-65535): ${rawPort}${NC}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return port;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ensureWebServerAuthCredentials() {
|
|
114
|
+
if (!SERVER_AUTH_USER) {
|
|
115
|
+
SERVER_AUTH_USER = 'admin';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!SERVER_AUTH_PASS) {
|
|
119
|
+
SERVER_AUTH_PASS = crypto.randomBytes(12).toString('hex');
|
|
120
|
+
SERVER_AUTH_PASS_AUTO = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
86
124
|
/**
|
|
87
125
|
* 计算文件的 SHA256 哈希值(跨平台)
|
|
88
126
|
* @param {string} filePath - 文件路径
|
|
@@ -101,7 +139,7 @@ function getFileSha256(filePath) {
|
|
|
101
139
|
* @returns {Object} 脱敏后的配置对象
|
|
102
140
|
*/
|
|
103
141
|
function sanitizeSensitiveData(obj) {
|
|
104
|
-
const sensitiveKeys = ['KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'AUTH', 'CREDENTIAL'];
|
|
142
|
+
const sensitiveKeys = ['KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'PASS', 'AUTH', 'CREDENTIAL'];
|
|
105
143
|
|
|
106
144
|
function sanitizeValue(key, value) {
|
|
107
145
|
if (typeof value !== 'string') return value;
|
|
@@ -264,6 +302,10 @@ function validateName(label, value, pattern) {
|
|
|
264
302
|
}
|
|
265
303
|
}
|
|
266
304
|
|
|
305
|
+
function isValidContainerName(value) {
|
|
306
|
+
return typeof value === 'string' && SAFE_CONTAINER_NAME_PATTERN.test(value);
|
|
307
|
+
}
|
|
308
|
+
|
|
267
309
|
// ==============================================================================
|
|
268
310
|
// SECTION: Environment Variables and Volume Handling
|
|
269
311
|
// ==============================================================================
|
|
@@ -858,6 +900,8 @@ function setupCommander() {
|
|
|
858
900
|
${MANYOYO_NAME} -n test --ef ./myenv.env -y c 使用当前目录 ./myenv.env 环境变量文件
|
|
859
901
|
${MANYOYO_NAME} -n test -- -c 恢复之前会话
|
|
860
902
|
${MANYOYO_NAME} -x echo 123 指定命令执行
|
|
903
|
+
${MANYOYO_NAME} --server --server-user admin --server-pass 123456 启动带登录认证的网页服务
|
|
904
|
+
${MANYOYO_NAME} --server 3000 启动网页交互服务
|
|
861
905
|
${MANYOYO_NAME} -n test -q tip -q cmd 多次使用静默选项
|
|
862
906
|
`);
|
|
863
907
|
|
|
@@ -886,6 +930,9 @@ function setupCommander() {
|
|
|
886
930
|
.option('--install <name>', '安装manyoyo命令 (docker-cli-plugin)')
|
|
887
931
|
.option('--show-config', '显示最终生效配置并退出')
|
|
888
932
|
.option('--show-command', '显示将执行的 docker run 命令并退出')
|
|
933
|
+
.option('--server [port]', '启动网页交互服务 (默认端口: 3000)')
|
|
934
|
+
.option('--server-user <username>', '网页服务登录用户名 (默认 admin)')
|
|
935
|
+
.option('--server-pass <password>', '网页服务登录密码 (默认自动生成随机密码)')
|
|
889
936
|
.option('--yes', '所有提示自动确认 (用于CI/脚本)')
|
|
890
937
|
.option('--rm-on-exit', '退出后自动删除容器 (一次性模式)')
|
|
891
938
|
.option('-q, --quiet <item>', '静默显示 (可多次使用: cnew,crm,tip,cmd,full)', (value, previous) => [...(previous || []), value], []);
|
|
@@ -955,7 +1002,7 @@ function setupCommander() {
|
|
|
955
1002
|
}
|
|
956
1003
|
|
|
957
1004
|
// Basic name validation to reduce injection risk
|
|
958
|
-
validateName('containerName', CONTAINER_NAME,
|
|
1005
|
+
validateName('containerName', CONTAINER_NAME, SAFE_CONTAINER_NAME_PATTERN);
|
|
959
1006
|
validateName('imageName', IMAGE_NAME, /^[A-Za-z0-9][A-Za-z0-9._/:-]*$/);
|
|
960
1007
|
validateName('imageVersion', IMAGE_VERSION, /^[A-Za-z0-9][A-Za-z0-9_.-]*$/);
|
|
961
1008
|
|
|
@@ -1010,6 +1057,26 @@ function setupCommander() {
|
|
|
1010
1057
|
RM_ON_EXIT = true;
|
|
1011
1058
|
}
|
|
1012
1059
|
|
|
1060
|
+
if (options.server !== undefined) {
|
|
1061
|
+
SERVER_MODE = true;
|
|
1062
|
+
SERVER_PORT = parseServerPort(options.server);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const serverUserValue = options.serverUser || runConfig.serverUser || config.serverUser || process.env.MANYOYO_SERVER_USER;
|
|
1066
|
+
if (serverUserValue) {
|
|
1067
|
+
SERVER_AUTH_USER = String(serverUserValue);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const serverPassValue = options.serverPass || runConfig.serverPass || config.serverPass || process.env.MANYOYO_SERVER_PASS;
|
|
1071
|
+
if (serverPassValue) {
|
|
1072
|
+
SERVER_AUTH_PASS = String(serverPassValue);
|
|
1073
|
+
SERVER_AUTH_PASS_AUTO = false;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (SERVER_MODE) {
|
|
1077
|
+
ensureWebServerAuthCredentials();
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1013
1080
|
if (options.showConfig) {
|
|
1014
1081
|
const finalConfig = {
|
|
1015
1082
|
hostPath: HOST_PATH,
|
|
@@ -1027,6 +1094,10 @@ function setupCommander() {
|
|
|
1027
1094
|
shellSuffix: EXEC_COMMAND_SUFFIX || "",
|
|
1028
1095
|
yolo: yoloValue || "",
|
|
1029
1096
|
quiet: quietValue || [],
|
|
1097
|
+
server: SERVER_MODE,
|
|
1098
|
+
serverPort: SERVER_MODE ? SERVER_PORT : null,
|
|
1099
|
+
serverUser: SERVER_AUTH_USER || "",
|
|
1100
|
+
serverPass: SERVER_AUTH_PASS || "",
|
|
1030
1101
|
exec: {
|
|
1031
1102
|
prefix: EXEC_COMMAND_PREFIX,
|
|
1032
1103
|
shell: EXEC_COMMAND,
|
|
@@ -1292,6 +1363,50 @@ async function handlePostExit(defaultCommand) {
|
|
|
1292
1363
|
}
|
|
1293
1364
|
}
|
|
1294
1365
|
|
|
1366
|
+
// ==============================================================================
|
|
1367
|
+
// SECTION: Web Server
|
|
1368
|
+
// ==============================================================================
|
|
1369
|
+
|
|
1370
|
+
async function runWebServerMode() {
|
|
1371
|
+
ensureWebServerAuthCredentials();
|
|
1372
|
+
|
|
1373
|
+
await startWebServer({
|
|
1374
|
+
serverPort: SERVER_PORT,
|
|
1375
|
+
authUser: SERVER_AUTH_USER,
|
|
1376
|
+
authPass: SERVER_AUTH_PASS,
|
|
1377
|
+
authPassAuto: SERVER_AUTH_PASS_AUTO,
|
|
1378
|
+
dockerCmd: DOCKER_CMD,
|
|
1379
|
+
hostPath: HOST_PATH,
|
|
1380
|
+
containerPath: CONTAINER_PATH,
|
|
1381
|
+
imageName: IMAGE_NAME,
|
|
1382
|
+
imageVersion: IMAGE_VERSION,
|
|
1383
|
+
execCommandPrefix: EXEC_COMMAND_PREFIX,
|
|
1384
|
+
execCommand: EXEC_COMMAND,
|
|
1385
|
+
execCommandSuffix: EXEC_COMMAND_SUFFIX,
|
|
1386
|
+
contModeArgs: CONT_MODE_ARGS,
|
|
1387
|
+
containerEnvs: CONTAINER_ENVS,
|
|
1388
|
+
containerVolumes: CONTAINER_VOLUMES,
|
|
1389
|
+
validateHostPath,
|
|
1390
|
+
formatDate,
|
|
1391
|
+
isValidContainerName,
|
|
1392
|
+
containerExists,
|
|
1393
|
+
getContainerStatus,
|
|
1394
|
+
waitForContainerReady,
|
|
1395
|
+
dockerExecArgs,
|
|
1396
|
+
showImagePullHint,
|
|
1397
|
+
removeContainer,
|
|
1398
|
+
webHistoryDir: path.join(os.homedir(), '.manyoyo', 'web-history'),
|
|
1399
|
+
colors: {
|
|
1400
|
+
RED,
|
|
1401
|
+
GREEN,
|
|
1402
|
+
YELLOW,
|
|
1403
|
+
BLUE,
|
|
1404
|
+
CYAN,
|
|
1405
|
+
NC
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1295
1410
|
// ==============================================================================
|
|
1296
1411
|
// Main Function
|
|
1297
1412
|
// ==============================================================================
|
|
@@ -1301,25 +1416,31 @@ async function main() {
|
|
|
1301
1416
|
// 1. Setup commander and parse arguments
|
|
1302
1417
|
setupCommander();
|
|
1303
1418
|
|
|
1304
|
-
// 2.
|
|
1419
|
+
// 2. Start web server mode
|
|
1420
|
+
if (SERVER_MODE) {
|
|
1421
|
+
await runWebServerMode();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// 3. Handle image build operation
|
|
1305
1426
|
if (IMAGE_BUILD_NEED) {
|
|
1306
1427
|
await buildImage(IMAGE_BUILD_ARGS, IMAGE_NAME, IMAGE_VERSION.split('-')[0]);
|
|
1307
1428
|
process.exit(0);
|
|
1308
1429
|
}
|
|
1309
1430
|
|
|
1310
|
-
//
|
|
1431
|
+
// 4. Handle remove container operation
|
|
1311
1432
|
handleRemoveContainer();
|
|
1312
1433
|
|
|
1313
|
-
//
|
|
1434
|
+
// 5. Validate host path safety
|
|
1314
1435
|
validateHostPath();
|
|
1315
1436
|
|
|
1316
|
-
//
|
|
1437
|
+
// 6. Setup container (create or connect)
|
|
1317
1438
|
const defaultCommand = await setupContainer();
|
|
1318
1439
|
|
|
1319
|
-
//
|
|
1440
|
+
// 7. Execute command in container
|
|
1320
1441
|
executeInContainer(defaultCommand);
|
|
1321
1442
|
|
|
1322
|
-
//
|
|
1443
|
+
// 8. Handle post-exit interactions
|
|
1323
1444
|
await handlePostExit(defaultCommand);
|
|
1324
1445
|
|
|
1325
1446
|
} catch (e) {
|
package/config.example.json
CHANGED