@xcanwin/manyoyo 5.0.2 → 5.1.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/bin/manyoyo.js +138 -6
- package/config.example.json +28 -0
- package/lib/plugin/index.js +65 -0
- package/lib/plugin/playwright-assets/compose-headed.yaml +19 -0
- package/lib/plugin/playwright-assets/compose-headless.yaml +11 -0
- package/lib/plugin/playwright-assets/headed.Dockerfile +15 -0
- package/lib/plugin/playwright-assets/init-headed.sh +17 -0
- package/lib/plugin/playwright.js +1185 -0
- package/package.json +1 -1
package/bin/manyoyo.js
CHANGED
|
@@ -14,6 +14,7 @@ const { buildContainerRunArgs, buildContainerRunCommand } = require('../lib/cont
|
|
|
14
14
|
const { initAgentConfigs } = require('../lib/init-config');
|
|
15
15
|
const { buildImage } = require('../lib/image-build');
|
|
16
16
|
const { resolveAgentResumeArg, buildAgentResumeCommand } = require('../lib/agent-resume');
|
|
17
|
+
const { runPluginCommand } = require('../lib/plugin');
|
|
17
18
|
const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_DEFAULT } = require('../package.json');
|
|
18
19
|
const IMAGE_VERSION_BASE = String(IMAGE_VERSION_DEFAULT || '1.0.0').split('-')[0];
|
|
19
20
|
const IMAGE_VERSION_HELP_EXAMPLE = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
|
|
@@ -236,6 +237,7 @@ function sanitizeSensitiveData(obj) {
|
|
|
236
237
|
* @property {Object.<string, string|number|boolean>} [env] - 环境变量映射
|
|
237
238
|
* @property {string[]} [envFile] - 环境文件数组
|
|
238
239
|
* @property {string[]} [volumes] - 挂载卷数组
|
|
240
|
+
* @property {Object.<string, Object>} [plugins] - 可选插件配置映射(如 plugins.playwright)
|
|
239
241
|
* @property {Object.<string, Object>} [runs] - 运行配置映射(-r <name>)
|
|
240
242
|
* @property {string} [yolo] - YOLO 模式
|
|
241
243
|
* @property {string} [containerMode] - 容器模式
|
|
@@ -698,11 +700,33 @@ function updateManyoyo() {
|
|
|
698
700
|
|
|
699
701
|
function getContList() {
|
|
700
702
|
try {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
703
|
+
const output = dockerExecArgs([
|
|
704
|
+
'ps', '-a', '--size',
|
|
705
|
+
'--format', '{{.Names}}\t{{.Status}}\t{{.Size}}\t{{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Networks}}\t{{.Mounts}}'
|
|
706
|
+
], { stdio: 'pipe' });
|
|
707
|
+
|
|
708
|
+
const rows = output
|
|
709
|
+
.split('\n')
|
|
710
|
+
.map(line => line.trim())
|
|
711
|
+
.filter(Boolean)
|
|
712
|
+
.filter(line => {
|
|
713
|
+
const cols = line.split('\t');
|
|
714
|
+
const name = cols[0] || '';
|
|
715
|
+
const image = cols[4] || '';
|
|
716
|
+
// include manyoyo runtime containers (image match)
|
|
717
|
+
// and plugin containers (both legacy manyoyo-* and new my-* prefixes)
|
|
718
|
+
return image.includes('manyoyo') || name.startsWith('manyoyo-') || name.startsWith('my-');
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
console.log('NO.\tNAMES\tSTATUS\tSIZE\tCONTAINER ID\tIMAGE\tPORTS\tNETWORKS\tMOUNTS');
|
|
722
|
+
if (rows.length > 0) {
|
|
723
|
+
const numberedRows = rows.map((line, index) => {
|
|
724
|
+
return `${index + 1}.\t${line}`;
|
|
725
|
+
});
|
|
726
|
+
console.log(numberedRows.join('\n'));
|
|
727
|
+
}
|
|
704
728
|
} catch (e) {
|
|
705
|
-
console.log(e.stdout || '');
|
|
729
|
+
console.log((e && e.stdout) || '');
|
|
706
730
|
}
|
|
707
731
|
}
|
|
708
732
|
|
|
@@ -843,6 +867,67 @@ async function setupCommander() {
|
|
|
843
867
|
selectedAction = action;
|
|
844
868
|
selectedOptions = options;
|
|
845
869
|
};
|
|
870
|
+
const selectPluginAction = (params = {}, options = {}) => {
|
|
871
|
+
selectAction('plugin', {
|
|
872
|
+
...options,
|
|
873
|
+
pluginAction: params.action || 'ls',
|
|
874
|
+
pluginName: params.pluginName || 'playwright',
|
|
875
|
+
pluginScene: params.scene || 'host-headless',
|
|
876
|
+
pluginHost: params.host || '',
|
|
877
|
+
pluginExtensions: Array.isArray(params.extensions) ? params.extensions : [],
|
|
878
|
+
pluginProdversion: params.prodversion || ''
|
|
879
|
+
});
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
const registerPlaywrightAliasCommands = (command) => {
|
|
883
|
+
command.command('ls')
|
|
884
|
+
.description('列出 playwright 启用场景')
|
|
885
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
886
|
+
.action(options => selectPluginAction({
|
|
887
|
+
action: 'ls',
|
|
888
|
+
pluginName: 'playwright',
|
|
889
|
+
scene: 'all'
|
|
890
|
+
}, options));
|
|
891
|
+
|
|
892
|
+
const actions = ['up', 'down', 'status', 'health', 'logs'];
|
|
893
|
+
actions.forEach(action => {
|
|
894
|
+
const sceneCommand = command.command(`${action} [scene]`)
|
|
895
|
+
.description(`${action} playwright 场景,scene 默认 host-headless`)
|
|
896
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)');
|
|
897
|
+
|
|
898
|
+
if (action === 'up') {
|
|
899
|
+
appendArrayOption(sceneCommand, '--ext <path>', '追加浏览器扩展目录(可多次传入;目录需包含 manifest.json)');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
sceneCommand.action((scene, options) => selectPluginAction({
|
|
903
|
+
action,
|
|
904
|
+
pluginName: 'playwright',
|
|
905
|
+
scene: scene || 'host-headless',
|
|
906
|
+
extensions: action === 'up' ? (options.ext || []) : []
|
|
907
|
+
}, options));
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
command.command('mcp-add')
|
|
911
|
+
.description('输出 playwright 的 MCP 接入命令')
|
|
912
|
+
.option('--host <host>', 'MCP URL 使用的主机名或IP (默认 host.docker.internal)')
|
|
913
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
914
|
+
.action(options => selectPluginAction({
|
|
915
|
+
action: 'mcp-add',
|
|
916
|
+
pluginName: 'playwright',
|
|
917
|
+
scene: 'all',
|
|
918
|
+
host: options.host || ''
|
|
919
|
+
}, options));
|
|
920
|
+
|
|
921
|
+
command.command('ext-download')
|
|
922
|
+
.description('下载并解压 Playwright 扩展到 ~/.manyoyo/plugin/playwright/extensions/')
|
|
923
|
+
.option('--prodversion <ver>', 'CRX 下载使用的 Chrome 版本号 (默认 132.0.0.0)')
|
|
924
|
+
.action(options => selectPluginAction({
|
|
925
|
+
action: 'ext-download',
|
|
926
|
+
pluginName: 'playwright',
|
|
927
|
+
scene: 'all',
|
|
928
|
+
prodversion: options.prodversion || ''
|
|
929
|
+
}, options));
|
|
930
|
+
};
|
|
846
931
|
|
|
847
932
|
program
|
|
848
933
|
.name(MANYOYO_NAME)
|
|
@@ -870,6 +955,8 @@ async function setupCommander() {
|
|
|
870
955
|
${MANYOYO_NAME} run -x "echo 123" 指定命令执行
|
|
871
956
|
${MANYOYO_NAME} serve 3000 -u admin -P 123456 启动带登录认证的网页服务
|
|
872
957
|
${MANYOYO_NAME} serve 0.0.0.0:3000 监听全部网卡,便于局域网访问
|
|
958
|
+
${MANYOYO_NAME} playwright up host-headless 启动 playwright 默认场景(推荐)
|
|
959
|
+
${MANYOYO_NAME} plugin playwright up host-headless 通过 plugin 命名空间启动
|
|
873
960
|
${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
|
|
874
961
|
`);
|
|
875
962
|
|
|
@@ -910,6 +997,21 @@ async function setupCommander() {
|
|
|
910
997
|
});
|
|
911
998
|
});
|
|
912
999
|
|
|
1000
|
+
const playwrightCommand = program.command('playwright').description('管理 playwright 插件服务(推荐)');
|
|
1001
|
+
registerPlaywrightAliasCommands(playwrightCommand);
|
|
1002
|
+
|
|
1003
|
+
const pluginCommand = program.command('plugin').description('管理 manyoyo 插件');
|
|
1004
|
+
pluginCommand.command('ls')
|
|
1005
|
+
.description('列出可用插件与启用场景')
|
|
1006
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
1007
|
+
.action(options => selectPluginAction({
|
|
1008
|
+
action: 'ls',
|
|
1009
|
+
pluginName: 'playwright',
|
|
1010
|
+
scene: 'all'
|
|
1011
|
+
}, options));
|
|
1012
|
+
const pluginPlaywrightCommand = pluginCommand.command('playwright').description('管理 playwright 插件服务');
|
|
1013
|
+
registerPlaywrightAliasCommands(pluginPlaywrightCommand);
|
|
1014
|
+
|
|
913
1015
|
const configCommand = program.command('config').description('查看解析后的配置或命令');
|
|
914
1016
|
const configShowCommand = configCommand.command('show').description('显示最终生效配置并退出');
|
|
915
1017
|
applyRunStyleOptions(configShowCommand, { includeRmOnExit: false, includeServePreview: true });
|
|
@@ -982,7 +1084,7 @@ async function setupCommander() {
|
|
|
982
1084
|
const isShowCommandMode = selectedAction === 'config-command';
|
|
983
1085
|
const isServerMode = options.server !== undefined;
|
|
984
1086
|
|
|
985
|
-
const noDockerActions = new Set(['init', 'update', 'install', 'config-show']);
|
|
1087
|
+
const noDockerActions = new Set(['init', 'update', 'install', 'config-show', 'plugin']);
|
|
986
1088
|
if (!noDockerActions.has(selectedAction)) {
|
|
987
1089
|
ensureDocker();
|
|
988
1090
|
}
|
|
@@ -1003,6 +1105,23 @@ async function setupCommander() {
|
|
|
1003
1105
|
process.exit(0);
|
|
1004
1106
|
}
|
|
1005
1107
|
|
|
1108
|
+
if (selectedAction === 'plugin') {
|
|
1109
|
+
const runConfig = options.run ? loadRunConfig(options.run, config) : {};
|
|
1110
|
+
return {
|
|
1111
|
+
isPluginMode: true,
|
|
1112
|
+
pluginRequest: {
|
|
1113
|
+
action: options.pluginAction,
|
|
1114
|
+
pluginName: options.pluginName,
|
|
1115
|
+
scene: options.pluginScene || 'host-headless',
|
|
1116
|
+
host: options.pluginHost || '',
|
|
1117
|
+
extensions: Array.isArray(options.pluginExtensions) ? options.pluginExtensions : [],
|
|
1118
|
+
prodversion: options.pluginProdversion || ''
|
|
1119
|
+
},
|
|
1120
|
+
pluginGlobalConfig: config,
|
|
1121
|
+
pluginRunConfig: runConfig
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1006
1125
|
// Load run config if specified
|
|
1007
1126
|
const runConfig = options.run ? loadRunConfig(options.run, config) : {};
|
|
1008
1127
|
|
|
@@ -1162,7 +1281,8 @@ async function setupCommander() {
|
|
|
1162
1281
|
isBuildMode,
|
|
1163
1282
|
isRemoveMode,
|
|
1164
1283
|
isShowCommandMode,
|
|
1165
|
-
isServerMode
|
|
1284
|
+
isServerMode,
|
|
1285
|
+
isPluginMode: false
|
|
1166
1286
|
};
|
|
1167
1287
|
}
|
|
1168
1288
|
|
|
@@ -1495,6 +1615,18 @@ async function main() {
|
|
|
1495
1615
|
try {
|
|
1496
1616
|
// 1. Setup commander and parse arguments
|
|
1497
1617
|
const modeState = await setupCommander();
|
|
1618
|
+
|
|
1619
|
+
if (modeState.isPluginMode) {
|
|
1620
|
+
const exitCode = await runPluginCommand(modeState.pluginRequest, {
|
|
1621
|
+
globalConfig: modeState.pluginGlobalConfig,
|
|
1622
|
+
runConfig: modeState.pluginRunConfig,
|
|
1623
|
+
projectRoot: path.join(__dirname, '..'),
|
|
1624
|
+
stdout: process.stdout,
|
|
1625
|
+
stderr: process.stderr
|
|
1626
|
+
});
|
|
1627
|
+
process.exit(exitCode);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1498
1630
|
const runtime = createRuntimeContext(modeState);
|
|
1499
1631
|
|
|
1500
1632
|
// 2. Start web server mode
|
package/config.example.json
CHANGED
|
@@ -25,6 +25,29 @@
|
|
|
25
25
|
"imageBuildArgs": [],
|
|
26
26
|
"quiet": ["tip", "cmd"],
|
|
27
27
|
|
|
28
|
+
// 可选插件(manyoyo playwright / manyoyo plugin playwright)
|
|
29
|
+
"plugins": {
|
|
30
|
+
"playwright": {
|
|
31
|
+
// mixed: 支持容器+宿主机;container: 仅容器;host: 仅宿主机
|
|
32
|
+
"runtime": "mixed",
|
|
33
|
+
// 启用场景(可按需裁剪)
|
|
34
|
+
"enabledScenes": ["cont-headless", "cont-headed", "host-headless", "host-headed"],
|
|
35
|
+
// mcp-add 默认 host(可改为 localhost / 127.0.0.1)
|
|
36
|
+
"mcpDefaultHost": "host.docker.internal",
|
|
37
|
+
// cont-headed 场景读取的密码环境变量名(默认 VNC_PASSWORD)
|
|
38
|
+
"vncPasswordEnvKey": "VNC_PASSWORD",
|
|
39
|
+
// playwright ext-download 的 CRX prodversion 参数
|
|
40
|
+
"extensionProdversion": "132.0.0.0",
|
|
41
|
+
"ports": {
|
|
42
|
+
"contHeadless": 8931,
|
|
43
|
+
"contHeaded": 8932,
|
|
44
|
+
"hostHeadless": 8933,
|
|
45
|
+
"hostHeaded": 8934,
|
|
46
|
+
"contHeadedNoVnc": 6080
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
28
51
|
// 运行配置集合:通过 -r <name> 读取 runs.<name>
|
|
29
52
|
"runs": {
|
|
30
53
|
"claude": {
|
|
@@ -33,6 +56,11 @@
|
|
|
33
56
|
"shell": "claude",
|
|
34
57
|
"env": {
|
|
35
58
|
"ANTHROPIC_MODEL": "claude-sonnet-4-5"
|
|
59
|
+
},
|
|
60
|
+
"plugins": {
|
|
61
|
+
"playwright": {
|
|
62
|
+
"runtime": "container"
|
|
63
|
+
}
|
|
36
64
|
}
|
|
37
65
|
}
|
|
38
66
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PlaywrightPlugin } = require('./playwright');
|
|
4
|
+
|
|
5
|
+
const AVAILABLE_PLUGINS = ['playwright'];
|
|
6
|
+
|
|
7
|
+
function asObject(value) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getPluginConfigs(pluginName, globalConfig, runConfig) {
|
|
15
|
+
const globalPlugins = asObject(asObject(globalConfig).plugins);
|
|
16
|
+
const runPlugins = asObject(asObject(runConfig).plugins);
|
|
17
|
+
return {
|
|
18
|
+
globalPluginConfig: asObject(globalPlugins[pluginName]),
|
|
19
|
+
runPluginConfig: asObject(runPlugins[pluginName])
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createPlugin(pluginName, options = {}) {
|
|
24
|
+
if (pluginName !== 'playwright') {
|
|
25
|
+
throw new Error(`未知插件: ${pluginName}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cfg = getPluginConfigs(pluginName, options.globalConfig, options.runConfig);
|
|
29
|
+
return new PlaywrightPlugin({
|
|
30
|
+
projectRoot: options.projectRoot,
|
|
31
|
+
stdout: options.stdout,
|
|
32
|
+
stderr: options.stderr,
|
|
33
|
+
globalConfig: cfg.globalPluginConfig,
|
|
34
|
+
runConfig: cfg.runPluginConfig
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function runPluginCommand(request, options = {}) {
|
|
39
|
+
const action = String(request && request.action || '').trim();
|
|
40
|
+
|
|
41
|
+
if (action === 'ls') {
|
|
42
|
+
const plugin = createPlugin('playwright', options);
|
|
43
|
+
return await plugin.run({ action: 'ls' });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pluginName = String(request && request.pluginName || '').trim();
|
|
47
|
+
if (!pluginName) {
|
|
48
|
+
throw new Error('plugin 名称不能为空');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const plugin = createPlugin(pluginName, options);
|
|
52
|
+
return await plugin.run({
|
|
53
|
+
action,
|
|
54
|
+
scene: request.scene,
|
|
55
|
+
host: request.host,
|
|
56
|
+
extensions: request.extensions,
|
|
57
|
+
prodversion: request.prodversion
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
AVAILABLE_PLUGINS,
|
|
63
|
+
createPlugin,
|
|
64
|
+
runPluginCommand
|
|
65
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
services:
|
|
2
|
+
playwright:
|
|
3
|
+
build:
|
|
4
|
+
context: .
|
|
5
|
+
dockerfile: headed.Dockerfile
|
|
6
|
+
args:
|
|
7
|
+
PLAYWRIGHT_MCP_BASE_IMAGE: mcr.microsoft.com/playwright/mcp:${PLAYWRIGHT_MCP_DOCKER_TAG:-latest}
|
|
8
|
+
image: ${PLAYWRIGHT_MCP_IMAGE:-localhost/xcanwin/manyoyo-playwright-headed}
|
|
9
|
+
container_name: ${PLAYWRIGHT_MCP_CONTAINER_NAME:-my-playwright-cont-headed}
|
|
10
|
+
init: true
|
|
11
|
+
stdin_open: true
|
|
12
|
+
ports:
|
|
13
|
+
- "127.0.0.1:${PLAYWRIGHT_MCP_PORT:-8932}:${PLAYWRIGHT_MCP_PORT:-8932}"
|
|
14
|
+
- "127.0.0.1:${PLAYWRIGHT_MCP_NOVNC_PORT:-6080}:6080"
|
|
15
|
+
environment:
|
|
16
|
+
VNC_PASSWORD: "${VNC_PASSWORD:?VNC_PASSWORD is required}"
|
|
17
|
+
volumes:
|
|
18
|
+
- ${PLAYWRIGHT_MCP_CONFIG_PATH:?PLAYWRIGHT_MCP_CONFIG_PATH is required}:/app/config/playwright.json:ro
|
|
19
|
+
command: ["node", "cli.js", "--config", "/app/config/playwright.json"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
services:
|
|
2
|
+
playwright:
|
|
3
|
+
image: mcr.microsoft.com/playwright/mcp:${PLAYWRIGHT_MCP_DOCKER_TAG:-latest}
|
|
4
|
+
container_name: ${PLAYWRIGHT_MCP_CONTAINER_NAME:-my-playwright-cont-headless}
|
|
5
|
+
init: true
|
|
6
|
+
stdin_open: true
|
|
7
|
+
ports:
|
|
8
|
+
- "127.0.0.1:${PLAYWRIGHT_MCP_PORT:-8931}:${PLAYWRIGHT_MCP_PORT:-8931}"
|
|
9
|
+
volumes:
|
|
10
|
+
- ${PLAYWRIGHT_MCP_CONFIG_PATH:?PLAYWRIGHT_MCP_CONFIG_PATH is required}:/app/config/playwright.json:ro
|
|
11
|
+
command: ["--config", "/app/config/playwright.json"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ARG PLAYWRIGHT_MCP_BASE_IMAGE=mcr.microsoft.com/playwright/mcp:latest
|
|
2
|
+
FROM ${PLAYWRIGHT_MCP_BASE_IMAGE}
|
|
3
|
+
|
|
4
|
+
USER root
|
|
5
|
+
RUN apt-get update && \
|
|
6
|
+
apt-get install -y --no-install-recommends \
|
|
7
|
+
ca-certificates xvfb x11vnc novnc websockify fluxbox && \
|
|
8
|
+
update-ca-certificates && \
|
|
9
|
+
apt-get clean && \
|
|
10
|
+
rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
COPY init-headed.sh /usr/local/bin/init-headed.sh
|
|
13
|
+
RUN chmod +x /usr/local/bin/init-headed.sh
|
|
14
|
+
|
|
15
|
+
ENTRYPOINT ["/usr/local/bin/init-headed.sh"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
export DISPLAY=:99
|
|
5
|
+
|
|
6
|
+
Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp &
|
|
7
|
+
fluxbox >/tmp/fluxbox.log 2>&1 &
|
|
8
|
+
|
|
9
|
+
if [[ -z "${VNC_PASSWORD:-}" ]]; then
|
|
10
|
+
echo "VNC_PASSWORD is required."
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
x11vnc -display :99 -forever -shared -rfbport 5900 -localhost -passwd "$VNC_PASSWORD" >/tmp/x11vnc.log 2>&1 &
|
|
15
|
+
websockify --web=/usr/share/novnc/ 6080 localhost:5900 >/tmp/websockify.log 2>&1 &
|
|
16
|
+
|
|
17
|
+
exec "$@"
|