@lazycatcloud/lzc-cli 1.1.5 → 1.1.6

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 (51) hide show
  1. package/cmds/app.js +1 -5
  2. package/cmds/dev.js +11 -5
  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 +7 -6
  14. package/lib/box/qemu_vm_mgr.js +12 -11
  15. package/lib/dev.js +0 -4
  16. package/lib/env.js +9 -5
  17. package/lib/generator.js +2 -2
  18. package/lib/sdk.js +5 -6
  19. package/lib/utils.js +69 -46
  20. package/package.json +7 -1
  21. package/scripts/cli.js +91 -12
  22. package/template/_lazycat/debug/shell/Dockerfile +5 -3
  23. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +2 -12
  24. package/template/_lazycat/debug/shell/entrypoint.sh +3 -1
  25. package/template/_lpk/Dockerfile.in +8 -0
  26. package/template/_lpk/devshell/Dockerfile +18 -0
  27. package/template/_lpk/devshell/build.sh +5 -0
  28. package/template/_lpk/devshell/entrypoint.sh +8 -0
  29. package/template/_lpk/devshell/sshd_config +117 -0
  30. package/template/_lpk/manifest.yml.in +17 -0
  31. package/template/_lpk/sync/Dockerfile +16 -0
  32. package/template/_lpk/sync/build.sh +5 -0
  33. package/template/_lpk/sync/entrypoint.sh +8 -0
  34. package/template/_lpk/sync/sshd_config +117 -0
  35. package/template/_lpk/sync.manifest.yml.in +3 -0
  36. package/template/golang/build.sh +6 -0
  37. package/template/golang/lzc-build.yml +52 -0
  38. package/template/ionic_vue3/lzc-build.yml +53 -0
  39. package/template/ionic_vue3/vite.config.ts +1 -1
  40. package/template/release/golang/build.sh +1 -1
  41. package/template/release/ionic_vue3/Dockerfile +1 -1
  42. package/template/release/ionic_vue3/build.sh +1 -3
  43. package/template/release/ionic_vue3/docker-compose.yml.in +0 -5
  44. package/template/release/vue/Dockerfile +1 -1
  45. package/template/release/vue/build.sh +2 -3
  46. package/template/release/vue/docker-compose.yml.in +0 -5
  47. package/template/vue/lzc-build.yml +53 -0
  48. package/template/vue/src/main.js +3 -14
  49. package/template/vue/vue.config.js +2 -1
  50. package/template/_lazycat/debug/shell/nginx.conf.template +0 -64
  51. 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(
@@ -60,7 +61,7 @@ export function boxCommand(box) {
60
61
  // 创建盒子阶段,如果出错,将直接删除所有的资源。
61
62
  boxId = await m.createVM(answer);
62
63
  } catch (error) {
63
- console.error(error);
64
+ logger.error(error);
64
65
  await m.cleanVM(answer.boxName);
65
66
  return;
66
67
  }
@@ -74,11 +75,11 @@ export function boxCommand(box) {
74
75
  }
75
76
 
76
77
  let h = await initHportal();
77
- console.log("添加盒子到 Hportal ......");
78
+ logger.debug("添加盒子到 Hportal ......");
78
79
  await h.addBox(answer.boxName);
79
- console.log("正在使用管理员帐号密码登录中......");
80
+ logger.debug("正在使用管理员帐号密码登录中......");
80
81
  await h.loginBox(boxId, answer.adminName, answer.adminPass);
81
- console.log("登录成功!");
82
+ logger.debug("登录成功!");
82
83
  },
83
84
  },
84
85
  {
@@ -88,7 +89,7 @@ export function boxCommand(box) {
88
89
  let m = await initQemuVM();
89
90
  let boxid = await m.runVM(boxName);
90
91
  if (boxid) {
91
- console.log("盒子ID: ", boxid);
92
+ logger.info("盒子ID: ", boxid);
92
93
  }
93
94
  },
94
95
  },
