@lazycatcloud/lzc-cli 1.1.1 → 1.1.4

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 (67) hide show
  1. package/cmds/app.js +137 -0
  2. package/cmds/config.js +55 -0
  3. package/cmds/create.js +55 -0
  4. package/cmds/dev.js +122 -0
  5. package/cmds/init.js +125 -0
  6. package/cmds/log.js +103 -0
  7. package/cmds/publish.js +116 -0
  8. package/lib/api.js +34 -36
  9. package/lib/archiver.js +50 -31
  10. package/lib/box/check_qemu.js +27 -0
  11. package/lib/box/hportal.js +114 -0
  12. package/lib/box/index.js +152 -0
  13. package/lib/box/qemu_vm_mgr.js +625 -0
  14. package/lib/box/schemes/vm_box_system_debian.json +47 -0
  15. package/lib/builder.js +154 -35
  16. package/lib/dev.js +51 -32
  17. package/lib/env.js +276 -57
  18. package/lib/generator.js +31 -0
  19. package/lib/git/git-commit.sh +7 -0
  20. package/lib/git/git-reset.sh +15 -0
  21. package/lib/key.js +14 -11
  22. package/lib/sdk.js +7 -10
  23. package/lib/utils.js +149 -53
  24. package/package.json +18 -5
  25. package/scripts/cli.js +134 -70
  26. package/template/_lazycat/app-config +1 -0
  27. package/template/_lazycat/docker-compose.yml.in +3 -5
  28. package/template/golang/README.md +3 -4
  29. package/template/golang/assets/css/bootstrap-responsive.css +26 -23
  30. package/template/golang/assets/css/bootstrap-responsive.min.css +1065 -1
  31. package/template/golang/assets/css/bootstrap.css +733 -362
  32. package/template/golang/assets/css/bootstrap.min.css +5299 -1
  33. package/template/golang/assets/css/rego.css +17 -17
  34. package/template/golang/assets/js/bootstrap.js +1340 -1311
  35. package/template/golang/assets/js/bootstrap.min.js +1240 -5
  36. package/template/golang/assets/js/rego.js +80 -69
  37. package/template/golang/index.html +61 -59
  38. package/template/ionic_vue3/README.md +46 -0
  39. package/template/ionic_vue3/_eslintrc.cjs +24 -0
  40. package/template/ionic_vue3/_gitignore +29 -0
  41. package/template/ionic_vue3/_vscode/extensions.json +6 -0
  42. package/template/ionic_vue3/capacitor.config.ts +10 -0
  43. package/template/ionic_vue3/env.d.ts +1 -0
  44. package/template/ionic_vue3/index.html +13 -0
  45. package/template/ionic_vue3/ionic.config.json +7 -0
  46. package/template/ionic_vue3/package.json +52 -0
  47. package/template/ionic_vue3/postcss.config.js +6 -0
  48. package/template/ionic_vue3/public/favicon.ico +0 -0
  49. package/template/ionic_vue3/src/App.vue +11 -0
  50. package/template/ionic_vue3/src/assets/logo.svg +1 -0
  51. package/template/ionic_vue3/src/index.css +3 -0
  52. package/template/ionic_vue3/src/main.ts +35 -0
  53. package/template/ionic_vue3/src/router/index.ts +15 -0
  54. package/template/ionic_vue3/src/theme/variables.css +231 -0
  55. package/template/ionic_vue3/src/views/Home.vue +38 -0
  56. package/template/ionic_vue3/tailwind.config.js +7 -0
  57. package/template/ionic_vue3/tsconfig.json +16 -0
  58. package/template/ionic_vue3/tsconfig.vite-config.json +8 -0
  59. package/template/ionic_vue3/vite.config.ts +28 -0
  60. package/template/release/golang/build.sh +1 -2
  61. package/template/release/ionic_vue3/Dockerfile +10 -0
  62. package/template/release/ionic_vue3/build.sh +9 -0
  63. package/template/release/ionic_vue3/docker-compose.yml.in +8 -0
  64. package/template/release/vue/Dockerfile +3 -2
  65. package/template/release/vue/build.sh +4 -2
  66. package/template/vue/README.md +5 -0
  67. package/template/vue/babel.config.js +2 -4
