@lazycatcloud/lzc-cli 1.1.4 → 1.1.7

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.
Files changed (52) hide show
  1. package/cmds/app.js +1 -5
  2. package/cmds/dev.js +12 -4
  3. package/lib/api.js +21 -7
  4. package/lib/app/index.js +92 -0
  5. package/lib/app/lpk_build.js +229 -0
  6. package/lib/app/lpk_create.js +201 -0
  7. package/lib/app/lpk_devshell.js +621 -0
  8. package/lib/app/lpk_installer.js +67 -0
  9. package/lib/archiver.js +0 -42
  10. package/lib/autologin.js +84 -0
  11. package/lib/box/check_qemu.js +39 -16
  12. package/lib/box/hportal.js +7 -1
  13. package/lib/box/index.js +9 -6
  14. package/lib/box/qemu_vm_mgr.js +37 -23
  15. package/lib/builder.js +12 -1
  16. package/lib/dev.js +4 -9
  17. package/lib/env.js +9 -5
  18. package/lib/generator.js +2 -2
  19. package/lib/sdk.js +29 -5
  20. package/lib/utils.js +69 -46
  21. package/package.json +8 -2
  22. package/scripts/cli.js +91 -12
  23. package/template/_lazycat/debug/shell/Dockerfile +5 -3
  24. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +2 -12
  25. package/template/_lazycat/debug/shell/entrypoint.sh +3 -1
  26. package/template/_lpk/Dockerfile.in +8 -0
  27. package/template/_lpk/devshell/Dockerfile +18 -0
  28. package/template/_lpk/devshell/build.sh +5 -0
  29. package/template/_lpk/devshell/entrypoint.sh +8 -0
  30. package/template/_lpk/devshell/sshd_config +117 -0
  31. package/template/_lpk/manifest.yml.in +17 -0
  32. package/template/_lpk/sync/Dockerfile +16 -0
  33. package/template/_lpk/sync/build.sh +5 -0
  34. package/template/_lpk/sync/entrypoint.sh +8 -0
  35. package/template/_lpk/sync/sshd_config +117 -0
  36. package/template/_lpk/sync.manifest.yml.in +3 -0
  37. package/template/golang/build.sh +6 -0
  38. package/template/golang/lzc-build.yml +52 -0
  39. package/template/ionic_vue3/lzc-build.yml +53 -0
  40. package/template/ionic_vue3/vite.config.ts +1 -1
  41. package/template/release/golang/build.sh +1 -1
  42. package/template/release/ionic_vue3/Dockerfile +1 -1
  43. package/template/release/ionic_vue3/build.sh +1 -3
  44. package/template/release/ionic_vue3/docker-compose.yml.in +0 -5
  45. package/template/release/vue/Dockerfile +1 -1
  46. package/template/release/vue/build.sh +2 -3
  47. package/template/release/vue/docker-compose.yml.in +0 -5
  48. package/template/vue/lzc-build.yml +53 -0
  49. package/template/vue/src/main.js +3 -14
  50. package/template/vue/vue.config.js +16 -0
  51. package/template/_lazycat/debug/shell/nginx.conf.template +0 -64
  52. package/template/vue/src/lzc.js +0 -110
package/lib/archiver.js CHANGED
@@ -94,54 +94,12 @@ class Archiver {
94
94
  }
95
95
 
96
96
  async finalize(zip = true) {
97
- // await this._changeContent();
98
97
  if (zip) {
99
98
  return await archiveFolder(this.tmpDir);
100
99
  } else {
101
100
  return this.tmpDir;
102
101
  }
103
102
  }
