@lazycatcloud/lzc-cli 1.1.8 → 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.
Files changed (105) hide show
  1. package/README.md +69 -11
  2. package/lib/api.js +71 -39
  3. package/lib/app/index.js +76 -21
  4. package/lib/app/lpk_build.js +95 -63
  5. package/lib/app/lpk_create.js +63 -41
  6. package/lib/app/lpk_create_generator.js +202 -0
  7. package/lib/app/lpk_devshell.js +393 -328
  8. package/lib/app/lpk_devshell_docker.js +211 -0
  9. package/lib/app/lpk_installer.js +63 -26
  10. package/lib/app/lpk_log.js +68 -0
  11. package/lib/app/lpk_status.js +18 -0
  12. package/lib/app/lpk_uninstall.js +19 -0
  13. package/lib/appstore/index.js +37 -0
  14. package/lib/appstore/login.js +96 -93
  15. package/lib/appstore/publish.js +62 -0
  16. package/lib/autologin.js +0 -78
  17. package/lib/box/api/clientapi.js +1322 -0
  18. package/lib/box/api/empty.js +35 -0
  19. package/lib/box/check_qemu.js +1 -0
  20. package/lib/box/index.js +41 -94
  21. package/lib/box/qemu_vm_mgr.js +208 -239
  22. package/lib/box/schemes/vm_box_system_debian.json +1 -1
  23. package/lib/docker-compose.js +1 -2
  24. package/lib/env.js +19 -101
  25. package/lib/key.js +1 -0
  26. package/lib/sdk.js +10 -25
  27. package/lib/utils.js +156 -132
  28. package/package.json +19 -10
  29. package/scripts/cli.js +14 -135
  30. package/template/_lpk/README.md +31 -0
  31. package/template/_lpk/exec.sh +19 -0
  32. package/template/_lpk/golang.manifest.yml.in +16 -0
  33. package/template/_lpk/lazycat.png +0 -0
  34. package/template/_lpk/lite.manifest.yml.in +19 -0
  35. package/template/_lpk/local_devshell/Dockerfile +16 -0
  36. package/template/_lpk/local_devshell/build.sh +5 -0
  37. package/template/_lpk/local_devshell/entrypoint.sh +87 -0
  38. package/template/{_lazycat/debug/shell → _lpk/local_devshell}/sshd_config +8 -8
  39. package/template/_lpk/manifest.yml.in +0 -1
  40. package/template/{vue/lzc-build.yml → _lpk/vue.lzc-build.yml.in} +9 -1
  41. package/template/golang/README.md +0 -2
  42. package/template/golang/_gitignore +2 -0
  43. package/template/golang/build.sh +6 -0
  44. package/template/golang/lazycat.png +0 -0
  45. package/template/golang/lzc-build.yml +9 -1
  46. package/template/golang/rego.go +15 -16
  47. package/template/golang/rego_test.go +13 -0
  48. package/template/ionic_vue3/lazycat.png +0 -0
  49. package/template/ionic_vue3/lzc-build.yml +9 -1
  50. package/template/lite/error_pages/502.html.tpl +13 -0
  51. package/template/lite/lazycat.png +0 -0
  52. package/template/lite/lzc-build.yml +60 -0
  53. package/cmds/app.js +0 -133
  54. package/cmds/config.js +0 -55
  55. package/cmds/create.js +0 -55
  56. package/cmds/dev.js +0 -130
  57. package/cmds/init.js +0 -125
  58. package/cmds/log.js +0 -103
  59. package/cmds/publish.js +0 -116
  60. package/lib/archiver.js +0 -105
  61. package/lib/box/hportal.js +0 -120
  62. package/lib/builder.js +0 -313
  63. package/lib/dev.js +0 -314
  64. package/lib/generator.js +0 -146
  65. package/template/_lazycat/_gitignore +0 -1
  66. package/template/_lazycat/app-config +0 -1
  67. package/template/_lazycat/debug/devforward/50x.html +0 -30
  68. package/template/_lazycat/debug/devforward/Dockerfile +0 -16
  69. package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +0 -11
  70. package/template/_lazycat/debug/devforward/entrypoint.sh +0 -10
  71. package/template/_lazycat/debug/devforward/nginx.conf.template +0 -56
  72. package/template/_lazycat/debug/devforward/sshd_config +0 -116
  73. package/template/_lazycat/debug/shell/50x.html +0 -32
  74. package/template/_lazycat/debug/shell/Dockerfile +0 -18
  75. package/template/_lazycat/debug/shell/build.sh +0 -15
  76. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +0 -13
  77. package/template/_lazycat/debug/shell/entrypoint.sh +0 -12
  78. package/template/_lazycat/docker-compose.yml.in +0 -15
  79. package/template/_lazycat/icon.svg +0 -1
  80. package/template/_lazycat/screenshot.png +0 -0
  81. package/template/_lpk/sync/Dockerfile +0 -16
  82. package/template/_lpk/sync/build.sh +0 -5
  83. package/template/_lpk/sync/entrypoint.sh +0 -8
  84. package/template/_lpk/sync/sshd_config +0 -117
  85. package/template/_lpk/sync.manifest.yml.in +0 -3
  86. package/template/release/golang/Dockerfile +0 -18
  87. package/template/release/golang/build.sh +0 -13
  88. package/template/release/ionic_vue3/Dockerfile +0 -10
  89. package/template/release/ionic_vue3/build.sh +0 -7
  90. package/template/release/ionic_vue3/docker-compose.yml.in +0 -3
  91. package/template/release/vue/Dockerfile +0 -10
  92. package/template/release/vue/build.sh +0 -10
  93. package/template/release/vue/docker-compose.yml.in +0 -3
  94. package/template/vue/README.md +0 -29
  95. package/template/vue/_dockerignore +0 -1
  96. package/template/vue/babel.config.js +0 -3
  97. package/template/vue/package.json +0 -43
  98. package/template/vue/public/favicon.ico +0 -0
  99. package/template/vue/public/index.html +0 -33
  100. package/template/vue/src/App.vue +0 -39
  101. package/template/vue/src/main.js +0 -8
  102. package/template/vue/src/todo.vue +0 -640
  103. package/template/vue/src/top-bar.vue +0 -100
  104. package/template/vue/src/webdav.vue +0 -183
  105. package/template/vue/vue.config.js +0 -21
