@lazycatcloud/lzc-cli 1.1.7 → 1.1.9
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 +69 -11
- package/lib/api.js +71 -36
- package/lib/app/index.js +79 -23
- package/lib/app/lpk_build.js +96 -52
- package/lib/app/lpk_create.js +63 -41
- package/lib/app/lpk_create_generator.js +202 -0
- package/lib/app/lpk_devshell.js +393 -328
- package/lib/app/lpk_devshell_docker.js +211 -0
- package/lib/app/lpk_installer.js +63 -26
- package/lib/app/lpk_log.js +68 -0
- package/lib/app/lpk_status.js +18 -0
- package/lib/app/lpk_uninstall.js +19 -0
- package/lib/appstore/index.js +37 -0
- package/lib/appstore/login.js +137 -0
- package/lib/appstore/publish.js +62 -0
- package/lib/autologin.js +0 -80
- package/lib/box/api/clientapi.js +1322 -0
- package/lib/box/api/empty.js +35 -0
- package/lib/box/check_qemu.js +1 -0
- package/lib/box/index.js +41 -94
- package/lib/box/qemu_vm_mgr.js +208 -239
- package/lib/box/schemes/vm_box_system_debian.json +1 -1
- package/lib/docker-compose.js +1 -2
- package/lib/env.js +23 -142
- package/lib/key.js +1 -0
- package/lib/sdk.js +10 -25
- package/lib/utils.js +156 -233
- package/package.json +19 -11
- package/scripts/cli.js +14 -135
- package/template/_lpk/README.md +31 -0
- package/template/_lpk/exec.sh +19 -0
- package/template/_lpk/golang.manifest.yml.in +16 -0
- package/template/_lpk/lazycat.png +0 -0
- package/template/_lpk/lite.manifest.yml.in +19 -0
- package/template/_lpk/local_devshell/Dockerfile +16 -0
- package/template/_lpk/local_devshell/build.sh +5 -0
- package/template/_lpk/local_devshell/entrypoint.sh +87 -0
- package/template/{_lazycat/debug/shell → _lpk/local_devshell}/sshd_config +8 -8
- package/template/_lpk/manifest.yml.in +0 -1
- package/template/{vue/lzc-build.yml → _lpk/vue.lzc-build.yml.in} +9 -1
- package/template/golang/README.md +0 -2
- package/template/golang/_gitignore +2 -0
- package/template/golang/build.sh +6 -0
- package/template/golang/lazycat.png +0 -0
- package/template/golang/lzc-build.yml +9 -1
- package/template/golang/rego.go +15 -16
- package/template/golang/rego_test.go +13 -0
- package/template/ionic_vue3/lazycat.png +0 -0
- package/template/ionic_vue3/lzc-build.yml +9 -1
- package/template/lite/error_pages/502.html.tpl +13 -0
- package/template/lite/lazycat.png +0 -0
- package/template/lite/lzc-build.yml +60 -0
- package/cmds/app.js +0 -133
- package/cmds/config.js +0 -55
- package/cmds/create.js +0 -55
- package/cmds/dev.js +0 -130
- package/cmds/init.js +0 -125
- package/cmds/log.js +0 -103
- package/cmds/publish.js +0 -116
- package/lib/archiver.js +0 -105
- package/lib/box/hportal.js +0 -120
- package/lib/builder.js +0 -313
- package/lib/dev.js +0 -314
- package/lib/generator.js +0 -146
- package/template/_lazycat/_gitignore +0 -1
- package/template/_lazycat/app-config +0 -1
- package/template/_lazycat/debug/devforward/50x.html +0 -30
- package/template/_lazycat/debug/devforward/Dockerfile +0 -16
- package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +0 -11
- package/template/_lazycat/debug/devforward/entrypoint.sh +0 -10
- package/template/_lazycat/debug/devforward/nginx.conf.template +0 -56
- package/template/_lazycat/debug/devforward/sshd_config +0 -116
- package/template/_lazycat/debug/shell/50x.html +0 -32
- package/template/_lazycat/debug/shell/Dockerfile +0 -18
- package/template/_lazycat/debug/shell/build.sh +0 -15
- package/template/_lazycat/debug/shell/docker-compose.override.yml.in +0 -13
- package/template/_lazycat/debug/shell/entrypoint.sh +0 -12
- package/template/_lazycat/docker-compose.yml.in +0 -15
- package/template/_lazycat/icon.svg +0 -1
- package/template/_lazycat/screenshot.png +0 -0
- package/template/_lpk/sync/Dockerfile +0 -16
- package/template/_lpk/sync/build.sh +0 -5
- package/template/_lpk/sync/entrypoint.sh +0 -8
- package/template/_lpk/sync/sshd_config +0 -117
- package/template/_lpk/sync.manifest.yml.in +0 -3
- package/template/release/golang/Dockerfile +0 -18
- package/template/release/golang/build.sh +0 -13
- package/template/release/ionic_vue3/Dockerfile +0 -10
- package/template/release/ionic_vue3/build.sh +0 -7
- package/template/release/ionic_vue3/docker-compose.yml.in +0 -3
- package/template/release/vue/Dockerfile +0 -10
- package/template/release/vue/build.sh +0 -10
- package/template/release/vue/docker-compose.yml.in +0 -3
- package/template/vue/README.md +0 -29
- package/template/vue/_dockerignore +0 -1
- package/template/vue/babel.config.js +0 -3
- package/template/vue/package.json +0 -43
- package/template/vue/public/favicon.ico +0 -0
- package/template/vue/public/index.html +0 -33
- package/template/vue/src/App.vue +0 -39
- package/template/vue/src/main.js +0 -8
- package/template/vue/src/todo.vue +0 -640
- package/template/vue/src/top-bar.vue +0 -100
- package/template/vue/src/webdav.vue +0 -183
- package/template/vue/vue.config.js +0 -21
package/lib/app/lpk_devshell.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import logger from "loglevel";
|
|
5
|
-
import {
|
|
5
|
+
import { spawn, execSync } from "node:child_process";
|
|
6
6
|
import { LpkBuild } from "./lpk_build.js";
|
|
7
7
|
import { LpkInstaller } from "./lpk_installer.js";
|
|
8
|
+
import debounce from "lodash.debounce";
|
|
8
9
|
import {
|
|
9
10
|
mergeYamlInMemory,
|
|
10
11
|
contextDirname,
|
|
@@ -14,7 +15,10 @@ import {
|
|
|
14
15
|
sleep,
|
|
15
16
|
envTemplateFile,
|
|
16
17
|
md5String,
|
|
18
|
+
md5File,
|
|
17
19
|
createTemplateFileCommon,
|
|
20
|
+
loadFromYaml,
|
|
21
|
+
FileLocker,
|
|
18
22
|
} from "../utils.js";
|
|
19
23
|
import yaml from "js-yaml";
|
|
20
24
|
import Key from "../key.js";
|
|
@@ -22,18 +26,153 @@ import os from "node:os";
|
|
|
22
26
|
import { sdkEnv } from "../env.js";
|
|
23
27
|
import commandExists from "command-exists";
|
|
24
28
|
import chokidar from "chokidar";
|
|
25
|
-
import
|
|
29
|
+
import _ from "lodash";
|
|
30
|
+
import BoxAPI from "../api.js";
|
|
31
|
+
import inquirer from "inquirer";
|
|
32
|
+
import { SdkDocker } from "./lpk_devshell_docker.js";
|
|
26
33
|
|
|
27
|
-
function
|
|
34
|
+
function sdkSSHHost() {
|
|
28
35
|
const sdkUrl = sdkEnv.sdkUrl;
|
|
29
36
|
let url = new URL(sdkUrl);
|
|
30
|
-
return `box@${url.hostname}
|
|
37
|
+
return `box@${url.hostname}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sdkSSHPort() {
|
|
41
|
+
return 2222;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sdkSSHAddress() {
|
|
45
|
+
const host = sdkSSHHost();
|
|
46
|
+
const port = sdkSSHPort();
|
|
47
|
+
return `${host}:${port}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 如果本地的机器没有 docker, 将会使用 rsync 同步
|
|
51
|
+
export function fallbackToRsync() {
|
|
52
|
+
return !commandExists.sync("docker");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 判断是否需要重新构建
|
|
56
|
+
// - 先判断 lzc-build.yml 是否发生改变
|
|
57
|
+
// - 再判断 lzc-build.yml 中的 manifest 中指定的文件是否发生改变
|
|
58
|
+
// - 文件的 md5 缓存在 /tmp/lzc-cli-devshell/$PKGID/hash => {}
|
|
59
|
+
// - 根据 backend api 判断一个 appid 是否属于 running
|
|
60
|
+
// - 根据在 backend api 中判断同步的目录下是否存在文件 判断当前运行的 app 是否已经有一个挂载的实例,避免重复挂载
|
|
61
|
+
class AppDevShellMonitor {
|
|
62
|
+
constructor(cwd, pkgId, rsyncMode = false) {
|
|
63
|
+
this.pwd = cwd ? path.resolve(cwd) : process.cwd();
|
|
64
|
+
this.rsyncMode = rsyncMode;
|
|
65
|
+
|
|
66
|
+
this.pkgId = pkgId;
|
|
67
|
+
this.boxapi = new BoxAPI(pkgId, sdkEnv.sdkUrl);
|
|
68
|
+
|
|
69
|
+
this.optionsFilePath = path.join(this.pwd, "lzc-build.yml");
|
|
70
|
+
this.options = loadFromYaml(this.optionsFilePath);
|
|
71
|
+
|
|
72
|
+
this.manifestFilePath = this.options["manifest"]
|
|
73
|
+
? path.join(this.pwd, this.options["manifest"])
|
|
74
|
+
: path.join(this.pwd, "lzc-manifest.yml");
|
|
75
|
+
|
|
76
|
+
this.hashObject = {
|
|
77
|
+
build: "",
|
|
78
|
+
manifest: "",
|
|
79
|
+
};
|
|
80
|
+
this.cacheFilePath = undefined;
|
|
81
|
+
this.oldHash = undefined;
|
|
82
|
+
this.newHash = undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async init() {
|
|
86
|
+
const pathId = await md5String(this.pwd);
|
|
87
|
+
this.cacheFilePath = path.resolve(
|
|
88
|
+
os.tmpdir(),
|
|
89
|
+
"lzc-cli-devshell",
|
|
90
|
+
pathId,
|
|
91
|
+
"hash"
|
|
92
|
+
);
|
|
93
|
+
ensureDir(this.cacheFilePath);
|
|
94
|
+
|
|
95
|
+
await this.updateHash();
|
|
96
|
+
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async shouldBuild() {
|
|
101
|
+
return (
|
|
102
|
+
this.change() ||
|
|
103
|
+
(await this.notRunning()) ||
|
|
104
|
+
(await this.noMount()) ||
|
|
105
|
+
(await this.noDevshell())
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async shouldRemount() {
|
|
110
|
+
try {
|
|
111
|
+
return await this.noMount();
|
|
112
|
+
} catch {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
change() {
|
|
118
|
+
logger.debug("oldHash", this.oldHash);
|
|
119
|
+
logger.debug("newHash", this.newHash);
|
|
120
|
+
return !_.isEqual(this.oldHash, this.newHash);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async updateHash() {
|
|
124
|
+
this.oldHash = isFileExist(this.cacheFilePath)
|
|
125
|
+
? JSON.parse(fs.readFileSync(this.cacheFilePath))
|
|
126
|
+
: {};
|
|
127
|
+
const buildHash = isFileExist(this.optionsFilePath)
|
|
128
|
+
? await md5File(this.optionsFilePath)
|
|
129
|
+
: "";
|
|
130
|
+
const manifestHash = isFileExist(this.manifestFilePath)
|
|
131
|
+
? await md5File(this.manifestFilePath)
|
|
132
|
+
: "";
|
|
133
|
+
this.newHash = {
|
|
134
|
+
build: buildHash,
|
|
135
|
+
manifest: manifestHash,
|
|
136
|
+
};
|
|
137
|
+
if (!_.isEqual(this.oldHash, this.newHash)) {
|
|
138
|
+
fs.writeFileSync(this.cacheFilePath, JSON.stringify(this.newHash));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async notRunning() {
|
|
143
|
+
try {
|
|
144
|
+
const { status } = await this.boxapi.status();
|
|
145
|
+
return status !== "running";
|
|
146
|
+
} catch {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async noMount() {
|
|
152
|
+
let hasMount = await this.boxapi.hasMount();
|
|
153
|
+
if (hasMount && this.rsyncMode) {
|
|
154
|
+
throw "sshfs docker 进程还在运行中,如果需要使用rsync模式,请先停止;或者继续使用sshfs模式";
|
|
155
|
+
}
|
|
156
|
+
if (this.rsyncMode) {
|
|
157
|
+
// rsync mode skip mount check
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return !hasMount;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async noDevshell() {
|
|
164
|
+
return !(await this.boxapi.isDevshell());
|
|
165
|
+
}
|
|
31
166
|
}
|
|
32
167
|
|
|
33
168
|
export class AppDevShell {
|
|
34
|
-
constructor(cwd, builder) {
|
|
169
|
+
constructor(cwd, builder, rsyncMode, forceBuild = false) {
|
|
35
170
|
this.cwd = cwd ? path.resolve(cwd) : process.cwd();
|
|
36
171
|
this.lpkBuild = builder;
|
|
172
|
+
this.monitor = undefined;
|
|
173
|
+
this.sshfsProcess = undefined;
|
|
174
|
+
this.rsyncMode = rsyncMode;
|
|
175
|
+
this.forceBuild = forceBuild;
|
|
37
176
|
}
|
|
38
177
|
|
|
39
178
|
async init() {
|
|
@@ -41,9 +180,28 @@ export class AppDevShell {
|
|
|
41
180
|
this.lpkBuild = new LpkBuild(this.cwd);
|
|
42
181
|
await this.lpkBuild.init();
|
|
43
182
|
}
|
|
183
|
+
const manifest = await this.lpkBuild.getManifest();
|
|
184
|
+
this.monitor = await new AppDevShellMonitor(
|
|
185
|
+
this.cwd,
|
|
186
|
+
manifest["package"],
|
|
187
|
+
this.rsyncMode
|
|
188
|
+
).init();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async build() {
|
|
192
|
+
// 先判断是否需要重新构建
|
|
193
|
+
if (this.forceBuild || (await this.monitor.shouldBuild())) {
|
|
194
|
+
logger.debug("build...");
|
|
195
|
+
await this.devshellBuild();
|
|
196
|
+
}
|
|
197
|
+
// 判断是否需要重新启动 sshfs 容器
|
|
198
|
+
if (!this.rsyncMode && (await this.monitor.shouldRemount())) {
|
|
199
|
+
logger.debug("mount...");
|
|
200
|
+
this.sshfsProcess = await this.sshfs();
|
|
201
|
+
}
|
|
44
202
|
}
|
|
45
203
|
|
|
46
|
-
async
|
|
204
|
+
async devshellBuild() {
|
|
47
205
|
// 确保 sdk key ,并上传到 sdk
|
|
48
206
|
const k = new Key();
|
|
49
207
|
await k.ensure(sdkEnv.sdkUrl);
|
|
@@ -55,9 +213,32 @@ export class AppDevShell {
|
|
|
55
213
|
logger.debug("devshell delete 'contentdir' field");
|
|
56
214
|
delete options["buildscript"];
|
|
57
215
|
delete options["contentdir"];
|
|
216
|
+
|
|
217
|
+
const devshell = options["devshell"];
|
|
218
|
+
if (!devshell) {
|
|
219
|
+
throw "devshell 模式下,devshell 字段必须要指定";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const routes = devshell["routes"];
|
|
223
|
+
if (!routes || routes.length == 0) {
|
|
224
|
+
throw "devshell 模式下,必须要指定 routes 内容";
|
|
225
|
+
}
|
|
226
|
+
|
|
58
227
|
return options;
|
|
59
228
|
});
|
|
60
229
|
|
|
230
|
+
// 复制 exec.sh 到 devshell 中去
|
|
231
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir) => {
|
|
232
|
+
const execScriptPath = path.join(
|
|
233
|
+
contextDirname(import.meta.url),
|
|
234
|
+
"../../template/_lpk/exec.sh"
|
|
235
|
+
);
|
|
236
|
+
let dest = path.join(contentdir, "devshell", "exec.sh");
|
|
237
|
+
ensureDir(dest);
|
|
238
|
+
fs.copyFileSync(execScriptPath, dest);
|
|
239
|
+
fs.chmodSync(dest, 0o775);
|
|
240
|
+
});
|
|
241
|
+
|
|
61
242
|
// 用 sdk ssh key 并复制到 contentdir 目录
|
|
62
243
|
// docker 中 sshd 的配置中已改成 /lzcapp/pkg/content/devshell/authorized_keys
|
|
63
244
|
this.lpkBuild.onBeforeTarContent(async (contentdir) => {
|
|
@@ -67,36 +248,36 @@ export class AppDevShell {
|
|
|
67
248
|
fs.copyFileSync(publicKey, dest);
|
|
68
249
|
});
|
|
69
250
|
|
|
70
|
-
//
|
|
71
|
-
this.lpkBuild.
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
251
|
+
// 复制 setupscript 脚本
|
|
252
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir, options) => {
|
|
253
|
+
const devshell = options["devshell"];
|
|
254
|
+
if (!devshell["setupscript"]) {
|
|
255
|
+
return;
|
|
75
256
|
}
|
|
76
257
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
258
|
+
logger.debug("处理 setupscript ");
|
|
259
|
+
const dest = path.join(contentdir, "devshell", "setupscript");
|
|
260
|
+
ensureDir(dest);
|
|
261
|
+
|
|
262
|
+
// 先判断是否文件
|
|
263
|
+
const filePath = path.resolve(devshell["setupscript"]);
|
|
264
|
+
if (isFileExist(filePath)) {
|
|
265
|
+
fs.copyFileSync(filePath, dest);
|
|
266
|
+
} else {
|
|
267
|
+
fs.writeFileSync(
|
|
268
|
+
dest,
|
|
269
|
+
`#!/bin/sh\nset -ex\n${devshell["setupscript"]}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
fs.chmodSync(dest, 0o775);
|
|
85
273
|
});
|
|
86
274
|
|
|
87
275
|
// 在生成 manifest.yml 之前合并 devshell manifest 模板字段
|
|
88
276
|
this.lpkBuild.onBeforeDumpYaml(async (manifest, options) => {
|
|
89
277
|
logger.debug("merge lzc-build.yml devshell routes field");
|
|
90
278
|
const devshell = options["devshell"];
|
|
91
|
-
if (!devshell) {
|
|
92
|
-
throw "devshell 模式下,devshell 字段必须要指定";
|
|
93
|
-
}
|
|
94
279
|
|
|
95
280
|
const routes = devshell["routes"];
|
|
96
|
-
if (!routes || routes.length == 0) {
|
|
97
|
-
throw "devshell 模式下,必须要指定 routes 内容";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
281
|
logger.debug("options devshell delete 'routes' field");
|
|
101
282
|
delete options["devshell"]["routes"];
|
|
102
283
|
// 如果 devshell 中的 router 和 manifest 中的 prefix 出现冲突
|
|
@@ -148,23 +329,26 @@ export class AppDevShell {
|
|
|
148
329
|
|
|
149
330
|
const depsStr = deps.sort().join(" ");
|
|
150
331
|
logger.debug("开始创建 Dockerfile 文件");
|
|
151
|
-
const tempDir = fs.mkdtempSync(".lzc-cli-build-dependencies");
|
|
152
|
-
const dockerfilePath = path.join(
|
|
153
|
-
contextDirname(import.meta.url),
|
|
154
|
-
"../../template/_lpk/Dockerfile.in"
|
|
155
|
-
);
|
|
156
|
-
await createTemplateFileCommon(
|
|
157
|
-
dockerfilePath,
|
|
158
|
-
path.join(tempDir, "Dockerfile"),
|
|
159
|
-
{ dependencies: depsStr }
|
|
160
|
-
);
|
|
161
|
-
|
|
162
332
|
const tag = `${await md5String(depsStr)}:latest`;
|
|
163
|
-
logger.debug(`开始在盒子中构建 ${tag} 镜像 from ${tempDir}`);
|
|
164
|
-
const sdk = new sdkDocker();
|
|
165
|
-
await sdk.buildImage(tag, tempDir, tempDir);
|
|
166
333
|
|
|
167
|
-
fs.
|
|
334
|
+
const tempDir = fs.mkdtempSync(".lzc-cli-build-dependencies");
|
|
335
|
+
try {
|
|
336
|
+
const dockerfilePath = path.join(
|
|
337
|
+
contextDirname(import.meta.url),
|
|
338
|
+
"../../template/_lpk/Dockerfile.in"
|
|
339
|
+
);
|
|
340
|
+
await createTemplateFileCommon(
|
|
341
|
+
dockerfilePath,
|
|
342
|
+
path.join(tempDir, "Dockerfile"),
|
|
343
|
+
{ dependencies: depsStr }
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
logger.debug(`开始在盒子中构建 ${tag} 镜像 from ${tempDir}`);
|
|
347
|
+
const sdk = new SdkDocker();
|
|
348
|
+
await sdk.buildImage(tag, tempDir, tempDir);
|
|
349
|
+
} finally {
|
|
350
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
351
|
+
}
|
|
168
352
|
|
|
169
353
|
delete manifest["application"]["devshell"];
|
|
170
354
|
manifest["application"]["image"] = tag;
|
|
@@ -187,7 +371,7 @@ export class AppDevShell {
|
|
|
187
371
|
|
|
188
372
|
logger.debug(`开始在盒子中构建 ${tag} 镜像`);
|
|
189
373
|
|
|
190
|
-
const sdk = new
|
|
374
|
+
const sdk = new SdkDocker();
|
|
191
375
|
await sdk.buildImage(tag, config["build"], process.cwd());
|
|
192
376
|
|
|
193
377
|
delete manifest["application"]["devshell"];
|
|
@@ -195,66 +379,135 @@ export class AppDevShell {
|
|
|
195
379
|
return manifest;
|
|
196
380
|
});
|
|
197
381
|
|
|
382
|
+
// 如果 devshell 中指定了 image 字段将使用 image 字段
|
|
383
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
384
|
+
const config = manifest["application"];
|
|
385
|
+
if (config["devshell"] && config["devshell"]["image"]) {
|
|
386
|
+
manifest["application"]["image"] = config["devshell"]["image"];
|
|
387
|
+
delete manifest["application"]["devshell"];
|
|
388
|
+
}
|
|
389
|
+
return manifest;
|
|
390
|
+
});
|
|
391
|
+
|
|
198
392
|
// 如果没有找到 devshell 中没有指定 image 不存在,将默认使用的 lzc-cli/devshell 容器
|
|
199
393
|
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
394
|
+
delete manifest["application"]["devshell"];
|
|
395
|
+
|
|
200
396
|
const config = manifest["application"];
|
|
201
397
|
if (config["image"]) {
|
|
202
398
|
return manifest;
|
|
203
399
|
}
|
|
400
|
+
|
|
401
|
+
logger.debug("use default lzc-cli/devshell image");
|
|
204
402
|
manifest["application"][
|
|
205
403
|
"image"
|
|
206
404
|
] = `registry.lazycat.cloud/lzc-cli/devshell:latest`;
|
|
207
405
|
return manifest;
|
|
208
406
|
});
|
|
209
407
|
|
|
408
|
+
// 添加一个 devshell 的标记在 lpk 中,标记当前 lpk 为一个 debug 版本
|
|
409
|
+
this.lpkBuild.onBeforeDumpLpk(async (options, cwd, destDir) => {
|
|
410
|
+
fs.writeFileSync(path.resolve(destDir, "devshell"), "");
|
|
411
|
+
});
|
|
412
|
+
|
|
210
413
|
// 在构建生成 lpk 包后,调用 deploy 进行部署
|
|
211
414
|
let installer = new LpkInstaller();
|
|
212
415
|
await installer.init();
|
|
213
416
|
await installer.deploy(this.lpkBuild);
|
|
214
417
|
|
|
215
418
|
await sleep(2000);
|
|
216
|
-
|
|
217
|
-
// 通过 rsync 同步源码到应用容器中
|
|
218
|
-
// TODO: 改成用 sshfs 的方式
|
|
219
|
-
await this.shell(true);
|
|
220
419
|
}
|
|
221
420
|
|
|
222
|
-
async
|
|
421
|
+
async rsyncShell(runShell) {
|
|
223
422
|
const k = new Key();
|
|
224
423
|
const pairs = await k.getKeyPair();
|
|
225
424
|
const manifest = await this.lpkBuild.getManifest();
|
|
226
|
-
const
|
|
227
|
-
|
|
425
|
+
const pkgId = manifest["package"];
|
|
426
|
+
|
|
427
|
+
let isSync = false;
|
|
428
|
+
try {
|
|
429
|
+
const locker = new FileLocker(pkgId);
|
|
430
|
+
locker.lock();
|
|
431
|
+
process.on("exit", () => {
|
|
432
|
+
logger.debug("filelock unlock");
|
|
433
|
+
locker.unlock();
|
|
434
|
+
});
|
|
435
|
+
isSync = true;
|
|
436
|
+
} catch (err) {
|
|
437
|
+
logger.debug("filelock catch");
|
|
438
|
+
logger.debug(err);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const devshell = new DevShell(pairs["private"], pkgId, runShell);
|
|
442
|
+
if (isSync) {
|
|
228
443
|
await devshell.shell();
|
|
229
444
|
} else {
|
|
230
|
-
await devshell.
|
|
445
|
+
await devshell.connectShell();
|
|
231
446
|
}
|
|
232
447
|
logger.debug("exit shell");
|
|
233
448
|
// TODO: shell 在正常情况下,按 Ctrl-D 就会退出,回到原来的本地的 shell ,但
|
|
234
449
|
// 现在会一直卡在退出状态后,必须要另外手动的指定 pkill node
|
|
235
450
|
process.exit(0);
|
|
236
451
|
}
|
|
452
|
+
|
|
453
|
+
async sshfs() {
|
|
454
|
+
const k = new Key();
|
|
455
|
+
const pairs = await k.getKeyPair();
|
|
456
|
+
const manifest = await this.lpkBuild.getManifest();
|
|
457
|
+
const localDevshell = new LocalDevshell(pairs, sdkSSHAddress());
|
|
458
|
+
const syncP = localDevshell.run(manifest.package, this.cwd);
|
|
459
|
+
await syncP.ready();
|
|
460
|
+
return syncP;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async sshfsShell(runShell) {
|
|
464
|
+
const manifest = await this.lpkBuild.getManifest();
|
|
465
|
+
const sdk = new SdkDocker();
|
|
466
|
+
await sdk.interactiveShell(manifest.package, runShell);
|
|
467
|
+
|
|
468
|
+
if (this.sshfsProcess) {
|
|
469
|
+
// already exit
|
|
470
|
+
if (this.sshfsProcess.p.exitCode) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const questions = [
|
|
475
|
+
{
|
|
476
|
+
name: "stop",
|
|
477
|
+
type: "input",
|
|
478
|
+
default: "n",
|
|
479
|
+
message: "是否停止 sshfs 同步容器(y/n): ",
|
|
480
|
+
},
|
|
481
|
+
];
|
|
482
|
+
const answers = await inquirer.prompt(questions);
|
|
483
|
+
if (answers.stop.toLowerCase() === "y") {
|
|
484
|
+
this.sshfsProcess.p.kill("SIGTERM");
|
|
485
|
+
} else {
|
|
486
|
+
this.sshfsProcess.p.unref();
|
|
487
|
+
// FIXME: why must process.exit()
|
|
488
|
+
process.exit(0);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
237
492
|
}
|
|
238
493
|
|
|
239
494
|
class DevShell {
|
|
240
|
-
constructor(keyFile, appId) {
|
|
495
|
+
constructor(keyFile, appId, runShell = "sh") {
|
|
241
496
|
logger.debug("keyFile", keyFile);
|
|
242
497
|
logger.debug("appid", appId);
|
|
243
498
|
|
|
244
|
-
this.tempDir = path.join(os.tmpdir(), "lzc-cli-devshell", appId);
|
|
245
|
-
if (!fs.existsSync(this.tmpdir)) {
|
|
246
|
-
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
247
|
-
}
|
|
248
499
|
this.keyFile = keyFile;
|
|
249
500
|
this.appId = appId;
|
|
501
|
+
this.runShell = runShell;
|
|
250
502
|
}
|
|
251
503
|
|
|
252
|
-
async syncProject(keyFile,
|
|
253
|
-
|
|
504
|
+
async syncProject(keyFile, appId) {
|
|
505
|
+
const host = sdkSSHHost();
|
|
506
|
+
const port = sdkSSHPort();
|
|
254
507
|
// prettier-ignore
|
|
255
508
|
let rsh = [
|
|
256
509
|
"ssh",
|
|
257
|
-
"-
|
|
510
|
+
"-p", `${port}`,
|
|
258
511
|
"-o", `"StrictHostKeyChecking=no"`,
|
|
259
512
|
"-o", `"UserKnownHostsFile=/dev/null"`,
|
|
260
513
|
"-o", `"ConnectionAttempts=3"`,
|
|
@@ -269,11 +522,18 @@ class DevShell {
|
|
|
269
522
|
process.exit(1);
|
|
270
523
|
}
|
|
271
524
|
|
|
525
|
+
let rsyncDebug = process.env.RSYNCDEBUG ? "-P" : "";
|
|
526
|
+
let storePath = `/var/lib/box-server/lzcapps/${appId}/lzcapp/cache/devshell`;
|
|
272
527
|
// FIXME: 下方执行命令不确定是否有兼容性问题
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
528
|
+
try {
|
|
529
|
+
execSync(
|
|
530
|
+
`rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update . ${host}:${storePath} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --delete --ignore-errors --usermap=:nobody --groupmap=*:nobody`,
|
|
531
|
+
{ stdio: ["ignore", "inherit", "inherit"] }
|
|
532
|
+
);
|
|
533
|
+
} catch (err) {
|
|
534
|
+
logger.error("rsync 同步失败");
|
|
535
|
+
logger.debug(err);
|
|
536
|
+
}
|
|
277
537
|
}
|
|
278
538
|
|
|
279
539
|
// fallback fs.watch on not darwin and windows platform
|
|
@@ -317,7 +577,7 @@ class DevShell {
|
|
|
317
577
|
|
|
318
578
|
// 监听非.gitignore文件
|
|
319
579
|
// TODO: 目前仅仅监听process.cwd()以下的文件
|
|
320
|
-
async watchFile(keyFile,
|
|
580
|
+
async watchFile(keyFile, appId) {
|
|
321
581
|
const ignore = new GitIgnore(process.cwd());
|
|
322
582
|
await ignore.collect();
|
|
323
583
|
chokidar
|
|
@@ -329,293 +589,98 @@ class DevShell {
|
|
|
329
589
|
},
|
|
330
590
|
ignoreInitial: true,
|
|
331
591
|
})
|
|
332
|
-
.on(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// 会先判断这个临时文件是否存在,如果不存在重新部署,更新流程
|
|
340
|
-
// 否则直接ssh连接
|
|
341
|
-
async storeShellStatus(isWatch) {
|
|
342
|
-
fs.writeFileSync(
|
|
343
|
-
path.join(this.tempDir, ".shellStatus"),
|
|
344
|
-
JSON.stringify({
|
|
345
|
-
isWatch,
|
|
346
|
-
})
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
isRsyncWatch() {
|
|
351
|
-
try {
|
|
352
|
-
let obj = this.readShellStatus();
|
|
353
|
-
return obj.isWatch;
|
|
354
|
-
} catch {
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async readShellStatus() {
|
|
360
|
-
return JSON.parse(
|
|
361
|
-
fs.readFileSync(path.join(this.tempDir, ".shellStatus"), "utf-8")
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async directShell() {
|
|
366
|
-
const keyFile = this.keyFile;
|
|
367
|
-
const appAddr = `sync.${this.appId}.lzcapp`;
|
|
368
|
-
|
|
369
|
-
let isWatch;
|
|
370
|
-
try {
|
|
371
|
-
({ isWatch } = await this.readShellStatus());
|
|
372
|
-
|
|
373
|
-
if (!isWatch) {
|
|
374
|
-
await this.syncProject(keyFile, appAddr, this.appId);
|
|
375
|
-
// 注册watch函数
|
|
376
|
-
await this.watchFile(keyFile, appAddr, this.appId);
|
|
377
|
-
|
|
378
|
-
this.storeShellStatus(true);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
await this.connectShell(this.appId);
|
|
382
|
-
} catch (e) {
|
|
383
|
-
return Promise.reject(e);
|
|
384
|
-
} finally {
|
|
385
|
-
if (!isWatch) {
|
|
386
|
-
this.storeShellStatus(false);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
592
|
+
.on(
|
|
593
|
+
"all",
|
|
594
|
+
debounce(() => {
|
|
595
|
+
this.syncProject(keyFile, appId);
|
|
596
|
+
}),
|
|
597
|
+
1000
|
|
598
|
+
);
|
|
389
599
|
}
|
|
390
|
-
|
|
391
600
|
async shell() {
|
|
392
601
|
let keyFile = this.keyFile;
|
|
393
|
-
const appAddr = `sync.${this.appId}.lzcapp`;
|
|
394
|
-
|
|
395
602
|
try {
|
|
396
603
|
// 当进入shell的时候,都同步一次
|
|
397
|
-
await this.syncProject(keyFile,
|
|
604
|
+
await this.syncProject(keyFile, this.appId);
|
|
398
605
|
// 注册watch函数
|
|
399
|
-
await this.watchFile(keyFile,
|
|
400
|
-
|
|
401
|
-
this.storeShellStatus(true);
|
|
606
|
+
await this.watchFile(keyFile, this.appId);
|
|
402
607
|
|
|
403
|
-
await this.connectShell(
|
|
608
|
+
await this.connectShell();
|
|
404
609
|
} catch (e) {
|
|
405
610
|
console.log(e);
|
|
406
611
|
// this.reset();
|
|
407
612
|
return Promise.reject(e);
|
|
408
|
-
} finally {
|
|
409
|
-
// 当shell退出后,更新isWatch状态
|
|
410
|
-
this.storeShellStatus(false);
|
|
411
613
|
}
|
|
412
614
|
}
|
|
413
615
|
|
|
414
|
-
async connectShell(
|
|
415
|
-
const sdk = new
|
|
416
|
-
|
|
417
|
-
await sdk.interactiveShell(`lzc--${replacedAppId}-app-1`);
|
|
616
|
+
async connectShell() {
|
|
617
|
+
const sdk = new SdkDocker();
|
|
618
|
+
await sdk.interactiveShell(this.appId, this.runShell);
|
|
418
619
|
}
|
|
419
620
|
}
|
|
420
621
|
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// 一个是 docker build 时的context. 这个一般是运行cli时候的路径
|
|
433
|
-
async buildImage(tag, buildDir, contextDir) {
|
|
434
|
-
const dockerfilePath = path.join(buildDir, "Dockerfile");
|
|
435
|
-
try {
|
|
436
|
-
await this.dockerRemoteBuildV2(contextDir, dockerfilePath, tag);
|
|
437
|
-
} catch (e) {
|
|
438
|
-
throw e;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
async collectContextFromDockerFile(contextDir, dockerfilePath) {
|
|
443
|
-
if (!fs.existsSync(dockerfilePath)) {
|
|
444
|
-
throw "未发现 Dockerfile";
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
let src = [path.relative(contextDir, dockerfilePath)];
|
|
448
|
-
|
|
449
|
-
// 通过 COPY 和 ADD 获取所有context中的文件
|
|
450
|
-
const ast = DockerfileParser.parse(fs.readFileSync(dockerfilePath, "utf8"));
|
|
451
|
-
for (let a of ast.getInstructions()) {
|
|
452
|
-
if (["COPY", "ADD"].includes(a.getInstruction())) {
|
|
453
|
-
const from = a.getArguments()[0].getValue().replace(/^\//, "");
|
|
454
|
-
|
|
455
|
-
const fromFullPath = path.join(contextDir, from);
|
|
456
|
-
|
|
457
|
-
if (fs.existsSync(fromFullPath)) {
|
|
458
|
-
const stat = fs.statSync(path.join(contextDir, from));
|
|
459
|
-
if (stat.isDirectory()) {
|
|
460
|
-
let files = await glob(path.join(from, "**"), {
|
|
461
|
-
cwd: contextDir,
|
|
462
|
-
});
|
|
463
|
-
src = src.concat(files);
|
|
464
|
-
} else if (stat.isFile()) {
|
|
465
|
-
src.push(from);
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
// try use glob
|
|
469
|
-
let files = await glob(from, {
|
|
470
|
-
cwd: contextDir,
|
|
471
|
-
});
|
|
472
|
-
src = src.concat(files);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// filter by dockerignore
|
|
477
|
-
const dockerIgnoreFile = path.join(contextDir, ".dockerignore");
|
|
478
|
-
//
|
|
479
|
-
if (fs.existsSync(dockerIgnoreFile)) {
|
|
480
|
-
let ig = ignore();
|
|
481
|
-
let data = fs.readFileSync(dockerIgnoreFile, "utf8");
|
|
482
|
-
ig.add(data.split("\n"));
|
|
483
|
-
src = ig.filter(src);
|
|
484
|
-
}
|
|
485
|
-
return src;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* v2 版本会收集所有需要的context 文件,将其传入buildImage 方法中
|
|
490
|
-
* @param contextDir
|
|
491
|
-
* @param dockerfile
|
|
492
|
-
* @param tag
|
|
493
|
-
*/
|
|
494
|
-
async dockerRemoteBuildV2(contextDir, dockerfile, tag) {
|
|
495
|
-
const src = await this.collectContextFromDockerFile(contextDir, dockerfile);
|
|
496
|
-
const host = new URL(sdkEnv.sdkUrl).host;
|
|
497
|
-
console.log(host);
|
|
498
|
-
const docker = await new DockerClient(host).init();
|
|
499
|
-
try {
|
|
500
|
-
await dockerPullLzcAppsImage(host);
|
|
501
|
-
|
|
502
|
-
return new Promise(async (resolve, reject) => {
|
|
503
|
-
let refresher = new StatusRefresher();
|
|
504
|
-
|
|
505
|
-
logger.info("开始在设备中构建镜像");
|
|
506
|
-
const stream = await docker.buildImage(
|
|
507
|
-
{
|
|
508
|
-
context: contextDir,
|
|
509
|
-
src: src,
|
|
510
|
-
},
|
|
511
|
-
{
|
|
512
|
-
dockerfile: src[0],
|
|
513
|
-
t: tag,
|
|
514
|
-
}
|
|
515
|
-
);
|
|
516
|
-
docker.modem.followProgress(
|
|
517
|
-
stream,
|
|
518
|
-
(err, res) => {
|
|
519
|
-
err ? reject(err) : resolve(res);
|
|
520
|
-
},
|
|
521
|
-
(res) => {
|
|
522
|
-
if (res.status) {
|
|
523
|
-
if (res.id) {
|
|
524
|
-
if (["Downloading", "Extracting"].includes(res.status)) {
|
|
525
|
-
refresher.updateStatus({
|
|
526
|
-
id: res.id,
|
|
527
|
-
content: `${res.status} ${res.progress}`,
|
|
528
|
-
});
|
|
529
|
-
} else {
|
|
530
|
-
refresher.updateStatus({
|
|
531
|
-
id: res.id,
|
|
532
|
-
content: res.status,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
} else {
|
|
536
|
-
process.stdout.write(res.status);
|
|
537
|
-
process.stdout.write("\n");
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (res.stream) {
|
|
542
|
-
if (res.stream.startsWith("Step")) {
|
|
543
|
-
refresher.reset();
|
|
544
|
-
}
|
|
545
|
-
process.stdout.write(res.stream);
|
|
546
|
-
}
|
|
547
|
-
if (res.error) {
|
|
548
|
-
reject(res.error);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
);
|
|
552
|
-
});
|
|
553
|
-
} catch (err) {
|
|
554
|
-
throw err;
|
|
555
|
-
}
|
|
622
|
+
// 本地的 devshell 容器
|
|
623
|
+
// - 将当前项目目录通过 volumes 的方式绑定到 local_devshell 中
|
|
624
|
+
// - local_devshell 中通过 ssh 反代本地的 22 端口到 sdk 中 30222
|
|
625
|
+
// - sdk 中的 sshfs 通过连接 30222 端口进行同步
|
|
626
|
+
// - 结合 local_devshell 容器中的 entrypoint 脚本
|
|
627
|
+
class LocalDevshell {
|
|
628
|
+
constructor(pairs, sdkHost) {
|
|
629
|
+
this.publicKey = pairs["public"];
|
|
630
|
+
this.privateKey = pairs["private"];
|
|
631
|
+
this.sdkHost = sdkHost;
|
|
632
|
+
this.log = logger.getLogger("LocalDevshell");
|
|
556
633
|
}
|
|
557
634
|
|
|
558
|
-
|
|
559
|
-
const host =
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
635
|
+
run(pkgId, projectPath) {
|
|
636
|
+
const host = this.sdkHost.split(":")[0];
|
|
637
|
+
const port = this.sdkHost.split(":")[1];
|
|
638
|
+
// prettier-ignore
|
|
639
|
+
const syncP = spawn(`docker`, [
|
|
640
|
+
`run`, `--rm`,
|
|
641
|
+
`--network=host`,
|
|
642
|
+
`-v`, `${projectPath}:/project/${pkgId}`,
|
|
643
|
+
`-v`, `${this.privateKey}:/sdk/privateKey`,
|
|
644
|
+
`-v`, `${this.publicKey}:/sdk/publicKey`,
|
|
645
|
+
`-e`, `PKGID=${pkgId}`,
|
|
646
|
+
`-e`, `SDK_HOST=${host}`,
|
|
647
|
+
`-e`, `SDK_PORT=${port}`,
|
|
648
|
+
`-e`, `HOSTUID=${process.getuid()}`,
|
|
649
|
+
`-e`, `HOSTGID=${process.getgid()}`,
|
|
650
|
+
`registry.lazycat.cloud/lzc-cli/local_devshell:0.0.4`,
|
|
651
|
+
], { detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
652
|
+
syncP.stdout.on("data", (data) => {
|
|
653
|
+
this.log.debug(data.toString());
|
|
568
654
|
});
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
process.stdin.setRawMode(true);
|
|
573
|
-
stream.pipe(process.stdout);
|
|
574
|
-
process.stdin.pipe(stream);
|
|
575
|
-
|
|
576
|
-
// prepare command
|
|
577
|
-
stream.write("cd /lzcapp/cache\n");
|
|
578
|
-
stream.write("ls\n");
|
|
579
|
-
|
|
580
|
-
await new Promise((resolve) => {
|
|
581
|
-
stream.on("end", () => {
|
|
582
|
-
logger.info("exit");
|
|
583
|
-
resolve();
|
|
584
|
-
});
|
|
655
|
+
syncP.stderr.on("data", (data) => {
|
|
656
|
+
this.log.debug(data.toString());
|
|
585
657
|
});
|
|
658
|
+
return {
|
|
659
|
+
p: syncP,
|
|
660
|
+
ready: async () => {
|
|
661
|
+
return new Promise((_resolve, _reject) => {
|
|
662
|
+
syncP.stdout.on("data", (data) => {
|
|
663
|
+
if (data.toString().search(/sshfs running/g) > -1) {
|
|
664
|
+
this.log.debug("sshfs ready");
|
|
665
|
+
_resolve();
|
|
666
|
+
}
|
|
667
|
+
});
|
|
586
668
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
class StatusRefresher {
|
|
594
|
-
constructor() {
|
|
595
|
-
this.reset();
|
|
596
|
-
}
|
|
597
|
-
refresh() {
|
|
598
|
-
this.logUpdate(this.pulls.map((p) => `${p.id}: ${p.content}`).join("\n"));
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
reset() {
|
|
602
|
-
this.pulls = [];
|
|
603
|
-
this.logUpdate = createLogUpdate(process.stdout);
|
|
604
|
-
}
|
|
669
|
+
syncP.on("error", (err) => {
|
|
670
|
+
this.log.error(err);
|
|
671
|
+
});
|
|
605
672
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
this.refresh();
|
|
673
|
+
syncP.on("close", (code) => {
|
|
674
|
+
if (code != 0) {
|
|
675
|
+
this.log.debug(`docker exited with code ${code}`);
|
|
676
|
+
_reject();
|
|
677
|
+
} else {
|
|
678
|
+
this.log.debug("sshfs close");
|
|
679
|
+
_resolve();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
},
|
|
684
|
+
};
|
|
620
685
|
}
|
|
621
686
|
}
|