104
- //
105
- // permissions:
106
- // - lzcapis
107
- // need REMOVE deprecated
108
- async _changeContent() {
109
- let base = new DockerCompose(path.join(this.tmpDir, "docker-compose.yml"));
110
- base
111
- .pipe((template) => {
112
- const meta = template[META_MARK];
113
- if (
114
- meta &&
115
- Array.isArray(meta["permissions"]) &&
116
- meta["permissions"].includes("lzcapis")
117
- ) {
118
- template[META_MARK]["ingress"].push({
119
- service: "lazycat-apis-sidecar",
120
- port: 8888,
121
- subdomain: env.get("APP_ID"),
122
- path: "/lzcapis/",
123
- auth: "oidc",
124
- authcallback: "/lzcapis/oidc-callback",
125
- });
126
- template["services"]["lazycat-apis-sidecar"] = {
127
- image: "registry.lazycat.cloud/lazycat-apis-sidecar",
128
- pull_policy: "always",
129
- // volumes_from: ["${APP_NAME}:rw"],
130
- volumes: ["lzcapis-lzcapp:/lzcapp"],
131
- command: [
132
- "--client-id=${LAZYCAT_AUTH_OIDC_CLIENT_ID}",
133
- "--client-secret=${LAZYCAT_AUTH_OIDC_CLIENT_SECRET}",
134
- "--client-url=https://${LAZYCAT_APP_ORIGIN}/lzcapis/",
135
- "--issuer=${LAZYCAT_AUTH_OIDC_ISSUER_URL}",
136
- "--prefix=lzcapis",
137
- "--fs-root=/lzcapp/documents",
138
- ],
139
- };
140
- template["volumes"]["lzcapis-lzcapp"] = null;
141
- }
142
- })
143
- .save();
144
- }
145
103
  }
146
104
 
147
105
  export default Archiver;