@@ -3,14 +3,36 @@ import fs from "node:fs";
3
3
  import { spawn, spawnSync } from "node:child_process";
4
4
  import path from "node:path";
5
5
  import inquirer from "inquirer";
6
- import https from "node:https";
7
- import http from "node:http";
8
6
  import process from "node:process";
9
7
  import net from "node:net";
10
8
  import fetch from "node-fetch";
11
- import zlib from "node:zlib";
12
9
  import os from "node:os";
13
10
  import logger from "loglevel";
11
+ import glob from "fast-glob";
12
+ import env from "../env.js";
13
+
14
+ import { Downloader, envsubstrDefault, contextDirname } from "../utils.js";
15
+ import { ShellCoreClientImpl, GrpcWebImpl } from "./api/clientapi.js";
16
+
17
+ const cc = new ShellCoreClientImpl(
18
+ new GrpcWebImpl("http://127.0.0.1:30553", {})
19
+ );
20
+
21
+ const remoteBox = "远程盒子";
22
+ const unRegistry = "未注册";
23
+
24
+ function getBoxStatus(status) {
25
+ switch (status) {
26
+ case 0:
27
+ return "未激活";
28
+ case 1:
29
+ return "连接中";
30
+ case 2:
31
+ return "已连接";
32
+ case 3:
33
+ return "无法连接";
34
+ }
35
+ }
14
36
 