@@ -127,7 +128,7 @@ export function boxCommand(box) {
127
128
 
128
129
  // 过滤条件不满足
129
130
  if (vmInfos.length == 0 && rmInfos == 0 && boxName) {
130
- console.log(`${boxName} 盒子不存在`);
131
+ logger.info(`${boxName} 盒子不存在`);
131
132
  let allVmInfos = await m.infoVM("", defaultBoxName);
132
133
  let allRmInfos = await rm.boxs("", defaultBoxName);
133
134
  showBoxInfo(allVmInfos, allRmInfos);
@@ -10,6 +10,7 @@ import net from "node:net";
10
10
  import fetch from "node-fetch";
11
11
  import zlib from "node:zlib";
12
12
  import os from "node:os";
13
+ import logger from "loglevel";
13
14
 
14
15
  async function getFreePort() {
15
16
  return new Promise((resolve, reject) => {
@@ -215,7 +216,7 @@ export class QemuVM {
215
216
  p.on("error", (e) => {
216
217
  throw e;
217
218
  });
218
- console.log("启动中...");
219
+ logger.debug("启动中...");
219
220
 
220
221
  // 需要等待 boxid 的出现
221
222
  return new Promise((resolve, reject) => {
@@ -239,7 +240,7 @@ export class QemuVM {
239
240
  clearInterval(id);
240
241
  p.unref();
241
242
  resolve(boxId);
242
- console.log("启动成功!");
243
+ logger.debug("启动成功!");
243
244
  }
244
245
  }, 1000);
245
246
 
@@ -260,17 +261,17 @@ export class QemuVM {
260
261
  async runVM(name) {
261
262
  let boxid = this.readBoxid(name);
262
263
  if (!boxid) {
263
- console.log(`${name} 盒子不存在,请通过 lzc-cli box create 创建`);
264
+ logger.info(`${name} 盒子不存在,请通过 lzc-cli box create 创建`);
264
265
  return;
265
266
  }
266
267
  let vmDir = path.join(this.scheme.path, `vm-${name}`);
267
268
  let pid = parseVmPID(vmDir);
268
269
  if (pid) {
269
- console.log(`${name} 盒子已经启动`);
270
+ logger.info(`${name} 盒子已经启动`);
270
271
  return boxid;
271
272
  }
272
273
  await this.startVM(name, vmDir);
273
- console.log(`${name} 盒子启动成功!`);
274
+ logger.info(`${name} 盒子启动成功!`);
274
275
  }
275
276
 
276
277
  /**
@@ -281,7 +282,7 @@ export class QemuVM {
281
282
  this.ensureVolumeDir(vmDir);
282
283
  await this.buildDisks(vmDir);
283
284
  let boxId = await this.startVM(boxName, vmDir);
284
- console.log("盒子ID: ", boxId);
285
+ logger.info("盒子ID: ", boxId);
285
286
 
286
287
  await this.registerVM(boxId, {
287
288
  boxName,
@@ -372,7 +373,7 @@ export class QemuVM {
372
373
  }
373
374
 
374
375
  async buildSystemDisk(name, diskPath, diskInfo) {
375
- console.log(`构建系统盘快照:${diskPath}`);
376
+ logger.debug(`构建系统盘快照:${diskPath}`);
376
377
  let baseImage = path.join(this.scheme.path, name);
377
378
  return spawnSync(
378
379
  "qemu-img",
@@ -382,7 +383,7 @@ export class QemuVM {
382
383
  }
383
384
 
384
385
  async buildDataDisk(name, diskPath, diskInfo) {
385
- console.log(`构建数据盘:${diskPath}`);
386
+ logger.debug(`构建数据盘:${diskPath}`);
386
387
  return spawnSync(
387
388
  "qemu-img",
388
389
  ["create", "-f", "qcow2", diskPath, diskInfo.size],
@@ -459,7 +460,7 @@ export class QemuVM {
459
460
  if (pid) {
460
461
  process.kill(pid);
461
462
  }
462
- console.log(`${name} 盒子已停止`);
463
+ logger.info(`${name} 盒子已停止`);
463
464
  }
464
465
 
465
466
  /**
@@ -487,7 +488,7 @@ export class QemuVM {
487
488
  fs.accessSync(vmDir);
488
489
  } catch {
489
490
  if (!silence) {
490
- console.log(`${name} 盒子不存在或者该盒子为一个真实盒子`);
491
+ logger.warn(`${name} 盒子不存在或者该盒子为一个真实盒子`);
491
492
  }
492
493
  return;
493
494
  }
@@ -508,7 +509,7 @@ export class QemuVM {
508
509
  return;
509
510
  }
510
511
  process.kill(pid);
511
- console.log(`${name} 盒子已停止`);
512
+ logger.info(`${name} 盒子已停止`);
512
513
  });
513
514
  }
514
515
 
package/lib/dev.js CHANGED
@@ -13,7 +13,6 @@ 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";
18
17
  import commandExists from "command-exists";
19
18
 
@@ -262,9 +261,6 @@ class DevModule {
262
261
  let appAddr;
263
262
 
264
263
  try {
265
- // 确保 sdk 能通过公钥正常访问
266
- await new Key().ensure(sdkEnv.sdkUrl);
267
-
268
264
  keyFile = path.join(this.tempDir, "debug/ssh/id_ed25519");
269
265
  appAddr = this.getAddress();
270
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))
@@ -126,7 +125,7 @@ export class SSHClient {
126
125
  }
127
126
 
128
127
  export async function dockerPullLzcAppsImage(host) {
129
- console.log(chalk.yellow("* 更新lzcapp镜像, 未来可能会移除此操作"));
128
+ logger.warn("* 更新lzcapp镜像, 未来可能会移除此操作");
130
129
  const opts = await connectOptions(host);
131
130
  const client = new SSHClient(opts);
132
131
  await client.connect();