@@ -0,0 +1,84 @@
1
+ import fetch from "node-fetch";
2
+
3
+ // autologin.js 提供一个封装好的 fetch request 请求方法,通过这个中间件,可以不用考虑
4
+ // auth:auto 所做的授权验证。
5
+ let AutoLoginToken = "";
6
+ let AutoLoginFailed = false;
7
+
8
+ function fetchTokenAction(url) {
9
+ return fetch(url).then(async (res) => {
10
+ const body = await res.text();
11
+ const token = body.match(/name=\"token\" value=\"(.*?)\"/i)[1];
12
+ const action = body.match(/action=\"(.*?)\"/i)[1];
13
+
14
+ return { token, action };
15
+ });
16
+ }
17
+
18
+ function fetchTokenActionAndRedirect(dataToken, dataAction) {
19
+ return fetch(dataAction, {
20
+ method: "POST",
21
+ body: `token=${dataToken}`,
22
+ redirect: "follow",
23
+ headers: {
24
+ accept:
25
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
26
+ "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
27
+ "content-type": "application/x-www-form-urlencoded",
28
+ },
29
+ }).then(async (res) => {
30
+ let body = await res.text();
31
+ const token = body.match(/name=\"token\" value=\"(.*?)\"/i)[1];
32
+ const action = body.match(/action=\"(.*?)\"/i)[1];
33
+ const redirect = body.match(/name=\"redirect\" value=\"(.*?)\"/i)[1];
34
+
35
+ return { token, action, redirect };
36
+ });
37
+ }
38
+
39
+ export async function request(url, options = {}) {
40
+ if (AutoLoginFailed) {
41
+ return fetch(url, options);
42
+ }
43
+
44
+ await tryAuthLogin(url);
45
+
46
+ if (!AutoLoginToken) {
47
+ return fetch(url, options);
48
+ }
49
+
50
+ let headers = { cookie: `HC-Auth-Token=${AutoLoginToken}` };
51
+ if (options.headers) {
52
+ headers = Object.assign({}, options.headers, headers);
53
+ }
54
+
55
+ options = Object.assign({}, options, { headers });
56
+ return fetch(url, options);
57
+ }
58
+
59
+ export async function tryAuthLogin(url) {
60
+ if (!url.startsWith("http")) {
61
+ url = "https://" + url;
62
+ }
63
+
64
+ try {
65
+ if (!AutoLoginToken) {
66
+ let _url = new URL(url);
67
+ let u = _url.host.split(".").reverse().slice(0, 3).reverse().join(".");
68
+
69
+ console.log(`${u} 尝试自动登录...`);
70
+
71
+ let data = await fetchTokenAction(`${_url.protocol}//${u}`);
72
+ let data2 = await fetchTokenActionAndRedirect(data.token, data.action);
73
+
74
+ AutoLoginToken = data2.token;
75
+ console.log(`自动登录成功!`);
76
+ return AutoLoginToken;
77
+ }
78
+ } catch (e) {
79
+ console.error(e);
80
+ console.log(`尝试自动登录失败,正在使用默认的登录方式...`);
81
+ AutoLoginFailed = true;
82
+ return "";
83
+ }
84
+ }
@@ -1,27 +1,50 @@
1
1
  // 检测当前系统是否安装 qemu 软件
2
2
  import commandExist from "command-exists";
3
3
  import chalk from "chalk";
4
+ import os from "node:os";
5
+
6
+ function linuxPlatform(pkg) {
7
+ let r = os.release();
8
+ if (r.search(/arch/gi) > -1) {
9
+ return `sudo pacman -S ${pkg}`;
10
+ } else if (r.search(/debian/gi) > -1) {
11
+ return `sudo apt install ${pkg}`;
12
+ } else {
13
+ return ``;
14
+ }
15
+ }
16
+
17
+ function platformPackage(pkg) {
18
+ let cmd = "";
19
+ switch (os.platform()) {
20
+ case "darwin":
21
+ cmd = `brew install ${pkg}`;
22
+ break;
23
+ case "linux":
24
+ cmd = linuxPlatform(pkg);
25
+ break;
26
+ }
27
+ return `${pkg}包没有发现,请先通过系统包管理器安装。\n${cmd}`;
28
+ }
4
29
 
5
30
  export class CheckQemu {
6
31
  constructor() {
7
- this.commands = ["qemu-img", "qemu-system-x86_64", "pgrep"];
32
+ this.commands = ["qemu-img", "qemu-system-x86_64"];
8
33
  }
9
34
 
10
35
  async init() {
11
- let ps = [];
12
- this.commands.forEach((cmd) => {
13
- let p = new Promise((resolve, reject) => {
14
- commandExist(cmd)
15
- .then(resolve)
16
- .catch(() => {
17
- console.error(
18
- chalk.red(`${cmd} 命令没有发现,请通过系统包管理器安装对应的包`)
19
- );
20
- reject();
21
- });
22
- });
23
- ps.push(p);
24
- });
25
- return Promise.all(ps);
36
+ for (let cmd of this.commands) {
37
+ if (!commandExist.sync(cmd)) {
38
+ let cmdErr = chalk.red(`${cmd} 命令没有发现`);
39
+ let pkgTips = chalk.blue(platformPackage("qemu"));
40
+ let tips = `${cmdErr}
41
+
42
+ ${pkgTips}
43
+
44
+ 查看更多信息 https://www.qemu.org/download/
45
+ `;
46
+ throw tips;
47
+ }
48
+ }
26
49
  }
27
50
  }
@@ -1,4 +1,5 @@
1
1
  import fetch from "node-fetch";
2
+ import logger from "loglevel";
2
3
 
3
4
  export class HportalManager {
4
5
  constructor(apiHost) {
@@ -58,11 +59,16 @@ export class HportalManager {
58
59
  async boxs(boxName, defaultBoxName = "") {
59
60
  let url = this.apiHost + "/admin/boxes";
60
61
 
61
- let origin;
62
+ let origin = [];
62
63
  try {
63
64
  const resp = await fetch(url);
64
65
  if (resp.status == 200) {
65
66
  origin = await resp.json();
67
+ } else {
68
+ logger.debug(
69
+ `status: ${resp.status}, url: ${resp.url}, text: ${resp.statusText}`
70
+ );
71
+ return [];
66
72
  }
67
73
  } catch {
68
74
  return [];
package/lib/box/index.js CHANGED
@@ -7,6 +7,7 @@ import { contextDirname } from "../utils.js";
7
7
  import env, { sdkEnv } from "../env.js";
8
8
  import { HportalManager, showBoxInfo } from "./hportal.js";
9
9
  import { CheckQemu } from "./check_qemu.js";
10
+ import logger from "loglevel";
10
11
 
11
12
  async function initQemuVM(ensureResources = false) {
12
13
  let defaultSchemeFile = path.join(
@@ -25,6 +26,8 @@ async function initQemuVM(ensureResources = false) {
25
26
  scheme.path = path.join(contextDirname(), "box-emulator", scheme.path);
26
27
  }
27
28
 
29
+ fs.mkdirSync(scheme.path, { recursive: true });
30
+
28
31
  if (ensureResources) {
29
32
  // 使用本地网络 api 调试 new QemuResource(scheme.path, "http://127.0.0.1");
30
33
  let resource = new QemuResource(scheme.path);
@@ -58,7 +61,7 @@ export function boxCommand(box) {
58
61
  // 创建盒子阶段,如果出错,将直接删除所有的资源。
59
62
  boxId = await m.createVM(answer);
60
63
  } catch (error) {
61
- console.error(error);
64
+ logger.error(error);
62
65
  await m.cleanVM(answer.boxName);
63
66
  return;
64
67
  }
@@ -72,11 +75,11 @@ export function boxCommand(box) {
72
75
  }
73
76
 
74
77
  let h = await initHportal();
75
- console.log("添加盒子到 Hportal ......");
78
+ logger.debug("添加盒子到 Hportal ......");
76
79
  await h.addBox(answer.boxName);
77
- console.log("正在使用管理员帐号密码登录中......");
80
+ logger.debug("正在使用管理员帐号密码登录中......");
78
81
  await h.loginBox(boxId, answer.adminName, answer.adminPass);
79
- console.log("登录成功!");
82
+ logger.debug("登录成功!");
80
83
  },
81
84
  },
82
85
  {
@@ -86,7 +89,7 @@ export function boxCommand(box) {
86
89
  let m = await initQemuVM();
87
90
  let boxid = await m.runVM(boxName);
88
91
  if (boxid) {
89
- console.log("盒子ID: ", boxid);
92
+ logger.info("盒子ID: ", boxid);
90
93
  }
91
94
  },
92
95
  },
@@ -125,7 +128,7 @@ export function boxCommand(box) {
125
128
 
126
129
  // 过滤条件不满足
127
130
  if (vmInfos.length == 0 && rmInfos == 0 && boxName) {
128
- console.log(`${boxName} 盒子不存在`);
131
+ logger.info(`${boxName} 盒子不存在`);
129
132
  let allVmInfos = await m.infoVM("", defaultBoxName);
130
133
  let allRmInfos = await rm.boxs("", defaultBoxName);
131
134
  showBoxInfo(allVmInfos, allRmInfos);
@@ -5,10 +5,12 @@ import path from "node:path";
5
5
  import inquirer from "inquirer";
6
6
  import https from "node:https";
7
7
  import http from "node:http";
8
- import lz4 from "lz4";
9
8
  import process from "node:process";
10
9
  import net from "node:net";
11
10
  import fetch from "node-fetch";
11
+ import zlib from "node:zlib";
12
+ import os from "node:os";
13
+ import logger from "loglevel";
12
14
 
13
15
  async function getFreePort() {
14
16
  return new Promise((resolve, reject) => {
@@ -39,7 +41,7 @@ export class QemuResource {
39
41
  */
40
42
  constructor(
41
43
  diskDir = "~/.cache/box-emulator",
42
- downloadUrl = "https://dl.lazycat.cloud/sdk/vm"
44
+ downloadUrl = "https://dl.lazycat.cloud/sdk"
43
45
  ) {
44
46
  this.diskDir = diskDir;
45
47
  this.downloadUrl = downloadUrl;
@@ -71,7 +73,7 @@ export class QemuResource {
71
73
  * 目前的更新,只是使用 md5 来判断是否需要重新下载 md5, 并不会使用 md5 来判断所下载的数据是否正确。
72
74
  */
73
75
  async shouldUpdate() {
74
- let oldMd5Path = path.join(this.diskDir, "disk-system.qcow2.lz4.md5");
76
+ let oldMd5Path = path.join(this.diskDir, "disk-system.qcow2.md5");
75
77
 
76
78
  // 如果不存在 md5 文件,直接返回需要更新
77
79
  try {
@@ -83,7 +85,7 @@ export class QemuResource {
83
85
  // 拉取最新的 md5
84
86
  let newMd5;
85
87
  try {
86
- let url = `${this.downloadUrl}/disk-system.qcow2.lz4.md5`;
88
+ let url = `${this.downloadUrl}/vm/disk-system.qcow2.md5`;
87
89
  let res = await fetch(url);
88
90
  if (res.status !== 200) {
89
91
  return false;
@@ -109,26 +111,34 @@ export class QemuResource {
109
111
  } catch {}
110
112
  }
111
113
 
114
+ await this.downloadOVMF();
112
115
  await this.downloadSystemDisk();
113
116
  await this.downloadPrivateKey();
114
117
  await this.downloadSystemDiskMd5();
115
118
  }
116
119
 
120
+ async downloadOVMF() {
121
+ let name = `${os.arch()}.OVMF.fd`;
122
+ let savePath = path.join(this.diskDir, name);
123
+ let url = `${this.downloadUrl}/${name}`;
124
+ await this.download(url, savePath);
125
+ }
126
+
117
127
  async downloadSystemDiskMd5() {
118
- let savePath = path.join(this.diskDir, "disk-system.qcow2.lz4.md5");
119
- let url = `${this.downloadUrl}/disk-system.qcow2.lz4.md5`;
128
+ let savePath = path.join(this.diskDir, "disk-system.qcow2.md5");
129
+ let url = `${this.downloadUrl}/vm/disk-system.qcow2.md5`;
120
130
  await this.download(url, savePath);
121
131
  }
122
132
 
123
133
  async downloadSystemDisk() {
124
134
  let savePath = path.join(this.diskDir, "disk-system.qcow2");
125
- let url = `${this.downloadUrl}/disk-system.qcow2.lz4`;
135
+ let url = `${this.downloadUrl}/vm/disk-system.qcow2.gz`;
126
136
  await this.download(url, savePath, true);
127
137
  }
128
138
 
129
139
  async downloadPrivateKey() {
130
140
  let savePath = path.join(this.diskDir, "id_rsa");
131
- let url = `${this.downloadUrl}/id_rsa`;
141
+ let url = `${this.downloadUrl}/vm/id_rsa`;
132
142
  await this.download(url, savePath);
133
143
  fs.chmodSync(savePath, 0o400);
134
144
  return;
@@ -147,7 +157,7 @@ export class QemuResource {
147
157
  );
148
158
  }
149
159
 
150
- download(url, savePath, enableLz4 = false) {
160
+ download(url, savePath, enableGzip = false) {
151
161
  let tmpPath = savePath + ".tmp";
152
162
  const options = new URL(url);
153
163
  let request = url.startsWith("https") ? https.request : http.request;
@@ -167,8 +177,8 @@ export class QemuResource {
167
177
  });
168
178
 
169
179
  let outputFile = fs.createWriteStream(tmpPath, { flags: "w+" });
170
- if (enableLz4) {
171
- let decoder = lz4.createDecoderStream();
180
+ if (enableGzip) {
181
+ let decoder = zlib.createUnzip();
172
182
  res.pipe(decoder).pipe(outputFile);
173
183
  } else {
174
184
  res.pipe(outputFile);
@@ -206,7 +216,7 @@ export class QemuVM {
206
216
  p.on("error", (e) => {
207
217
  throw e;
208
218
  });
209
- console.log("启动中...");
219
+ logger.debug("启动中...");
210
220
 
211
221
  // 需要等待 boxid 的出现
212
222
  return new Promise((resolve, reject) => {
@@ -230,7 +240,7 @@ export class QemuVM {
230
240
  clearInterval(id);
231
241
  p.unref();
232
242
  resolve(boxId);
233
- console.log("启动成功!");
243
+ logger.debug("启动成功!");
234
244
  }
235
245
  }, 1000);
236
246
 
@@ -251,17 +261,17 @@ export class QemuVM {
251
261
  async runVM(name) {
252
262
  let boxid = this.readBoxid(name);
253
263
  if (!boxid) {
254
- console.log(`${name} 盒子不存在,请通过 lzc-cli box create 创建`);
264
+ logger.info(`${name} 盒子不存在,请通过 lzc-cli box create 创建`);
255
265
  return;
256
266
  }
257
267
  let vmDir = path.join(this.scheme.path, `vm-${name}`);
258
268
  let pid = parseVmPID(vmDir);
259
269
  if (pid) {
260
- console.log(`${name} 盒子已经启动`);
270
+ logger.info(`${name} 盒子已经启动`);
261
271
  return boxid;
262
272
  }
263
273
  await this.startVM(name, vmDir);
264
- console.log(`${name} 盒子启动成功!`);
274
+ logger.info(`${name} 盒子启动成功!`);
265
275
  }
266
276
 
267
277
  /**
@@ -272,7 +282,7 @@ export class QemuVM {
272
282
  this.ensureVolumeDir(vmDir);
273
283
  await this.buildDisks(vmDir);
274
284
  let boxId = await this.startVM(boxName, vmDir);
275
- console.log("盒子ID: ", boxId);
285
+ logger.info("盒子ID: ", boxId);
276
286
 
277
287
  await this.registerVM(boxId, {
278
288
  boxName,
@@ -363,7 +373,7 @@ export class QemuVM {
363
373
  }
364
374
 
365
375
  async buildSystemDisk(name, diskPath, diskInfo) {
366
- console.log(`构建系统盘快照:${diskPath}`);
376
+ logger.debug(`构建系统盘快照:${diskPath}`);
367
377
  let baseImage = path.join(this.scheme.path, name);
368
378
  return spawnSync(
369
379
  "qemu-img",
@@ -373,7 +383,7 @@ export class QemuVM {
373
383
  }
374
384
 
375
385
  async buildDataDisk(name, diskPath, diskInfo) {
376
- console.log(`构建数据盘:${diskPath}`);
386
+ logger.debug(`构建数据盘:${diskPath}`);
377
387
  return spawnSync(
378
388
  "qemu-img",
379
389
  ["create", "-f", "qcow2", diskPath, diskInfo.size],
@@ -385,7 +395,11 @@ export class QemuVM {
385
395
  * 寻找默认可用的 bios 文件
386
396
  */
387
397
  findBIOS() {
388
- let includes = ["/usr/share/ovmf/x64/OVMF.fd", "/usr/share/ovmf/OVMF.fd"];
398
+ let includes = [
399
+ "/usr/share/ovmf/x64/OVMF.fd",
400
+ "/usr/share/ovmf/OVMF.fd",
401
+ path.join(this.scheme.path, `${os.arch()}.OVMF.fd`),
402
+ ];
389
403
  for (let file of includes) {
390
404
  try {
391
405
  fs.accessSync(file, fs.constants.F_OK);
@@ -446,7 +460,7 @@ export class QemuVM {
446
460
  if (pid) {
447
461
  process.kill(pid);
448
462
  }
449
- console.log(`${name} 盒子已停止`);
463
+ logger.info(`${name} 盒子已停止`);
450
464
  }
451
465
 
452
466
  /**
@@ -474,7 +488,7 @@ export class QemuVM {
474
488
  fs.accessSync(vmDir);
475
489
  } catch {
476
490
  if (!silence) {
477
- console.log(`${name} 盒子不存在或者该盒子为一个真实盒子`);
491
+ logger.warn(`${name} 盒子不存在或者该盒子为一个真实盒子`);
478
492
  }
479
493
  return;
480
494
  }
@@ -495,7 +509,7 @@ export class QemuVM {
495
509
  return;
496
510
  }
497
511
  process.kill(pid);
498
- console.log(`${name} 盒子已停止`);
512
+ logger.info(`${name} 盒子已停止`);
499
513
  });
500
514
  }
501
515
 
package/lib/builder.js CHANGED
@@ -4,7 +4,7 @@ import path from "path";
4
4
  import chalk from "chalk";
5
5
  import execa from "execa";
6
6
  // import { archiveFolder } from "./utils.js";
7
- import { DockerClient } from "./sdk.js";
7
+ import { dockerPullLzcAppsImage, DockerClient } from "./sdk.js";
8
8
  import { DockerfileParser } from "dockerfile-ast";
9
9
  import glob from "fast-glob";
10
10
  import ignore from "@balena/dockerignore";
@@ -213,6 +213,17 @@ export default class Builder {
213
213
  console.log(host);
214
214
  const docker = await new DockerClient(host).init();
215
215
  try {
216
+ // TODO, 使用 docker.pull 方法会失败, 原因未知, 使用 workaround 方案
217
+ // docker.pull(
218
+ // "hello-world",
219
+ // // "registry.lazycat.cloud/lzc/lzcapp:0.1",
220
+ // // { authconfig: { auth: "bG5rczpsbmtzMjAyMA==" } },
221
+ // (err, stream) => {
222
+ // console.log("====", err, stream);
223
+ // }
224
+ // );
225
+ await dockerPullLzcAppsImage(host);
226
+
216
227
  return new Promise(async (resolve, reject) => {
217
228
  let refresher = new StatusRefresher();
218
229
 
package/lib/dev.js CHANGED
@@ -13,8 +13,8 @@ import path from "path";
13
13
  import env, { sdkEnv } from "./env.js";
14
14
  import _get from "lodash.get";
15
15
  import { execPreBuild } from "./builder.js";
16
- import Key from "./key.js";
17
16
  import chokidar from "chokidar";
17
+ import commandExists from "command-exists";
18
18
 
19
19
  import Archiver from "../lib/archiver.js";
20
20
 
@@ -138,14 +138,12 @@ class DevModule {
138
138
  keyFile,
139
139
  ].join(" ");
140
140
  // 检查rsync工具是否存在:提示用户
141
- try {
142
- execSync("rsync -V", {
143
- stdio: "ignore",
144
- });
145
- } catch {
141
+ const rsyncExisted = commandExists.sync('rsync')
142
+ if (!rsyncExisted) {
146
143
  console.log(chalk.red("请检查 rsync 是否安装,路径是否正确!"));
147
144
  process.exit();
148
145
  }
146
+ // FIXME: 下方执行命令不确定是否有兼容性问题
149
147
  execSync(
150
148
  `rsync --rsh='${rsh}' -czrPR . root@${appAddr}:/root/${this.appId} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --delete`,
151
149
  { stdio: ["ignore", "ignore", "inherit"] }
@@ -263,9 +261,6 @@ class DevModule {
263
261
  let appAddr;
264
262
 
265
263
  try {
266
- // 确保 sdk 能通过公钥正常访问
267
- await new Key().ensure(sdkEnv.sdkUrl);
268
-
269
264
  keyFile = path.join(this.tempDir, "debug/ssh/id_ed25519");
270
265
  appAddr = this.getAddress();
271
266
 
package/lib/env.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  import { debuglog } from "util";
13
13
  import inquirer from "inquirer";
14
14
  import chalk from "chalk";
15
- import fetch from "node-fetch";
15
+ import { request } from "./autologin.js";
16
16
  const debug = debuglog("lib/env");
17
17
 
18
18
  const GLOBAL_CONFIG_NAME = "box-config.json";
@@ -83,7 +83,7 @@ async function checkURL(url, suffix = "") {
83
83
  }, 5000);
84
84
 
85
85
  try {
86
- const resp = await fetch(url + suffix, { signal: controller.signal });
86
+ const resp = await request(url + suffix, { signal: controller.signal });
87
87
  if (resp.status != 200) {
88
88
  // 设备可以访问 (client 有正常连接), 但是sdk 这个服务无法访问
89
89
  throw new Error(
@@ -99,9 +99,6 @@ async function checkURL(url, suffix = "") {
99
99
  case "FetchError":
100
100
  console.log(chalk.red("盒子入口地址有误,请核对后再试"));
101
101
  process.exit();
102
- case "AbortError":
103
- console.log(chalk.red(`请求 ${url} 超时, 请确保懒猫云客户端已连接`));
104
- process.exit();
105
102
  default:
106
103
  throw error;
107
104
  }
@@ -246,6 +243,13 @@ class SDKEnv {
246
243
  return "";
247
244
  }
248
245
 
246
+ get sdkHostName() {
247
+ if (env.has("DEFAULT_BOXNAME")) {
248
+ return `sdk.${env.get("DEFAULT_BOXNAME")}.heiyu.space`;
249
+ }
250
+ return "";
251
+ }
252
+
249
253
  async ensure() {
250
254
  if (this.sdkUrl) {
251
255
  try {
package/lib/generator.js CHANGED
@@ -12,7 +12,7 @@ export const TemplateConfig = {
12
12
  ionic_vue3: {
13
13
  template: "ionic_vue3",
14
14
  defaultEnvs: {
15
- HTTP_SERVICE_PORT: "8080",
15
+ HTTP_SERVICE_PORT: "80",
16
16
  BUILD_CONTEXT: ".lazycat/release",
17
17
  },
18
18
  after: async function (name) {
@@ -43,7 +43,7 @@ export const TemplateConfig = {
43
43
  vue: {
44
44
  template: "vue",
45
45
  defaultEnvs: {
46
- HTTP_SERVICE_PORT: "8080",
46
+ HTTP_SERVICE_PORT: "80",
47
47
  BUILD_CONTEXT: ".lazycat/release",
48
48
  },
49
49
  after: async function (name) {
package/lib/sdk.js CHANGED
@@ -4,8 +4,8 @@ import Docker from "dockerode";
4
4
  import process from "process";
5
5
  import fs from "fs";
6
6
  import { Client } from "ssh2";
7
- import chalk from "chalk";
8
7
  import Key from "./key.js";
8
+ import logger from "loglevel";
9
9
 
10
10
  export async function connectOptions(host) {
11
11
  const pairs = await new Key().getKeyPair();
@@ -38,7 +38,7 @@ export class DockerClient {
38
38
  async init() {
39
39
  this.docker = !!this.host
40
40
  ? new Docker({
41
- protocal: "http",
41
+ protocal: "ssh",
42
42
  agent: ssh(await connectOptions(this.host)),
43
43
  })
44
44
  : new Docker();
@@ -47,9 +47,7 @@ export class DockerClient {
47
47
 
48
48
  async function onUncaughtException(e) {
49
49
  if (e.code == "ECONNREFUSED") {
50
- console.log(
51
- chalk.red(`无法连接 sdk 服务, 请确保 ${chalk.yellow(host)} 可以访问`)
52
- );
50
+ logger.error(`无法连接 sdk 服务, 请确保 ${host} 可以访问`);
53
51
  process.exit(1);
54
52
  } else {
55
53
  throw e;
@@ -78,6 +76,7 @@ export class SSHClient {
78
76
  this.con
79
77
  .on("ready", () => resolve(this.con))
80
78
  .on("error", (err) => {
79
+ logger.error("ssh client ", err);
81
80
  reject(err);
82
81
  })
83
82
  .on("close", () => resolve(null))
@@ -124,3 +123,28 @@ export class SSHClient {
124
123
  );
125
124
  }
126
125
  }
126
+
127
+ export async function dockerPullLzcAppsImage(host) {
128
+ logger.warn("* 更新lzcapp镜像, 未来可能会移除此操作");
129
+ const opts = await connectOptions(host);
130
+ const client = new SSHClient(opts);
131
+ await client.connect();
132
+ try {
133
+ const stream = await client.exec(
134
+ "docker pull registry.lazycat.cloud/lzc/lzcapp:0.1"
135
+ );
136
+
137
+ return new Promise((resolve, reject) => {
138
+ stream.stdout.pipe(process.stdout);
139
+ stream.stderr.pipe(process.stdout);
140
+ stream.on("close", () => {
141
+ client.close();
142
+ resolve();
143
+ });
144
+ stream.on("error", (e) => reject(e));
145
+ });
146
+ } catch (e) {
147
+ client.close();
148
+ throw e;
149
+ }
150
+ }