@@ -0,0 +1,625 @@
1
+ #!/bin/node
2
+ import fs from "node:fs";
3
+ import { spawn, spawnSync } from "node:child_process";
4
+ import path from "node:path";
5
+ import inquirer from "inquirer";
6
+ import https from "node:https";
7
+ import http from "node:http";
8
+ import lz4 from "lz4";
9
+ import process from "node:process";
10
+ import net from "node:net";
11
+ import fetch from "node-fetch";
12
+
13
+ async function getFreePort() {
14
+ return new Promise((resolve, reject) => {
15
+ const server = net.createServer();
16
+ server.listen(() => {
17
+ let addr = server.address();
18
+ resolve(addr.port);
19
+ server.close();
20
+ });
21
+ server.on("error", reject);
22
+ });
23
+ }
24
+
25
+ function parseVmPID(vmDir) {
26
+ let p = spawnSync("pgrep", ["-f", path.join(vmDir, "disk-system.qcow2")]);
27
+ if (p.status == 0) {
28
+ return p.stdout.toString();
29
+ } else {
30
+ // 没有找到任何记录
31
+ return "";
32
+ }
33
+ }
34
+
35
+ export class QemuResource {
36
+ /**
37
+ * diskDir qemu 磁盘存储路径,默认 ~/.local/share/lazycat/box-emulator
38
+ * downloadUrl system-disk 下载路径
39
+ */
40
+ constructor(
41
+ diskDir = "~/.cache/box-emulator",
42
+ downloadUrl = "https://dl.lazycat.cloud/sdk/vm"
43
+ ) {
44
+ this.diskDir = diskDir;
45
+ this.downloadUrl = downloadUrl;
46
+ }
47
+
48
+ async init() {
49
+ fs.mkdirSync(this.diskDir, { recursive: true });
50
+ let shouldUpdate = await this.shouldUpdate();
51
+ if (shouldUpdate) {
52
+ let answer = await inquirer.prompt([
53
+ {
54
+ type: "confirm",
55
+ name: "yesorno",
56
+ message: `检测到盒子系统具有更新,是否需要重新下载盒子系统镜像?`,
57
+ default: true,
58
+ },
59
+ ]);
60
+ if (!answer.yesorno) {
61
+ shouldUpdate = false;
62
+ }
63
+ }
64
+ await this.ensureSystemDisk(shouldUpdate);
65
+ }
66
+
67
+ /**
68
+ * 根据下载到的 md5 和本地缓存的 md5 进行比较,如果不一样,则代表具有更新,
69
+ * 然后提示用户是否更新。
70
+ * 如果在获取 md5 的过程中,发生网络错误,则跳过更新检查。
71
+ * 目前的更新,只是使用 md5 来判断是否需要重新下载 md5, 并不会使用 md5 来判断所下载的数据是否正确。
72
+ */
73
+ async shouldUpdate() {
74
+ let oldMd5Path = path.join(this.diskDir, "disk-system.qcow2.lz4.md5");
75
+
76
+ // 如果不存在 md5 文件,直接返回需要更新
77
+ try {
78
+ fs.accessSync(oldMd5Path);
79
+ } catch {
80
+ return true;
81
+ }
82
+
83
+ // 拉取最新的 md5
84
+ let newMd5;
85
+ try {
86
+ let url = `${this.downloadUrl}/disk-system.qcow2.lz4.md5`;
87
+ let res = await fetch(url);
88
+ if (res.status !== 200) {
89
+ return false;
90
+ }
91
+ newMd5 = await res.text();
92
+ } catch {
93
+ return false;
94
+ }
95
+
96
+ let oldMd5 = fs.readFileSync(oldMd5Path, { encoding: "utf-8" });
97
+ return oldMd5 !== newMd5;
98
+ }
99
+
100
+ /**
101
+ * 确保 qemu 系统盘的镜像存在,如果不存在,将会从 downloadUrl 中下载
102
+ */
103
+ async ensureSystemDisk(update = false) {
104
+ let systemDiskFile = path.join(this.diskDir, "disk-system.qcow2");
105
+ if (!update) {
106
+ try {
107
+ fs.accessSync(systemDiskFile);
108
+ return;
109
+ } catch {}
110
+ }
111
+
112
+ await this.downloadSystemDisk();
113
+ await this.downloadPrivateKey();
114
+ await this.downloadSystemDiskMd5();
115
+ }
116
+
117
+ async downloadSystemDiskMd5() {
118
+ let savePath = path.join(this.diskDir, "disk-system.qcow2.lz4.md5");
119
+ let url = `${this.downloadUrl}/disk-system.qcow2.lz4.md5`;
120
+ await this.download(url, savePath);
121
+ }
122
+
123
+ async downloadSystemDisk() {
124
+ let savePath = path.join(this.diskDir, "disk-system.qcow2");
125
+ let url = `${this.downloadUrl}/disk-system.qcow2.lz4`;
126
+ await this.download(url, savePath, true);
127
+ }
128
+
129
+ async downloadPrivateKey() {
130
+ let savePath = path.join(this.diskDir, "id_rsa");
131
+ let url = `${this.downloadUrl}/id_rsa`;
132
+ await this.download(url, savePath);
133
+ fs.chmodSync(savePath, 0o400);
134
+ return;
135
+ }
136
+
137
+ showDownloadingProgress(received, total) {
138
+ var percentage = ((received * 100) / total).toFixed(2);
139
+ process.stdout.write("\r");
140
+ process.stdout.write(
141
+ percentage +
142
+ "% | " +
143
+ received +
144
+ " bytes downloaded out of " +
145
+ total +
146
+ " bytes."
147
+ );
148
+ }
149
+
150
+ download(url, savePath, enableLz4 = false) {
151
+ let tmpPath = savePath + ".tmp";
152
+ const options = new URL(url);
153
+ let request = url.startsWith("https") ? https.request : http.request;
154
+
155
+ return new Promise((resolve, reject) => {
156
+ const req = request(options, (res) => {
157
+ if (res.statusCode != 200) {
158
+ reject(`下载 ${url} 失败`);
159
+ return;
160
+ }
161
+
162
+ let total = parseInt(res.headers["content-length"]);
163
+ let recive = 0;
164
+ res.on("data", (chunk) => {
165
+ recive += chunk.length;
166
+ this.showDownloadingProgress(recive, total);
167
+ });
168
+
169
+ let outputFile = fs.createWriteStream(tmpPath, { flags: "w+" });
170
+ if (enableLz4) {
171
+ let decoder = lz4.createDecoderStream();
172
+ res.pipe(decoder).pipe(outputFile);
173
+ } else {
174
+ res.pipe(outputFile);
175
+ }
176
+
177
+ outputFile.on("error", reject);
178
+ outputFile.on("finish", () => {
179
+ fs.renameSync(tmpPath, savePath);
180
+ resolve();
181
+ });
182
+ });
183
+ req.on("error", reject);
184
+ req.end();
185
+ });
186
+ }
187
+ }
188
+
189
+ export class QemuVM {
190
+ /**
191
+ * @param {Object} scheme - 各个操作系统的配置文件.
192
+ */
193
+ constructor(scheme) {
194
+ this.scheme = scheme;
195
+ }
196
+
197
+ /**
198
+ * 运行虚拟盒子,并等待 boxid 的出现,确保盒子启动正常
199
+ */
200
+ async startVM(name, vmDir) {
201
+ let args = await this.buildQemuArgs(name, vmDir);
202
+ let p = spawn("qemu-system-x86_64", args, {
203
+ detached: true,
204
+ stdio: ["pipe", "ignore", "inherit"],
205
+ });
206
+ p.on("error", (e) => {
207
+ throw e;
208
+ });
209
+ console.log("启动中...");
210
+
211
+ // 需要等待 boxid 的出现
212
+ return new Promise((resolve, reject) => {
213
+ let count = 0;
214
+ let id = setInterval(() => {
215
+ if (count == 15) {
216
+ count = 0;
217
+ }
218
+ process.stdout.write("\r");
219
+ process.stdout.write("等待中" + ".".repeat(count));
220
+ count++;
221
+
222
+ if (p.exitCode) {
223
+ reject(`qemu 已经退出 Code: ${p.exitCode}`);
224
+ clearInterval(id);
225
+ return;
226
+ }
227
+
228
+ let boxId = this.readBoxid(name);
229
+ if (boxId) {
230
+ clearInterval(id);
231
+ p.unref();
232
+ resolve(boxId);
233
+ console.log("启动成功!");
234
+ }
235
+ }, 1000);
236
+
237
+ // 当在等待期间,ctrl-c 退出
238
+ process.on("SIGINT", () => {
239
+ p.kill();
240
+ clearInterval(id);
241
+ reject("exit");
242
+ });
243
+ });
244
+ }
245
+
246
+ /**
247
+ * 运行一个指定的虚拟机,如果已经运行,将直接退出。如果盒子不存在,将报错退出
248
+ * @param {string} name - 盒子的名称
249
+ * @return {string} id - 盒子的id
250
+ **/
251
+ async runVM(name) {
252
+ let boxid = this.readBoxid(name);
253
+ if (!boxid) {
254
+ console.log(`${name} 盒子不存在,请通过 lzc-cli box create 创建`);
255
+ return;
256
+ }
257
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
258
+ let pid = parseVmPID(vmDir);
259
+ if (pid) {
260
+ console.log(`${name} 盒子已经启动`);
261
+ return boxid;
262
+ }
263
+ await this.startVM(name, vmDir);
264
+ console.log(`${name} 盒子启动成功!`);
265
+ }
266
+
267
+ /**
268
+ * 创建一个指定的虚拟机,并启动等待注册成功。
269
+ **/
270
+ async createVM({ boxName, adminName, adminPass }) {
271
+ let vmDir = this.ensureVmDir(boxName);
272
+ this.ensureVolumeDir(vmDir);
273
+ await this.buildDisks(vmDir);
274
+ let boxId = await this.startVM(boxName, vmDir);
275
+ console.log("盒子ID: ", boxId);
276
+
277
+ await this.registerVM(boxId, {
278
+ boxName,
279
+ adminName,
280
+ adminPass,
281
+ });
282
+ return boxId;
283
+ }
284
+
285
+ /**
286
+ * 请求用户输入盒子的基本信息
287
+ **/
288
+ async askBoxInfo() {
289
+ const noEmpty = (value) => value != "";
290
+
291
+ return inquirer.prompt([
292
+ {
293
+ type: "input",
294
+ name: "boxName",
295
+ message: "请输入盒子名称:",
296
+ validate: noEmpty,
297
+ },
298
+ {
299
+ type: "input",
300
+ name: "adminName",
301
+ message: "请输入盒子管理员名称:",
302
+ default: "admin",
303
+ },
304
+ {
305
+ type: "password",
306
+ name: "adminPass",
307
+ message: "请输入盒子管理员密码:",
308
+ mask: "*",
309
+ validate: noEmpty,
310
+ },
311
+ ]);
312
+ }
313
+
314
+ /**
315
+ * 调用 hportal client 进行注册
316
+ **/
317
+ async registerVM(boxid, { boxName, adminName, adminPass }) {
318
+ // prettier-ignore
319
+ spawnSync("home-portal-client",[
320
+ "-setup", "-boxid", boxid,
321
+ "-boxname", boxName,
322
+ "-user", adminName,
323
+ "-password", adminPass,
324
+ ], {stdio: "inherit"})
325
+ return boxName;
326
+ }
327
+
328
+ /**
329
+ * 根据虚拟机名称确保缓存文件夹存在,返回对应的虚拟机缓存文件夹
330
+ */
331
+ ensureVmDir(name) {
332
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
333
+ fs.mkdirSync(vmDir, { recursive: true });
334
+ return vmDir;
335
+ }
336
+
337
+ ensureVolumeDir(vmDir) {
338
+ for (let key in this.scheme.volume) {
339
+ let bindPath = `${vmDir}/volume-${key}`;
340
+ fs.mkdirSync(bindPath, { recursive: true });
341
+ }
342
+ }
343
+
344
+ /**
345
+ * 构建硬盘,从 scheme 中读取 disk 的配置信息,并构建。
346
+ */
347
+ async buildDisks(vmDir) {
348
+ for (const diskInfo of this.scheme.disks) {
349
+ let name = `disk-${diskInfo.id}.qcow2`;
350
+ let diskPath = path.join(vmDir, name);
351
+
352
+ try {
353
+ fs.accessSync(diskPath, fs.constants.F_OK);
354
+ continue;
355
+ } catch {}
356
+
357
+ if (diskInfo.system) {
358
+ await this.buildSystemDisk(name, diskPath, diskInfo);
359
+ } else {
360
+ await this.buildDataDisk(name, diskPath, diskInfo);
361
+ }
362
+ }
363
+ }
364
+
365
+ async buildSystemDisk(name, diskPath, diskInfo) {
366
+ console.log(`构建系统盘快照:${diskPath}`);
367
+ let baseImage = path.join(this.scheme.path, name);
368
+ return spawnSync(
369
+ "qemu-img",
370
+ ["create", "-f", "qcow2", "-b", baseImage, "-F", "qcow2", diskPath],
371
+ { stdio: "inherit" }
372
+ );
373
+ }
374
+
375
+ async buildDataDisk(name, diskPath, diskInfo) {
376
+ console.log(`构建数据盘:${diskPath}`);
377
+ return spawnSync(
378
+ "qemu-img",
379
+ ["create", "-f", "qcow2", diskPath, diskInfo.size],
380
+ { stdio: "inherit" }
381
+ );
382
+ }
383
+
384
+ /**
385
+ * 寻找默认可用的 bios 文件
386
+ */
387
+ findBIOS() {
388
+ let includes = ["/usr/share/ovmf/x64/OVMF.fd", "/usr/share/ovmf/OVMF.fd"];
389
+ for (let file of includes) {
390
+ try {
391
+ fs.accessSync(file, fs.constants.F_OK);
392
+ return file;
393
+ } catch {}
394
+ }
395
+
396
+ throw "找不到 ovmf 文件";
397
+ }
398
+
399
+ /**
400
+ * 构建 qemu 启动的参数
401
+ */
402
+ async buildQemuArgs(name, vmDir) {
403
+ let sshPort = await getFreePort();
404
+ let bios = this.findBIOS();
405
+ // prettier-ignore
406
+ let args = [
407
+ "-name", `${this.scheme.name}-${this.scheme.uuid}-vm-${name}`,
408
+ "-machine", "pc,accel=kvm,accel=kvf,accel=xen,accel=hax,accel=nvmm,accel=whpx,accel=tcg",
409
+ "-m", `${this.scheme.memory}`,
410
+ "-smp", "4,sockets=4,cores=1,threads=1",
411
+ "-bios", bios,
412
+ "-device", "piix3-usb-uhci",
413
+ "-vnc", `unix:${vmDir}/.vnc`,
414
+ "-monitor", `unix:${vmDir}/.monitor,server,nowait`,
415
+ "-serial", `unix:${vmDir}/.serial,server,nowait`,
416
+ "-parallel", `unix:${vmDir}/.parallel,server,nowait`,
417
+ "-netdev", `user,hostfwd=tcp::${sshPort}-:22,id=eth`,
418
+ "-device", "virtio-net-pci,netdev=eth",
419
+ "-chardev", "stdio,id=s1,signal=off",
420
+ "-device", "isa-serial,chardev=s1",
421
+ ]
422
+
423
+ this.scheme.disks.forEach((diskInfo, index) => {
424
+ let file = `file=${vmDir}/disk-${diskInfo.id}.qcow2,format=qcow2,index=${index},media=disk,if=virtio`;
425
+ args.push("-drive", file);
426
+ });
427
+
428
+ Object.keys(this.scheme.volume).forEach((key) => {
429
+ let volumePath = this.scheme.volume[key];
430
+ let bindPath = `${vmDir}/volume-${key}`;
431
+ args.push(
432
+ "-virtfs",
433
+ `local,path=${bindPath},mount_tag=id_2245023265ae4cf87d02c8b6,security_model=mapped-xattr,id=id_2245023265ae4cf87d02c8b6`
434
+ );
435
+ });
436
+
437
+ return args;
438
+ }
439
+
440
+ /**
441
+ * 停止虚拟盒子
442
+ */
443
+ async stopVM(name) {
444
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
445
+ let pid = parseVmPID(vmDir);
446
+ if (pid) {
447
+ process.kill(pid);
448
+ }
449
+ console.log(`${name} 盒子已停止`);
450
+ }
451
+
452
+ /**
453
+ * 清理盒子,用于注册失败或者在注册过程中用户手动中止的情况
454
+ */
455
+ async cleanVM(name) {
456
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
457
+
458
+ try {
459
+ fs.accessSync(vmDir);
460
+ } catch {
461
+ return;
462
+ }
463
+ fs.rmSync(vmDir, { recursive: true, force: true });
464
+ }
465
+
466
+ /**
467
+ * 删除虚拟盒子
468
+ * @param {boolean} silence - 是否提示错误,如果被删除的盒子不存在
469
+ */
470
+ async deleteVM(name, silence = false) {
471
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
472
+
473
+ try {
474
+ fs.accessSync(vmDir);
475
+ } catch {
476
+ if (!silence) {
477
+ console.log(`${name} 盒子不存在或者该盒子为一个真实盒子`);
478
+ }
479
+ return;
480
+ }
481
+
482
+ let pid = parseVmPID(vmDir);
483
+ if (pid) {
484
+ await inquirer
485
+ .prompt([
486
+ {
487
+ type: "confirm",
488
+ name: "yesorno",
489
+ message: `${name} 正在运行中,是否停止?`,
490
+ default: false,
491
+ },
492
+ ])
493
+ .then(async (answer) => {
494
+ if (!answer.yesorno) {
495
+ return;
496
+ }
497
+ process.kill(pid);
498
+ console.log(`${name} 盒子已停止`);
499
+ });
500
+ }
501
+
502
+ return inquirer
503
+ .prompt([
504
+ {
505
+ type: "confirm",
506
+ name: "yesorno",
507
+ message: `确定要删除 ${name} 盒子吗?`,
508
+ default: false,
509
+ },
510
+ ])
511
+ .then(async (answer) => {
512
+ if (!answer.yesorno) {
513
+ return;
514
+ }
515
+ fs.rmSync(vmDir, { recursive: true, force: true });
516
+ });
517
+ }
518
+
519
+ /**
520
+ * 从缓存路径中列出指定盒子信息
521
+ */
522
+ sigleVM(name, defaultBoxName = "") {
523
+ let volumeDir = path.join(this.scheme.path, `vm-${name}`, `volume-config`);
524
+
525
+ try {
526
+ fs.accessSync(volumeDir);
527
+ } catch {
528
+ return;
529
+ }
530
+
531
+ const read = (name) => {
532
+ try {
533
+ return fs.readFileSync(path.join(volumeDir, name), {
534
+ encoding: "utf-8",
535
+ });
536
+ } catch {
537
+ return "not found";
538
+ }
539
+ };
540
+
541
+ let boxName = read("box.name");
542
+
543
+ return {
544
+ default: boxName == defaultBoxName ? "yes" : "no",
545
+ boxName,
546
+ boxId: read("box.id"),
547
+ };
548
+ }
549
+
550
+ /**
551
+ * 从缓存路径中列出所有的盒子信息
552
+ */
553
+ infoAllVM(defaultBoxName = "") {
554
+ let entries = fs.readdirSync(this.scheme.path, { withFileTypes: true });
555
+ let result = [];
556
+
557
+ entries.forEach((entry) => {
558
+ if (!entry.isDirectory()) {
559
+ return;
560
+ }
561
+
562
+ if (!entry.name.startsWith("vm-")) {
563
+ return;
564
+ }
565
+
566
+ let name = entry.name.substr(3);
567
+ let info = this.sigleVM(name, defaultBoxName);
568
+ if (info) {
569
+ let pid = parseVmPID(path.join(this.scheme.path, entry.name));
570
+ info["vm status"] = pid ? "running" : "stop";
571
+ result.push(info);
572
+ }
573
+ });
574
+
575
+ return result;
576
+ }
577
+
578
+ /**
579
+ * 返回指定盒子信息
580
+ */
581
+ async infoSigleVM(name, defaultBoxName = "") {
582
+ let baseInfo = this.sigleVM(name, defaultBoxName);
583
+ if (!baseInfo) {
584
+ return [];
585
+ }
586
+
587
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
588
+ baseInfo["vm status"] = parseVmPID(vmDir) ? "running" : "stop";
589
+ return [baseInfo];
590
+ }
591
+
592
+ /**
593
+ * 根据 scheme 中的 path 和 uuid 和 name 获取盒子的信息
594
+ */
595
+ async infoVM(name, defaultBoxName = "") {
596
+ let infos;
597
+ if (!name) {
598
+ infos = await this.infoAllVM(defaultBoxName);
599
+ } else {
600
+ infos = await this.infoSigleVM(name, defaultBoxName);
601
+ }
602
+ return infos;
603
+ }
604
+
605
+ /**
606
+ * 根据 scheme 中的 path 和 uuid 和 name 获取盒子的boxid
607
+ */
608
+ readBoxid(name) {
609
+ if (!this.scheme.volume["config"]) {
610
+ return "";
611
+ }
612
+
613
+ let vmDir = path.join(this.scheme.path, `vm-${name}`);
614
+ let volumeDir = path.join(vmDir, `volume-config`);
615
+
616
+ try {
617
+ let boxid = fs.readFileSync(path.join(volumeDir, "box.id"), {
618
+ encoding: "utf8",
619
+ });
620
+ return boxid;
621
+ } catch {
622
+ return "";
623
+ }
624
+ }
625
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "home-cloud",
3
+ "uuid": "047dac31-b75b-46ed-aedb-a31fa46a6503",
4
+ "memory": 4096,
5
+ "kernel": "debian",
6
+ "uefi": true,
7
+ "path": "$HOME/.cache/box-emulator",
8
+ "volume": {
9
+ "config": "/etc/home-cloud"
10
+ },
11
+ "disks": [
12
+ {
13
+ "id": "system",
14
+ "size": "8G",
15
+ "system": true,
16
+ "partitions": [
17
+ {
18
+ "fs_type": "vfat",
19
+ "size": "100M",
20
+ "mount_point": "/boot/ESP"
21
+ },
22
+ {
23
+ "fs_type": "ext4",
24
+ "size": "4G",
25
+ "mount_point": "/"
26
+ },
27
+ {
28
+ "fs_type": "ext4",
29
+ "size": "full",
30
+ "mount_point": "/mnt"
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "id": "data1",
36
+ "size": "64G",
37
+ "system": false,
38
+ "partitions": []
39
+ },
40
+ {
41
+ "id": "data2",
42
+ "size": "64G",
43
+ "rebuild": false,
44
+ "partitions": []
45
+ }
46
+ ]
47
+ }