15
37
  async function getFreePort() {
16
38
  return new Promise((resolve, reject) => {
@@ -34,9 +56,18 @@ function parseVmPID(vmDir) {
34
56
  }
35
57
  }
36
58
 
59
+ function isDefault(boxName) {
60
+ const defaultBoxName = env.get("DEFAULT_BOXNAME");
61
+ return boxName == defaultBoxName ? "yes" : "no";
62
+ }
63
+
64
+ function isRunning(vmname) {
65
+ return parseVmPID(vmname) ? "running" : "stop";
66
+ }
67
+
37
68
  export class QemuResource {
38
69
  /**
39
- * diskDir qemu 磁盘存储路径,默认 ~/.local/share/lazycat/box-emulator
70
+ * diskDir qemu 磁盘存储路径,默认 ~/.cache/box-emulator
40
71
  * downloadUrl system-disk 下载路径
41
72
  */
42
73
  constructor(
@@ -44,13 +75,26 @@ export class QemuResource {
44
75
  downloadUrl = "https://dl.lazycat.cloud/sdk"
45
76
  ) {
46
77
  this.diskDir = diskDir;
47
- this.downloadUrl = downloadUrl;
78
+
79
+ this.downloadUrlRoot = downloadUrl;
80
+ this.downloadUrl = undefined;
81
+
82
+ this.downloader = new Downloader();
48
83
  }
49
84
 
50
85
  async init() {
86
+ // fetch 最新的稳定版
87
+ const res = await fetch(`${this.downloadUrlRoot}/vm/index.json`);
88
+ if (res.status !== 200) {
89
+ throw "获取最新的版本信息出错";
90
+ }
91
+ const body = await res.json();
92
+ const version = body["stable-latest"];
93
+ this.downloadUrl = `${this.downloadUrlRoot}/vm/${version}`;
94
+
51
95
  fs.mkdirSync(this.diskDir, { recursive: true });
52
- let shouldUpdate = await this.shouldUpdate();
53
- if (shouldUpdate) {
96
+ let { update, force } = await this.shouldUpdate();
97
+ if (!force && update) {
54
98
  let answer = await inquirer.prompt([
55
99
  {
56
100
  type: "confirm",
@@ -60,10 +104,11 @@ export class QemuResource {
60
104
  },
61
105
  ]);
62
106
  if (!answer.yesorno) {
63
- shouldUpdate = false;
107
+ update = false;
64
108
  }
65
109
  }
66
- await this.ensureSystemDisk(shouldUpdate);
110
+ await this.ensureSystemDisk(update);
111
+ return this;
67
112
  }
68
113
 
69
114
  /**
@@ -79,24 +124,24 @@ export class QemuResource {
79
124
  try {
80
125
  fs.accessSync(oldMd5Path);
81
126
  } catch {
82
- return true;
127
+ return { update: true, force: true };
83
128
  }
84
129
 
85
130
  // 拉取最新的 md5
86
131
  let newMd5;
87
132
  try {
88
- let url = `${this.downloadUrl}/vm/disk-system.qcow2.md5`;
133
+ let url = `${this.downloadUrl}/disk-system.qcow2.md5`;
89
134
  let res = await fetch(url);
90
135
  if (res.status !== 200) {
91
- return false;
136
+ return { update: false };
92
137
  }
93
138
  newMd5 = await res.text();
94
139
  } catch {
95
- return false;
140
+ return { update: false };
96
141
  }
97
142
 
98
143
  let oldMd5 = fs.readFileSync(oldMd5Path, { encoding: "utf-8" });
99
- return oldMd5 !== newMd5;
144
+ return { update: oldMd5 !== newMd5 };
100
145
  }
101
146
 
102
147
  /**
@@ -120,95 +165,64 @@ export class QemuResource {
120
165
  async downloadOVMF() {
121
166
  let name = `${os.arch()}.OVMF.fd`;
122
167
  let savePath = path.join(this.diskDir, name);
123
- let url = `${this.downloadUrl}/${name}`;
124
- await this.download(url, savePath);
168
+ let url = `${this.downloadUrlRoot}/${name}`;
169
+ await this.downloader.download(url, savePath);
125
170
  }
126
171
 
127
172
  async downloadSystemDiskMd5() {
128
173
  let savePath = path.join(this.diskDir, "disk-system.qcow2.md5");
129
- let url = `${this.downloadUrl}/vm/disk-system.qcow2.md5`;
130
- await this.download(url, savePath);
174
+ let url = `${this.downloadUrl}/disk-system.qcow2.md5`;
175
+ await this.downloader.download(url, savePath);
131
176
  }
132
177
 
133
178
  async downloadSystemDisk() {
134
179
  let savePath = path.join(this.diskDir, "disk-system.qcow2");
135
- let url = `${this.downloadUrl}/vm/disk-system.qcow2.gz`;
136
- await this.download(url, savePath, true);
180
+ let url = `${this.downloadUrl}/disk-system.qcow2.gz`;
181
+ await this.downloader.download(url, savePath, true);
137
182
  }
138
183
 
139
184
  async downloadPrivateKey() {
140
185
  let savePath = path.join(this.diskDir, "id_rsa");
141
- let url = `${this.downloadUrl}/vm/id_rsa`;
142
- await this.download(url, savePath);
186
+ let url = `${this.downloadUrl}/id_rsa`;
187
+ await this.downloader.download(url, savePath);
143
188
  fs.chmodSync(savePath, 0o400);
144
189
  return;
145
190
  }
146
-
147
- showDownloadingProgress(received, total) {
148
- var percentage = ((received * 100) / total).toFixed(2);
149
- process.stdout.write("\r");
150
- process.stdout.write(
151
- percentage +
152
- "% | " +
153
- received +
154
- " bytes downloaded out of " +
155
- total +
156
- " bytes."
157
- );
158
- }
159
-
160
- download(url, savePath, enableGzip = false) {
161
- let tmpPath = savePath + ".tmp";
162
- const options = new URL(url);
163
- let request = url.startsWith("https") ? https.request : http.request;
164
-
165
- return new Promise((resolve, reject) => {
166
- const req = request(options, (res) => {
167
- if (res.statusCode != 200) {
168
- reject(`下载 ${url} 失败`);
169
- return;
170
- }
171
-
172
- let total = parseInt(res.headers["content-length"]);
173
- let recive = 0;
174
- res.on("data", (chunk) => {
175
- recive += chunk.length;
176
- this.showDownloadingProgress(recive, total);
177
- });
178
-
179
- let outputFile = fs.createWriteStream(tmpPath, { flags: "w+" });
180
- if (enableGzip) {
181
- let decoder = zlib.createUnzip();
182
- res.pipe(decoder).pipe(outputFile);
183
- } else {
184
- res.pipe(outputFile);
185
- }
186
-
187
- outputFile.on("error", reject);
188
- outputFile.on("finish", () => {
189
- fs.renameSync(tmpPath, savePath);
190
- resolve();
191
- });
192
- });
193
- req.on("error", reject);
194
- req.end();
195
- });
196
- }
197
191
  }
198
192
 
199
193
  export class QemuVM {
200
194
  /**
201
195
  * @param {Object} scheme - 各个操作系统的配置文件.
202
196
  */
203
- constructor(scheme) {
197
+ constructor() {
198
+ this.scheme = undefined;
199
+ }
200
+
201
+ async init() {
202
+ let defaultSchemeFile = path.resolve(
203
+ contextDirname(import.meta.url),
204
+ "schemes",
205
+ "vm_box_system_debian.json"
206
+ );
207
+ const content = await envsubstrDefault(
208
+ fs.readFileSync(defaultSchemeFile, "utf8"),
209
+ process.env
210
+ );
211
+ let scheme = JSON.parse(content);
212
+ if (scheme.path && !scheme.path.startsWith("/")) {
213
+ scheme.path = path.resolve(scheme.path);
214
+ }
215
+ fs.mkdirSync(scheme.path, { recursive: true });
216
+
204
217
  this.scheme = scheme;
218
+ return this;
205
219
  }
206
220
 
207
221
  /**
208
222
  * 运行虚拟盒子,并等待 boxid 的出现,确保盒子启动正常
209
223
  */
210
- async startVM(name, vmDir) {
211
- let args = await this.buildQemuArgs(name, vmDir);
224
+ async startVM(vmname) {
225
+ let args = await this.buildQemuArgs(vmname);
212
226
  let p = spawn("qemu-system-x86_64", args, {
213
227
  detached: true,
214
228
  stdio: ["pipe", "ignore", "inherit"],
@@ -216,7 +230,7 @@ export class QemuVM {
216
230
  p.on("error", (e) => {
217
231
  throw e;
218
232
  });
219
- logger.debug("启动中...");
233
+ logger.info("启动中...");
220
234
 
221
235
  // 需要等待 boxid 的出现
222
236
  return new Promise((resolve, reject) => {
@@ -235,7 +249,7 @@ export class QemuVM {
235
249
  return;
236
250
  }
237
251
 
238
- let boxId = this.readBoxid(name);
252
+ let boxId = this.readBoxid(vmname);
239
253
  if (boxId) {
240
254
  clearInterval(id);
241
255
  p.unref();
@@ -258,88 +272,37 @@ export class QemuVM {
258
272
  * @param {string} name - 盒子的名称
259
273
  * @return {string} id - 盒子的id
260
274
  **/
261
- async runVM(name) {
262
- let boxid = this.readBoxid(name);
263
- if (!boxid) {
264
- logger.info(`${name} 盒子不存在,请通过 lzc-cli box create 创建`);
275
+ async runVM(nameOrBoxId) {
276
+ const { vmname, name, id } = await this.findVmname(nameOrBoxId);
277
+ if (!vmname) {
278
+ logger.info(`${name || id} 盒子不存在,请通过 lzc-cli box create 创建`);
265
279
  return;
266
280
  }
267
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
268
- let pid = parseVmPID(vmDir);
281
+ let pid = parseVmPID(vmname);
269
282
  if (pid) {
270
- logger.info(`${name} 盒子已经启动`);
271
- return boxid;
283
+ return id;
272
284
  }
273
- await this.startVM(name, vmDir);
274
- logger.info(`${name} 盒子启动成功!`);
285
+ await this.startVM(vmname);
286
+ logger.info(`${name || id} 盒子启动成功!`);
275
287
  }
276
288
 
277
289
  /**
278
290
  * 创建一个指定的虚拟机,并启动等待注册成功。
279
291
  **/
280
- async createVM({ boxName, adminName, adminPass }) {
281
- let vmDir = this.ensureVmDir(boxName);
292
+ async createVM(resourcer, name) {
293
+ const vmDir = this.ensureVmDir(name);
282
294
  this.ensureVolumeDir(vmDir);
283
295
  await this.buildDisks(vmDir);
284
- let boxId = await this.startVM(boxName, vmDir);
285
- logger.info("盒子ID: ", boxId);
286
296
 
287
- await this.registerVM(boxId, {
288
- boxName,
289
- adminName,
290
- adminPass,
291
- });
297
+ let boxId = await this.startVM(path.basename(vmDir));
292
298
  return boxId;
293
299
  }
294
300
 
295
- /**
296
- * 请求用户输入盒子的基本信息
297
- **/
298
- async askBoxInfo() {
299
- const noEmpty = (value) => value != "";
300
-
301
- return inquirer.prompt([
302
- {
303
- type: "input",
304
- name: "boxName",
305
- message: "请输入盒子名称:",
306
- validate: noEmpty,
307
- },
308
- {
309
- type: "input",
310
- name: "adminName",
311
- message: "请输入盒子管理员名称:",
312
- default: "admin",
313
- },
314
- {
315
- type: "password",
316
- name: "adminPass",
317
- message: "请输入盒子管理员密码:",
318
- mask: "*",
319
- validate: noEmpty,
320
- },
321
- ]);
322
- }
323
-
324
- /**
325
- * 调用 hportal client 进行注册
326
- **/
327
- async registerVM(boxid, { boxName, adminName, adminPass }) {
328
- // prettier-ignore
329
- spawnSync("home-portal-client",[
330
- "-setup", "-boxid", boxid,
331
- "-boxname", boxName,
332
- "-user", adminName,
333
- "-password", adminPass,
334
- ], {stdio: "inherit"})
335
- return boxName;
336
- }
337
-
338
301
  /**
339
302
  * 根据虚拟机名称确保缓存文件夹存在,返回对应的虚拟机缓存文件夹
340
303
  */
341
304
  ensureVmDir(name) {
342
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
305
+ const vmDir = path.resolve(this.scheme.path, `vm-${name}`);
343
306
  fs.mkdirSync(vmDir, { recursive: true });
344
307
  return vmDir;
345
308
  }
@@ -413,12 +376,13 @@ export class QemuVM {
413
376
  /**
414
377
  * 构建 qemu 启动的参数
415
378
  */
416
- async buildQemuArgs(name, vmDir) {
379
+ async buildQemuArgs(vmname) {
380
+ const vmDir = path.resolve(this.scheme.path, vmname);
417
381
  let sshPort = await getFreePort();
418
382
  let bios = this.findBIOS();
419
383
  // prettier-ignore
420
384
  let args = [
421
- "-name", `${this.scheme.name}-${this.scheme.uuid}-vm-${name}`,
385
+ "-name", `${this.scheme.name}-${this.scheme.uuid}-${vmname}`,
422
386
  "-machine", "pc,accel=kvm,accel=kvf,accel=xen,accel=hax,accel=nvmm,accel=whpx,accel=tcg",
423
387
  "-m", `${this.scheme.memory}`,
424
388
  "-smp", "4,sockets=4,cores=1,threads=1",
@@ -454,21 +418,21 @@ export class QemuVM {
454
418
  /**
455
419
  * 停止虚拟盒子
456
420
  */
457
- async stopVM(name) {
458
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
459
- let pid = parseVmPID(vmDir);
421
+ async stopVM(nameOrBoxId) {
422
+ const { vmname, name, id } = await this.findVmname(nameOrBoxId);
423
+ let pid = parseVmPID(vmname);
460
424
  if (pid) {
461
425
  process.kill(pid);
462
426
  }
463
- logger.info(`${name} 盒子已停止`);
427
+ logger.info(`${name || id} 盒子已停止`);
464
428
  }
465
429
 
466
430
  /**
467
431
  * 清理盒子,用于注册失败或者在注册过程中用户手动中止的情况
468
432
  */
469
- async cleanVM(name) {
470
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
471
-
433
+ async cleanVM(nameOrBoxId) {
434
+ const { vmname } = await this.findVmname(nameOrBoxId);
435
+ const vmDir = path.resolve(this.scheme.path, vmname);
472
436
  try {
473
437
  fs.accessSync(vmDir);
474
438
  } catch {
@@ -477,22 +441,23 @@ export class QemuVM {
477
441
  fs.rmSync(vmDir, { recursive: true, force: true });
478
442
  }
479
443
 
480
- /**
481
- * 删除虚拟盒子
482
- * @param {boolean} silence - 是否提示错误,如果被删除的盒子不存在
483
- */
484
- async deleteVM(name, silence = false) {
485
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
444
+ async deleteVM(nameOrBoxId) {
445
+ const boxes = await this.listAllVM();
446
+ const box = boxes.find((b) => b.id == nameOrBoxId || b.name == nameOrBoxId);
447
+ if (!box) {
448
+ logger.info(`${nameOrBoxId} 盒子不存在`);
449
+ return;
450
+ }
451
+ if (box.status !== unRegistry) {
452
+ await cc.DelBox({ id: box.id });
453
+ }
486
454
 
487
- try {
488
- fs.accessSync(vmDir);
489
- } catch {
490
- if (!silence) {
491
- logger.warn(`${name} 盒子不存在或者该盒子为一个真实盒子`);
492
- }
455
+ if (box["vm status"] === remoteBox) {
493
456
  return;
494
457
  }
495
458
 
459
+ const { vmname, name, id } = box;
460
+ const vmDir = path.resolve(this.scheme.path, vmname);
496
461
  let pid = parseVmPID(vmDir);
497
462
  if (pid) {
498
463
  await inquirer
@@ -500,7 +465,7 @@ export class QemuVM {
500
465
  {
501
466
  type: "confirm",
502
467
  name: "yesorno",
503
- message: `${name} 正在运行中,是否停止?`,
468
+ message: `${name || id} 正在运行中,是否停止?`,
504
469
  default: false,
505
470
  },
506
471
  ])
@@ -509,7 +474,7 @@ export class QemuVM {
509
474
  return;
510
475
  }
511
476
  process.kill(pid);
512
- logger.info(`${name} 盒子已停止`);
477
+ logger.info(`${name || id} 盒子已停止`);
513
478
  });
514
479
  }
515
480
 
@@ -518,7 +483,7 @@ export class QemuVM {
518
483
  {
519
484
  type: "confirm",
520
485
  name: "yesorno",
521
- message: `确定要删除 ${name} 盒子吗?`,
486
+ message: `确定要删除 ${name || id} 盒子吗?`,
522
487
  default: false,
523
488
  },
524
489
  ])
@@ -530,101 +495,67 @@ export class QemuVM {
530
495
  });
531
496
  }
532
497
 
533
- /**
534
- * 从缓存路径中列出指定盒子信息
535
- */
536
- sigleVM(name, defaultBoxName = "") {
537
- let volumeDir = path.join(this.scheme.path, `vm-${name}`, `volume-config`);
538
-
539
- try {
540
- fs.accessSync(volumeDir);
541
- } catch {
542
- return;
543
- }
544
-
545
- const read = (name) => {
546
- try {
547
- return fs.readFileSync(path.join(volumeDir, name), {
548
- encoding: "utf-8",
549
- });
550
- } catch {
551
- return "not found";
552
- }
553
- };
554
-
555
- let boxName = read("box.name");
556
-
557
- return {
558
- default: boxName == defaultBoxName ? "yes" : "no",
559
- boxName,
560
- boxId: read("box.id"),
561
- };
562
- }
498
+ async listAllVM() {
499
+ const boxes = (await cc.QueryBoxList({})).boxes;
500
+ const entries = await this.loadBoxIdAndBoxName();
563
501
 
564
- /**
565
- * 从缓存路径中列出所有的盒子信息
566
- */
567
- infoAllVM(defaultBoxName = "") {
568
- let entries = fs.readdirSync(this.scheme.path, { withFileTypes: true });
569
502
  let result = [];
503
+ Object.keys(entries).forEach((vmname) => {
504
+ let info = entries[vmname];
505
+ const box = boxes.find((box) => box.boxId == info.id);
506
+ const status = box ? getBoxStatus(box.status) : unRegistry;
507
+ result.push({
508
+ ...info,
509
+ status,
510
+ default: isDefault(info.name),
511
+ "vm status": isRunning(vmname),
512
+ });
513
+ });
570
514
 
571
- entries.forEach((entry) => {
572
- if (!entry.isDirectory()) {
573
- return;
574
- }
575
-
576
- if (!entry.name.startsWith("vm-")) {
577
- return;
578
- }
579
-
580
- let name = entry.name.substr(3);
581
- let info = this.sigleVM(name, defaultBoxName);
582
- if (info) {
583
- let pid = parseVmPID(path.join(this.scheme.path, entry.name));
584
- info["vm status"] = pid ? "running" : "stop";
585
- result.push(info);
515
+ boxes.forEach((box) => {
516
+ let info = result.find((r) => r.id == box.boxId);
517
+ if (!info) {
518
+ result.push({
519
+ id: box.boxId,
520
+ name: box.boxName,
521
+ status: getBoxStatus(box.status),
522
+ default: isDefault(box.boxName),
523
+ "vm status": remoteBox,
524
+ });
586
525
  }
587
526
  });
588
-
589
527
  return result;
590
528
  }
591
529
 
592
- /**
593
- * 返回指定盒子信息
594
- */
595
- async infoSigleVM(name, defaultBoxName = "") {
596
- let baseInfo = this.sigleVM(name, defaultBoxName);
597
- if (!baseInfo) {
598
- return [];
599
- }
600
-
601
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
602
- baseInfo["vm status"] = parseVmPID(vmDir) ? "running" : "stop";
603
- return [baseInfo];
604
- }
605
-
606
530
  /**
607
531
  * 根据 scheme 中的 path 和 uuid 和 name 获取盒子的信息
608
532
  */
609
- async infoVM(name, defaultBoxName = "") {
610
- let infos;
611
- if (!name) {
612
- infos = await this.infoAllVM(defaultBoxName);
613
- } else {
614
- infos = await this.infoSigleVM(name, defaultBoxName);
533
+ async listVM(nameOrBoxId) {
534
+ let infos = await this.listAllVM();
535
+ if (nameOrBoxId) {
536
+ let info = infos.find(
537
+ (i) => i.name === nameOrBoxId || i.id === nameOrBoxId
538
+ );
539
+ if (info) {
540
+ infos = [info];
541
+ }
615
542
  }
616
- return infos;
543
+
544
+ return infos.map((info) => {
545
+ delete info["vmname"];
546
+ return info;
547
+ });
617
548
  }
618
549
 
619
550
  /**
620
551
  * 根据 scheme 中的 path 和 uuid 和 name 获取盒子的boxid
621
552
  */
622
- readBoxid(name) {
553
+ readBoxid(vmname) {
623
554
  if (!this.scheme.volume["config"]) {
624
555
  return "";
625
556
  }
626
557
 
627
- let vmDir = path.join(this.scheme.path, `vm-${name}`);
558
+ let vmDir = path.join(this.scheme.path, vmname);
628
559
  let volumeDir = path.join(vmDir, `volume-config`);
629
560
 
630
561
  try {
@@ -636,4 +567,42 @@ export class QemuVM {
636
567
  return "";
637
568
  }
638
569
  }
570
+
571
+ async loadBoxIdAndBoxName() {
572
+ const files = await glob(
573
+ ["vm-*/volume-config/box.id", "vm-*/volume-config/box.name"],
574
+ {
575
+ cwd: this.scheme.path,
576
+ dot: false,
577
+ }
578
+ );
579
+ const result = {};
580
+ files.forEach((file) => {
581
+ const vmDir = file.split(path.sep)[0];
582
+ const type = path.extname(file).toLowerCase() === ".name" ? "name" : "id";
583
+ const value = fs.readFileSync(
584
+ path.resolve(this.scheme.path, file),
585
+ "utf8"
586
+ );
587
+ if (!result[vmDir]) {
588
+ result[vmDir] = { id: "", name: "", vmname: "" };
589
+ }
590
+ result[vmDir][type] = value;
591
+ result[vmDir]["vmname"] = vmDir;
592
+ });
593
+ return result;
594
+ }
595
+
596
+ async findVmname(nameOrBoxId) {
597
+ const entries = await this.loadBoxIdAndBoxName();
598
+ const vmname = Object.keys(entries).find((key) => {
599
+ const e = entries[key];
600
+ return e?.name === nameOrBoxId || e.id === nameOrBoxId;
601
+ });
602
+ if (!vmname) {
603
+ throw `${nameOrBoxId} 盒子不存在,请检查盒子名称或者ID是否正确`;
604
+ }
605
+
606
+ return entries[vmname];
607
+ }
639
608
  }
@@ -4,7 +4,7 @@
4
4
  "memory": 4096,
5
5
  "kernel": "debian",
6
6
  "uefi": true,
7
- "path": "$HOME/.cache/box-emulator",
7
+ "path": "${HOME}/.cache/box-emulator",
8
8
  "volume": {
9
9
  "config": "/etc/home-cloud"
10
10
  },
@@ -1,7 +1,6 @@
1
1
  import yaml from "js-yaml";
2
2
  import fs from "fs";
3
3
  import mergeWith from "lodash.mergewith";
4
- import isArray from "lodash.isarray";
5
4
 
6
5
  export function load(filePath) {
7
6
  return yaml.load(fs.readFileSync(filePath, "utf8"));
@@ -30,7 +29,7 @@ export default class DockerCompose {
30
29
  }
31
30
 
32
31
  this.obj = mergeWith(this.obj, newObj, (objValue, srcValue) => {
33
- if (isArray(objValue)) {
32
+ if (Array.isArray(objValue)) {
34
33
  return objValue.concat(srcValue);
35
34
  }
36
35
  });