@xcanwin/manyoyo 3.8.6 → 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 +134 -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], []);
|
|
@@ -907,6 +954,11 @@ function setupCommander() {
|
|
|
907
954
|
process.argv.splice(2, 1);
|
|
908
955
|
}
|
|
909
956
|
|
|
957
|
+
// No args: show help instead of starting container
|
|
958
|
+
if (process.argv.length <= 2) {
|
|
959
|
+
program.help();
|
|
960
|
+
}
|
|
961
|
+
|
|
910
962
|
// Ensure docker/podman is available
|
|
911
963
|
ensureDocker();
|
|
912
964
|
|
|
@@ -950,7 +1002,7 @@ function setupCommander() {
|
|
|
950
1002
|
}
|
|
951
1003
|
|
|
952
1004
|
// Basic name validation to reduce injection risk
|
|
953
|
-
validateName('containerName', CONTAINER_NAME,
|
|
1005
|
+
validateName('containerName', CONTAINER_NAME, SAFE_CONTAINER_NAME_PATTERN);
|
|
954
1006
|
validateName('imageName', IMAGE_NAME, /^[A-Za-z0-9][A-Za-z0-9._/:-]*$/);
|
|
955
1007
|
validateName('imageVersion', IMAGE_VERSION, /^[A-Za-z0-9][A-Za-z0-9_.-]*$/);
|
|
956
1008
|
|
|
@@ -1005,6 +1057,26 @@ function setupCommander() {
|
|
|
1005
1057
|
RM_ON_EXIT = true;
|
|
1006
1058
|
}
|
|
1007
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
|
+
|
|
1008
1080
|
if (options.showConfig) {
|
|
1009
1081
|
const finalConfig = {
|
|
1010
1082
|
hostPath: HOST_PATH,
|
|
@@ -1022,6 +1094,10 @@ function setupCommander() {
|
|
|
1022
1094
|
shellSuffix: EXEC_COMMAND_SUFFIX || "",
|
|
1023
1095
|
yolo: yoloValue || "",
|
|
1024
1096
|
quiet: quietValue || [],
|
|
1097
|
+
server: SERVER_MODE,
|
|
1098
|
+
serverPort: SERVER_MODE ? SERVER_PORT : null,
|
|
1099
|
+
serverUser: SERVER_AUTH_USER || "",
|
|
1100
|
+
serverPass: SERVER_AUTH_PASS || "",
|
|
1025
1101
|
exec: {
|
|
1026
1102
|
prefix: EXEC_COMMAND_PREFIX,
|
|
1027
1103
|
shell: EXEC_COMMAND,
|
|
@@ -1287,6 +1363,50 @@ async function handlePostExit(defaultCommand) {
|
|
|
1287
1363
|
}
|
|
1288
1364
|
}
|
|
1289
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
|
+
|
|
1290
1410
|
// ==============================================================================
|
|
1291
1411
|
// Main Function
|
|
1292
1412
|
// ==============================================================================
|
|
@@ -1296,25 +1416,31 @@ async function main() {
|
|
|
1296
1416
|
// 1. Setup commander and parse arguments
|
|
1297
1417
|
setupCommander();
|
|
1298
1418
|
|
|
1299
|
-
// 2.
|
|
1419
|
+
// 2. Start web server mode
|
|
1420
|
+
if (SERVER_MODE) {
|
|
1421
|
+
await runWebServerMode();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// 3. Handle image build operation
|
|
1300
1426
|
if (IMAGE_BUILD_NEED) {
|
|
1301
1427
|
await buildImage(IMAGE_BUILD_ARGS, IMAGE_NAME, IMAGE_VERSION.split('-')[0]);
|
|
1302
1428
|
process.exit(0);
|
|
1303
1429
|
}
|
|
1304
1430
|
|
|
1305
|
-
//
|
|
1431
|
+
// 4. Handle remove container operation
|
|
1306
1432
|
handleRemoveContainer();
|
|
1307
1433
|
|
|
1308
|
-
//
|
|
1434
|
+
// 5. Validate host path safety
|
|
1309
1435
|
validateHostPath();
|
|
1310
1436
|
|
|
1311
|
-
//
|
|
1437
|
+
// 6. Setup container (create or connect)
|
|
1312
1438
|
const defaultCommand = await setupContainer();
|
|
1313
1439
|
|
|
1314
|
-
//
|
|
1440
|
+
// 7. Execute command in container
|
|
1315
1441
|
executeInContainer(defaultCommand);
|
|
1316
1442
|
|
|
1317
|
-
//
|
|
1443
|
+
// 8. Handle post-exit interactions
|
|
1318
1444
|
await handlePostExit(defaultCommand);
|
|
1319
1445
|
|
|
1320
1446
|
} catch (e) {
|
package/config.example.json
CHANGED