@phystack/cli 5.0.3 → 5.1.0

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 (218) hide show
  1. package/bin/index.js +27 -1
  2. package/package.json +12 -65
  3. package/dist/commands/app/build-apps.js +0 -66
  4. package/dist/commands/app/build-apps.js.map +0 -1
  5. package/dist/commands/app/build-container-remote.js +0 -171
  6. package/dist/commands/app/build-container-remote.js.map +0 -1
  7. package/dist/commands/app/build-container.js +0 -330
  8. package/dist/commands/app/build-container.js.map +0 -1
  9. package/dist/commands/app/build.js +0 -398
  10. package/dist/commands/app/build.js.map +0 -1
  11. package/dist/commands/app/create.js +0 -463
  12. package/dist/commands/app/create.js.map +0 -1
  13. package/dist/commands/app/deploy.js +0 -176
  14. package/dist/commands/app/deploy.js.map +0 -1
  15. package/dist/commands/app/device-picker.js +0 -150
  16. package/dist/commands/app/device-picker.js.map +0 -1
  17. package/dist/commands/app/import.js +0 -336
  18. package/dist/commands/app/import.js.map +0 -1
  19. package/dist/commands/app/list.js +0 -26
  20. package/dist/commands/app/list.js.map +0 -1
  21. package/dist/commands/app/publish.js +0 -332
  22. package/dist/commands/app/publish.js.map +0 -1
  23. package/dist/commands/app/settings.js +0 -133
  24. package/dist/commands/app/settings.js.map +0 -1
  25. package/dist/commands/app/types.js +0 -14
  26. package/dist/commands/app/types.js.map +0 -1
  27. package/dist/commands/app/upload-description.js +0 -139
  28. package/dist/commands/app/upload-description.js.map +0 -1
  29. package/dist/commands/app/upload-settings.js +0 -29
  30. package/dist/commands/app/upload-settings.js.map +0 -1
  31. package/dist/commands/app/utils.js +0 -86
  32. package/dist/commands/app/utils.js.map +0 -1
  33. package/dist/commands/auth/login.js +0 -145
  34. package/dist/commands/auth/login.js.map +0 -1
  35. package/dist/commands/auth/logout.js +0 -19
  36. package/dist/commands/auth/logout.js.map +0 -1
  37. package/dist/commands/descriptor/create.js +0 -143
  38. package/dist/commands/descriptor/create.js.map +0 -1
  39. package/dist/commands/descriptor/index.js +0 -36
  40. package/dist/commands/descriptor/index.js.map +0 -1
  41. package/dist/commands/descriptor/publish.js +0 -163
  42. package/dist/commands/descriptor/publish.js.map +0 -1
  43. package/dist/commands/descriptor/show.js +0 -68
  44. package/dist/commands/descriptor/show.js.map +0 -1
  45. package/dist/commands/dev/cp.js +0 -764
  46. package/dist/commands/dev/cp.js.map +0 -1
  47. package/dist/commands/dev/develop.js +0 -175
  48. package/dist/commands/dev/develop.js.map +0 -1
  49. package/dist/commands/dev/forward.js +0 -132
  50. package/dist/commands/dev/forward.js.map +0 -1
  51. package/dist/commands/dev/index.js +0 -80
  52. package/dist/commands/dev/index.js.map +0 -1
  53. package/dist/commands/dev/list.js +0 -96
  54. package/dist/commands/dev/list.js.map +0 -1
  55. package/dist/commands/dev/screen-devtools.js +0 -156
  56. package/dist/commands/dev/screen-devtools.js.map +0 -1
  57. package/dist/commands/dev/select.js +0 -118
  58. package/dist/commands/dev/select.js.map +0 -1
  59. package/dist/commands/dev/shell.js +0 -186
  60. package/dist/commands/dev/shell.js.map +0 -1
  61. package/dist/commands/dev/vnc.js +0 -75
  62. package/dist/commands/dev/vnc.js.map +0 -1
  63. package/dist/commands/device/select.js +0 -118
  64. package/dist/commands/device/select.js.map +0 -1
  65. package/dist/commands/flash.js +0 -1120
  66. package/dist/commands/flash.js.map +0 -1
  67. package/dist/commands/inst/create.js +0 -55
  68. package/dist/commands/inst/create.js.map +0 -1
  69. package/dist/commands/inst/index.js +0 -15
  70. package/dist/commands/inst/index.js.map +0 -1
  71. package/dist/commands/inst/list.js +0 -26
  72. package/dist/commands/inst/list.js.map +0 -1
  73. package/dist/commands/legacydev/debug.js +0 -11
  74. package/dist/commands/legacydev/debug.js.map +0 -1
  75. package/dist/commands/legacydev/deploy.js +0 -15
  76. package/dist/commands/legacydev/deploy.js.map +0 -1
  77. package/dist/commands/legacydev/dumpTwin.js +0 -27
  78. package/dist/commands/legacydev/dumpTwin.js.map +0 -1
  79. package/dist/commands/legacydev/forward.js +0 -104
  80. package/dist/commands/legacydev/forward.js.map +0 -1
  81. package/dist/commands/legacydev/index.js +0 -188
  82. package/dist/commands/legacydev/index.js.map +0 -1
  83. package/dist/commands/legacydev/invoke.js +0 -29
  84. package/dist/commands/legacydev/invoke.js.map +0 -1
  85. package/dist/commands/legacydev/js.js +0 -69
  86. package/dist/commands/legacydev/js.js.map +0 -1
  87. package/dist/commands/legacydev/list.js +0 -196
  88. package/dist/commands/legacydev/list.js.map +0 -1
  89. package/dist/commands/legacydev/logs.js +0 -60
  90. package/dist/commands/legacydev/logs.js.map +0 -1
  91. package/dist/commands/legacydev/modules.js +0 -50
  92. package/dist/commands/legacydev/modules.js.map +0 -1
  93. package/dist/commands/legacydev/move.js +0 -23
  94. package/dist/commands/legacydev/move.js.map +0 -1
  95. package/dist/commands/legacydev/ota.js +0 -88
  96. package/dist/commands/legacydev/ota.js.map +0 -1
  97. package/dist/commands/legacydev/patchTwin.js +0 -21
  98. package/dist/commands/legacydev/patchTwin.js.map +0 -1
  99. package/dist/commands/legacydev/pin.js +0 -23
  100. package/dist/commands/legacydev/pin.js.map +0 -1
  101. package/dist/commands/legacydev/pub.js +0 -25
  102. package/dist/commands/legacydev/pub.js.map +0 -1
  103. package/dist/commands/legacydev/rdp.js +0 -64
  104. package/dist/commands/legacydev/rdp.js.map +0 -1
  105. package/dist/commands/legacydev/screen-devtools.js +0 -142
  106. package/dist/commands/legacydev/screen-devtools.js.map +0 -1
  107. package/dist/commands/legacydev/settingsShow.js +0 -89
  108. package/dist/commands/legacydev/settingsShow.js.map +0 -1
  109. package/dist/commands/legacydev/settingsUpdate.js +0 -114
  110. package/dist/commands/legacydev/settingsUpdate.js.map +0 -1
  111. package/dist/commands/legacydev/shell.js +0 -167
  112. package/dist/commands/legacydev/shell.js.map +0 -1
  113. package/dist/commands/legacydev/showTwin.js +0 -9
  114. package/dist/commands/legacydev/showTwin.js.map +0 -1
  115. package/dist/commands/legacydev/statusLog.js +0 -56
  116. package/dist/commands/legacydev/statusLog.js.map +0 -1
  117. package/dist/commands/legacydev/sub.js +0 -39
  118. package/dist/commands/legacydev/sub.js.map +0 -1
  119. package/dist/commands/legacydev/vnc.js +0 -61
  120. package/dist/commands/legacydev/vnc.js.map +0 -1
  121. package/dist/commands/simulator/index.js +0 -37
  122. package/dist/commands/simulator/index.js.map +0 -1
  123. package/dist/commands/simulator/list.js +0 -20
  124. package/dist/commands/simulator/list.js.map +0 -1
  125. package/dist/commands/simulator/remove.js +0 -19
  126. package/dist/commands/simulator/remove.js.map +0 -1
  127. package/dist/commands/simulator/run.js +0 -380
  128. package/dist/commands/simulator/run.js.map +0 -1
  129. package/dist/commands/simulator/start.js +0 -186
  130. package/dist/commands/simulator/start.js.map +0 -1
  131. package/dist/commands/tenant/index.js +0 -21
  132. package/dist/commands/tenant/index.js.map +0 -1
  133. package/dist/commands/tenant/list.js +0 -14
  134. package/dist/commands/tenant/list.js.map +0 -1
  135. package/dist/commands/tenant/select.js +0 -87
  136. package/dist/commands/tenant/select.js.map +0 -1
  137. package/dist/commands/vm/create.js +0 -959
  138. package/dist/commands/vm/create.js.map +0 -1
  139. package/dist/commands/vm/index.js +0 -130
  140. package/dist/commands/vm/index.js.map +0 -1
  141. package/dist/commands/vm/list.js +0 -124
  142. package/dist/commands/vm/list.js.map +0 -1
  143. package/dist/commands/vm/logs.js +0 -66
  144. package/dist/commands/vm/logs.js.map +0 -1
  145. package/dist/commands/vm/remove.js +0 -180
  146. package/dist/commands/vm/remove.js.map +0 -1
  147. package/dist/commands/vm/shell.js +0 -400
  148. package/dist/commands/vm/shell.js.map +0 -1
  149. package/dist/commands/vm/start.js +0 -861
  150. package/dist/commands/vm/start.js.map +0 -1
  151. package/dist/commands/vm/stop.js +0 -232
  152. package/dist/commands/vm/stop.js.map +0 -1
  153. package/dist/index.js +0 -169
  154. package/dist/index.js.map +0 -1
  155. package/dist/services/admin-api/admin-api.types.js +0 -3
  156. package/dist/services/admin-api/admin-api.types.js.map +0 -1
  157. package/dist/services/admin-api/device-modules.admin-api.service.js +0 -58
  158. package/dist/services/admin-api/device-modules.admin-api.service.js.map +0 -1
  159. package/dist/services/admin-api/devices-admin-api.service.js +0 -213
  160. package/dist/services/admin-api/devices-admin-api.service.js.map +0 -1
  161. package/dist/services/admin-api/gridapps-admin-api.service.js +0 -59
  162. package/dist/services/admin-api/gridapps-admin-api.service.js.map +0 -1
  163. package/dist/services/admin-api/index.js +0 -151
  164. package/dist/services/admin-api/index.js.map +0 -1
  165. package/dist/services/admin-api/installations-admin-api.service.js +0 -29
  166. package/dist/services/admin-api/installations-admin-api.service.js.map +0 -1
  167. package/dist/services/admin-api/organizations-admin-api.service.js +0 -53
  168. package/dist/services/admin-api/organizations-admin-api.service.js.map +0 -1
  169. package/dist/services/auth/device-grant-auth.service.js +0 -226
  170. package/dist/services/auth/device-grant-auth.service.js.map +0 -1
  171. package/dist/services/env.js +0 -21
  172. package/dist/services/env.js.map +0 -1
  173. package/dist/services/phyhub/index.js +0 -200
  174. package/dist/services/phyhub/index.js.map +0 -1
  175. package/dist/services/phyhub/phyhub.types.js +0 -3
  176. package/dist/services/phyhub/phyhub.types.js.map +0 -1
  177. package/dist/simulator/index.js +0 -162
  178. package/dist/simulator/index.js.map +0 -1
  179. package/dist/simulator/local-server.js +0 -147
  180. package/dist/simulator/local-server.js.map +0 -1
  181. package/dist/simulator/logger.js +0 -43
  182. package/dist/simulator/logger.js.map +0 -1
  183. package/dist/simulator/message-router.js +0 -379
  184. package/dist/simulator/message-router.js.map +0 -1
  185. package/dist/simulator/twin-cache.js +0 -58
  186. package/dist/simulator/twin-cache.js.map +0 -1
  187. package/dist/simulator/types.js +0 -16
  188. package/dist/simulator/types.js.map +0 -1
  189. package/dist/utils/device-fetcher.js +0 -92
  190. package/dist/utils/device-fetcher.js.map +0 -1
  191. package/dist/utils/devices.js +0 -41
  192. package/dist/utils/devices.js.map +0 -1
  193. package/dist/utils/docker-credentials.js +0 -720
  194. package/dist/utils/docker-credentials.js.map +0 -1
  195. package/dist/utils/emulated-device.js +0 -91
  196. package/dist/utils/emulated-device.js.map +0 -1
  197. package/dist/utils/index.js +0 -147
  198. package/dist/utils/index.js.map +0 -1
  199. package/dist/utils/modules.js +0 -36
  200. package/dist/utils/modules.js.map +0 -1
  201. package/dist/utils/org-selector.js +0 -108
  202. package/dist/utils/org-selector.js.map +0 -1
  203. package/dist/utils/proxy.js +0 -31
  204. package/dist/utils/proxy.js.map +0 -1
  205. package/dist/utils/registry-credentials.js +0 -113
  206. package/dist/utils/registry-credentials.js.map +0 -1
  207. package/dist/utils/simulator-config.js +0 -142
  208. package/dist/utils/simulator-config.js.map +0 -1
  209. package/dist/utils/statuses.js +0 -124
  210. package/dist/utils/statuses.js.map +0 -1
  211. package/dist/utils/templates.js +0 -197
  212. package/dist/utils/templates.js.map +0 -1
  213. package/dist/utils/tenant-storage.js +0 -88
  214. package/dist/utils/tenant-storage.js.map +0 -1
  215. package/dist/utils/vm.js +0 -434
  216. package/dist/utils/vm.js.map +0 -1
  217. package/dist/utils/with-spinner.js +0 -20
  218. package/dist/utils/with-spinner.js.map +0 -1
@@ -1,959 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.default = createVm;
40
- const fs_1 = __importDefault(require("fs"));
41
- const path_1 = __importDefault(require("path"));
42
- const child_process_1 = require("child_process");
43
- const chalk_1 = __importDefault(require("chalk"));
44
- const uuid_1 = require("uuid");
45
- const rimraf_1 = require("rimraf");
46
- const crypto_1 = __importDefault(require("crypto"));
47
- const vm_1 = require("../../utils/vm");
48
- /**
49
- * Determines the architecture to use for the VM
50
- * Returns the specified architecture if it's valid, or null if no valid arch was provided
51
- */
52
- function validateArch(userArch) {
53
- if (userArch) {
54
- if (['amd64', 'arm64'].includes(userArch)) {
55
- return userArch;
56
- }
57
- console.warn(chalk_1.default.yellow(`Unsupported architecture: ${userArch}, please choose a supported option`));
58
- }
59
- return null;
60
- }
61
- /**
62
- * Detect the host system architecture
63
- */
64
- function getHostArch() {
65
- const systemArch = process.arch;
66
- if (systemArch === 'x64')
67
- return 'amd64';
68
- if (systemArch === 'arm64')
69
- return 'arm64';
70
- console.warn(chalk_1.default.yellow(`Unusual host architecture detected: ${systemArch}`));
71
- return 'amd64'; // Default to amd64 as fallback
72
- }
73
- /**
74
- * Gets the download URL and version for PhyOS based on architecture
75
- */
76
- async function getPhyOsDownloadUrl(arch, versionStr) {
77
- // If a specific version is provided, use it directly (only if it's a valid string)
78
- if (versionStr && typeof versionStr === 'string' && /^\d+\.\d+\.\d+$/.test(versionStr)) {
79
- const url = `https://os.phygrid.com/phyos.${versionStr}.qemu${arch}.tar.gz`;
80
- return { url, version: versionStr };
81
- }
82
- // Otherwise, use the short URL and follow redirects to get the latest version
83
- const shortUrl = `https://qr.run/phyos-qemu${arch}-latest`;
84
- console.log(chalk_1.default.blue(`Getting latest version URL from ${shortUrl}...`));
85
- try {
86
- // Use curl to follow redirect and get the final URL
87
- const curlOutput = (0, child_process_1.execSync)(`curl -sI "${shortUrl}" | grep -i "^location:" | tail -n1`, { encoding: 'utf8' });
88
- const redirectUrl = curlOutput.trim().split(/:\s+/)[1];
89
- if (!redirectUrl) {
90
- throw new Error('Failed to get redirect URL for PhyOS');
91
- }
92
- console.log(chalk_1.default.blue(`Redirect URL: ${redirectUrl}`));
93
- // Extract version from the URL (format: phyos.1.2.3.qemuXXX.tar.gz)
94
- const versionMatch = redirectUrl.match(/phyos\.([0-9]+\.[0-9]+\.[0-9]+)\.qemu/i);
95
- if (!versionMatch || !versionMatch[1]) {
96
- throw new Error(`Failed to extract version from URL: ${redirectUrl}`);
97
- }
98
- const version = versionMatch[1];
99
- console.log(chalk_1.default.blue(`Detected version: ${version}. Please wait while we verify the checksum...`));
100
- return { url: redirectUrl, version };
101
- }
102
- catch (error) {
103
- console.error(chalk_1.default.red(`Error following redirect: ${error.message}`));
104
- throw new Error(`Failed to determine latest PhyOS version: ${error.message}`);
105
- }
106
- }
107
- /**
108
- * Downloads a file using curl with progress bar
109
- */
110
- function downloadWithCurl(url, outputPath) {
111
- return new Promise((resolve, reject) => {
112
- console.log(chalk_1.default.blue(`Downloading from ${url}...`));
113
- // Create curl process with progress bar
114
- const curl = (0, child_process_1.spawn)('curl', [
115
- '--progress-bar', // Show progress bar
116
- '-L', // Follow redirects
117
- '-o', outputPath, // Output file
118
- url // URL to download
119
- ], {
120
- stdio: ['ignore', 'inherit', 'inherit'] // Show progress in terminal
121
- });
122
- curl.on('close', (code) => {
123
- if (code === 0) {
124
- resolve();
125
- }
126
- else {
127
- reject(new Error(`curl exited with code ${code}`));
128
- }
129
- });
130
- curl.on('error', (err) => {
131
- reject(new Error(`Failed to execute curl: ${err.message}`));
132
- });
133
- });
134
- }
135
- /**
136
- * Gets the SHA256 checksum URL for the tar file
137
- */
138
- function getSha256Url(tarUrl) {
139
- return `${tarUrl}.sha256`;
140
- }
141
- /**
142
- * Verifies the SHA256 checksum of a file
143
- */
144
- async function verifySha256(filePath, sha256Path) {
145
- try {
146
- const sha256Content = fs_1.default.readFileSync(sha256Path, 'utf8').trim();
147
- const expectedHash = sha256Content.split(/\s+/)[0].trim();
148
- if (!expectedHash || expectedHash.length !== 64)
149
- return false;
150
- const fileBuffer = fs_1.default.readFileSync(filePath);
151
- const hash = crypto_1.default.createHash('sha256');
152
- hash.update(fileBuffer);
153
- const actualHash = hash.digest('hex');
154
- return actualHash.toLowerCase() === expectedHash.toLowerCase();
155
- }
156
- catch (error) {
157
- console.warn(chalk_1.default.yellow(`Warning: Failed to verify SHA256 checksum: ${error.message}`));
158
- return false;
159
- }
160
- }
161
- /**
162
- * Computes and saves SHA256 hash for a file
163
- */
164
- function computeAndSaveSha256(filePath, sha256Path) {
165
- return new Promise((resolve, reject) => {
166
- const hash = crypto_1.default.createHash('sha256');
167
- let fileStream;
168
- try {
169
- fileStream = fs_1.default.createReadStream(filePath, { highWaterMark: 1024 * 1024 }); // 1MB chunks
170
- }
171
- catch (error) {
172
- console.warn(chalk_1.default.yellow(`Warning: Failed to create read stream for ${path_1.default.basename(filePath)}: ${error.message}`));
173
- reject(error);
174
- return;
175
- }
176
- fileStream.on('data', (chunk) => {
177
- hash.update(chunk);
178
- });
179
- fileStream.on('end', () => {
180
- try {
181
- const computedHash = hash.digest('hex');
182
- fs_1.default.writeFileSync(sha256Path, computedHash);
183
- resolve();
184
- }
185
- catch (error) {
186
- console.warn(chalk_1.default.yellow(`Warning: Failed to write SHA256 file for ${path_1.default.basename(filePath)}: ${error.message}`));
187
- reject(error);
188
- }
189
- });
190
- fileStream.on('error', (error) => {
191
- console.warn(chalk_1.default.yellow(`Warning: Failed to compute SHA256 for ${path_1.default.basename(filePath)}: ${error.message}`));
192
- reject(error);
193
- });
194
- });
195
- }
196
- /**
197
- * Verifies a file against its saved SHA256 hash (handles large files by reading in chunks)
198
- */
199
- function verifyFileSha256(filePath, sha256Path) {
200
- return new Promise((resolve) => {
201
- try {
202
- if (!fs_1.default.existsSync(sha256Path)) {
203
- resolve(false);
204
- return;
205
- }
206
- const expectedHash = fs_1.default.readFileSync(sha256Path, 'utf8').trim();
207
- if (!expectedHash || expectedHash.length !== 64) {
208
- resolve(false);
209
- return;
210
- }
211
- const hash = crypto_1.default.createHash('sha256');
212
- const fileStream = fs_1.default.createReadStream(filePath);
213
- fileStream.on('data', (chunk) => {
214
- hash.update(chunk);
215
- });
216
- fileStream.on('end', () => {
217
- const actualHash = hash.digest('hex');
218
- resolve(actualHash.toLowerCase() === expectedHash.toLowerCase());
219
- });
220
- fileStream.on('error', () => {
221
- resolve(false);
222
- });
223
- }
224
- catch {
225
- resolve(false);
226
- }
227
- });
228
- }
229
- /**
230
- * Copy a file from source to destination
231
- */
232
- function copyFile(sourcePath, destinationPath) {
233
- fs_1.default.copyFileSync(sourcePath, destinationPath);
234
- }
235
- /**
236
- * Extract tar file using system tar command
237
- */
238
- function extractTarFile(tarFilePath, outputDir) {
239
- return new Promise((resolve, reject) => {
240
- console.log(chalk_1.default.blue(`Extracting ${path_1.default.basename(tarFilePath)} to ${outputDir}...`));
241
- let stderrOutput = '';
242
- const tar = (0, child_process_1.spawn)('tar', [
243
- '-xzf', // Extract gzipped tar
244
- tarFilePath, // Input file
245
- '-C', outputDir // Output directory
246
- ], {
247
- stdio: ['ignore', 'inherit', 'pipe']
248
- });
249
- tar.stderr?.on('data', (data) => {
250
- stderrOutput += data.toString();
251
- process.stderr.write(data);
252
- });
253
- tar.on('close', (code) => {
254
- if (code === 0) {
255
- resolve();
256
- }
257
- else {
258
- const errorMsg = stderrOutput.trim()
259
- ? `tar extraction failed with code ${code}: ${stderrOutput.trim()}`
260
- : `tar extraction failed with code ${code}`;
261
- reject(new Error(errorMsg));
262
- }
263
- });
264
- tar.on('error', (err) => {
265
- reject(new Error(`Failed to execute tar: ${err.message}`));
266
- });
267
- });
268
- }
269
- /**
270
- * Gets the current disk size in GB
271
- */
272
- function getCurrentDiskSizeGB(diskFilePath) {
273
- if (!fs_1.default.existsSync(diskFilePath)) {
274
- return 0;
275
- }
276
- const stats = fs_1.default.statSync(diskFilePath);
277
- const sizeInBytes = stats.size;
278
- const sizeInGB = sizeInBytes / (1024 * 1024 * 1024);
279
- return Math.round(sizeInGB * 100) / 100; // Round to 2 decimal places
280
- }
281
- /**
282
- * Expands the disk size using dd command
283
- */
284
- function expandDiskSize(diskFilePath, targetSizeGB) {
285
- return new Promise((resolve, reject) => {
286
- console.log(chalk_1.default.blue(`Expanding disk to ${targetSizeGB}GB...`));
287
- // Calculate the additional size needed in bytes
288
- const currentSizeGB = getCurrentDiskSizeGB(diskFilePath);
289
- const additionalSizeBytes = Math.ceil((targetSizeGB - currentSizeGB) * 1024 * 1024 * 1024);
290
- if (additionalSizeBytes <= 0) {
291
- resolve(); // No expansion needed
292
- return;
293
- }
294
- // Use a more compatible dd command approach
295
- // Calculate block size and count to avoid the '1m' format issue
296
- const blockSize = 1024 * 1024; // 1MB in bytes
297
- const count = Math.ceil(additionalSizeBytes / blockSize);
298
- const dd = (0, child_process_1.spawn)('dd', [
299
- 'if=/dev/zero',
300
- `bs=${blockSize}`,
301
- `count=${count}`,
302
- '>>',
303
- diskFilePath
304
- ], {
305
- stdio: ['ignore', 'inherit', 'inherit'],
306
- shell: true
307
- });
308
- dd.on('close', (code) => {
309
- if (code === 0) {
310
- console.log(chalk_1.default.green(`✅ Disk expanded to ${targetSizeGB}GB`));
311
- resolve();
312
- }
313
- else {
314
- reject(new Error(`dd command failed with code ${code}`));
315
- }
316
- });
317
- dd.on('error', (err) => {
318
- reject(new Error(`Failed to execute dd: ${err.message}`));
319
- });
320
- });
321
- }
322
- /**
323
- * Downloads and extracts a PhyOS image
324
- */
325
- async function downloadPhyOS(name, arch, versionStr, retryCount = 0, silent = false) {
326
- if (!silent) {
327
- console.log(chalk_1.default.blue('Determining PhyOS version and download URL...'));
328
- }
329
- // Get the download URL and version
330
- const { url, version: detectedVersion } = await getPhyOsDownloadUrl(arch, versionStr);
331
- // Get paths for cache and target VM
332
- const vmDir = (0, vm_1.getVmDir)(name);
333
- const cacheTarPath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'tar');
334
- const cacheSha256Path = `${cacheTarPath}.sha256`;
335
- const cacheImagePath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'image');
336
- const cacheDiskPath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'disk', 'zfs');
337
- const cacheInitramfsPath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'initramfs');
338
- const tarFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.tar.gz`);
339
- const imageFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.Image`);
340
- const diskFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.zfs`);
341
- const initramfsFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.cpio.gz`);
342
- // Ensure the VM directory exists
343
- if (!fs_1.default.existsSync(vmDir)) {
344
- fs_1.default.mkdirSync(vmDir, { recursive: true });
345
- }
346
- // Check if we need to download or can use cache
347
- let needsDownload = true;
348
- let needsExtract = true;
349
- // Check if cache has the files already
350
- if (fs_1.default.existsSync(cacheImagePath) && fs_1.default.existsSync(cacheDiskPath) && fs_1.default.existsSync(cacheInitramfsPath)) {
351
- if (!silent) {
352
- console.log(chalk_1.default.green(`Found cached PhyOS ${detectedVersion} files, verifying checksums...`));
353
- }
354
- // Verify each cached file against its saved hash
355
- const imageSha256Path = `${cacheImagePath}.sha256`;
356
- const diskSha256Path = `${cacheDiskPath}.sha256`;
357
- const initramfsSha256Path = `${cacheInitramfsPath}.sha256`;
358
- const imageValid = await verifyFileSha256(cacheImagePath, imageSha256Path);
359
- const diskValid = await verifyFileSha256(cacheDiskPath, diskSha256Path);
360
- const initramfsValid = await verifyFileSha256(cacheInitramfsPath, initramfsSha256Path);
361
- if (!imageValid || !diskValid || !initramfsValid) {
362
- if (!silent) {
363
- console.log(chalk_1.default.yellow(`Cached files verification failed, will redownload...`));
364
- }
365
- try {
366
- if (fs_1.default.existsSync(cacheImagePath))
367
- fs_1.default.unlinkSync(cacheImagePath);
368
- if (fs_1.default.existsSync(cacheDiskPath))
369
- fs_1.default.unlinkSync(cacheDiskPath);
370
- if (fs_1.default.existsSync(cacheInitramfsPath))
371
- fs_1.default.unlinkSync(cacheInitramfsPath);
372
- if (fs_1.default.existsSync(imageSha256Path))
373
- fs_1.default.unlinkSync(imageSha256Path);
374
- if (fs_1.default.existsSync(diskSha256Path))
375
- fs_1.default.unlinkSync(diskSha256Path);
376
- if (fs_1.default.existsSync(initramfsSha256Path))
377
- fs_1.default.unlinkSync(initramfsSha256Path);
378
- }
379
- catch (error) {
380
- if (!silent) {
381
- console.warn(chalk_1.default.yellow(`Warning: Failed to remove invalid cache files: ${error.message}`));
382
- }
383
- }
384
- // Continue to download/extract flow
385
- }
386
- else {
387
- if (!silent) {
388
- console.log(chalk_1.default.green('Checksum verification passed!'));
389
- console.log(chalk_1.default.blue('Copying files from cache to VM directory...'));
390
- }
391
- needsDownload = false;
392
- needsExtract = false;
393
- // Copy files from cache to VM directory
394
- copyFile(cacheImagePath, imageFile);
395
- copyFile(cacheDiskPath, diskFile);
396
- copyFile(cacheInitramfsPath, initramfsFile);
397
- }
398
- }
399
- // Check if we have the tar file but still need to extract
400
- else if (fs_1.default.existsSync(cacheTarPath)) {
401
- console.log(chalk_1.default.green(`Found cached PhyOS ${detectedVersion} tar file, verifying checksum...`));
402
- // Verify cached tar file
403
- let isValid = false;
404
- if (fs_1.default.existsSync(cacheSha256Path)) {
405
- isValid = await verifySha256(cacheTarPath, cacheSha256Path);
406
- }
407
- else {
408
- // Download SHA256 and verify
409
- const sha256Url = getSha256Url(url);
410
- try {
411
- await downloadWithCurl(sha256Url, cacheSha256Path);
412
- isValid = await verifySha256(cacheTarPath, cacheSha256Path);
413
- }
414
- catch {
415
- isValid = false;
416
- }
417
- }
418
- if (!isValid) {
419
- console.log(chalk_1.default.yellow(`Cached tar file verification failed, will redownload...`));
420
- try {
421
- if (fs_1.default.existsSync(cacheTarPath))
422
- fs_1.default.unlinkSync(cacheTarPath);
423
- if (fs_1.default.existsSync(cacheSha256Path))
424
- fs_1.default.unlinkSync(cacheSha256Path);
425
- }
426
- catch (error) {
427
- console.warn(chalk_1.default.yellow(`Warning: Failed to remove invalid cache files: ${error.message}`));
428
- }
429
- needsDownload = true;
430
- }
431
- else {
432
- console.log(chalk_1.default.green('Checksum verification passed!'));
433
- needsDownload = false;
434
- copyFile(cacheTarPath, tarFile);
435
- }
436
- }
437
- // Download if needed
438
- if (needsDownload) {
439
- // Download SHA256 checksum file first to verify the file integrity
440
- const sha256Url = getSha256Url(url);
441
- await downloadWithCurl(sha256Url, cacheSha256Path);
442
- // Download the tar.gz file using curl with progress bar
443
- console.log(chalk_1.default.blue(`Downloading PhyOS ${detectedVersion}...`));
444
- await downloadWithCurl(url, cacheTarPath);
445
- // Verify the download was successful
446
- if (!fs_1.default.existsSync(cacheTarPath) || fs_1.default.statSync(cacheTarPath).size === 0) {
447
- throw new Error('Download failed or resulted in an empty file');
448
- }
449
- // Verify SHA256 checksum
450
- console.log(chalk_1.default.blue('Verifying SHA256 checksum...'));
451
- if (!(await verifySha256(cacheTarPath, cacheSha256Path))) {
452
- // Delete invalid files and retry once
453
- try {
454
- if (fs_1.default.existsSync(cacheTarPath))
455
- fs_1.default.unlinkSync(cacheTarPath);
456
- if (fs_1.default.existsSync(cacheSha256Path))
457
- fs_1.default.unlinkSync(cacheSha256Path);
458
- }
459
- catch (error) {
460
- console.warn(chalk_1.default.yellow(`Warning: Failed to remove invalid files: ${error.message}`));
461
- }
462
- console.log(chalk_1.default.yellow('Checksum verification failed, redownloading...'));
463
- await downloadWithCurl(sha256Url, cacheSha256Path);
464
- await downloadWithCurl(url, cacheTarPath);
465
- if (!(await verifySha256(cacheTarPath, cacheSha256Path))) {
466
- throw new Error('SHA256 checksum verification failed after retry');
467
- }
468
- }
469
- console.log(chalk_1.default.green('Checksum verification passed!'));
470
- // Copy to VM directory for extraction
471
- copyFile(cacheTarPath, tarFile);
472
- }
473
- // Extract if needed
474
- if (needsExtract) {
475
- console.log(chalk_1.default.green('Extracting files...'));
476
- try {
477
- // Extract the tar.gz file using system tar command
478
- await extractTarFile(tarFile, vmDir);
479
- // Check if the expected files exist after extraction
480
- if (!fs_1.default.existsSync(imageFile) || !fs_1.default.existsSync(diskFile) || !fs_1.default.existsSync(initramfsFile)) {
481
- console.error(chalk_1.default.red(`❗️Required files (${path_1.default.basename(imageFile)}, ${path_1.default.basename(diskFile)}, ${path_1.default.basename(initramfsFile)}) not found in the extracted archive at ${vmDir}`));
482
- // Clean up potentially corrupt extraction? Maybe remove extracted files or vmDir?
483
- try {
484
- if (fs_1.default.existsSync(imageFile))
485
- fs_1.default.unlinkSync(imageFile);
486
- if (fs_1.default.existsSync(diskFile))
487
- fs_1.default.unlinkSync(diskFile);
488
- if (fs_1.default.existsSync(initramfsFile))
489
- fs_1.default.unlinkSync(initramfsFile);
490
- // Consider removing tarFile as well if it came from cache but extraction failed
491
- // if (!needsDownload && fs.existsSync(tarFile)) fs.unlinkSync(tarFile);
492
- }
493
- catch (cleanupError) {
494
- console.warn(chalk_1.default.yellow(`Warning: Failed to clean up incomplete files: ${cleanupError.message}`));
495
- }
496
- throw new Error('Required files not found after extraction');
497
- }
498
- // Cache the extracted files and compute their SHA256 hashes
499
- console.log(chalk_1.default.green('Caching extracted files for future use...'));
500
- copyFile(imageFile, cacheImagePath);
501
- copyFile(diskFile, cacheDiskPath);
502
- copyFile(initramfsFile, cacheInitramfsPath);
503
- // Compute and save SHA256 hashes for verification (using streams for large files)
504
- console.log(chalk_1.default.blue('Computing checksums for cached files...'));
505
- try {
506
- await computeAndSaveSha256(cacheImagePath, `${cacheImagePath}.sha256`);
507
- }
508
- catch (error) {
509
- console.warn(chalk_1.default.yellow(`Warning: Failed to compute SHA256 for image file: ${error.message}`));
510
- }
511
- try {
512
- await computeAndSaveSha256(cacheDiskPath, `${cacheDiskPath}.sha256`);
513
- }
514
- catch (error) {
515
- console.warn(chalk_1.default.yellow(`Warning: Failed to compute SHA256 for disk file: ${error.message}`));
516
- }
517
- try {
518
- await computeAndSaveSha256(cacheInitramfsPath, `${cacheInitramfsPath}.sha256`);
519
- }
520
- catch (error) {
521
- console.warn(chalk_1.default.yellow(`Warning: Failed to compute SHA256 for initramfs file: ${error.message}`));
522
- }
523
- }
524
- catch (extractError) {
525
- // Check for corruption errors and retry once
526
- const errorMsg = extractError.message.toLowerCase();
527
- const isCorruption = errorMsg.includes('truncated') || errorMsg.includes('corrupt') ||
528
- errorMsg.includes('gzip') || errorMsg.includes('invalid') ||
529
- errorMsg.includes('unexpected end');
530
- if (isCorruption && retryCount < 1) {
531
- console.error(chalk_1.default.red(`Corrupted file detected: ${extractError.message}`));
532
- console.log(chalk_1.default.yellow('Removing corrupted file and redownloading...'));
533
- try {
534
- if (fs_1.default.existsSync(tarFile))
535
- fs_1.default.unlinkSync(tarFile);
536
- if (fs_1.default.existsSync(cacheTarPath))
537
- fs_1.default.unlinkSync(cacheTarPath);
538
- if (fs_1.default.existsSync(cacheSha256Path))
539
- fs_1.default.unlinkSync(cacheSha256Path);
540
- }
541
- catch (error) {
542
- console.warn(chalk_1.default.yellow(`Warning: Failed to remove corrupted files: ${error.message}`));
543
- }
544
- return downloadPhyOS(name, arch, versionStr, retryCount + 1);
545
- }
546
- console.error(chalk_1.default.red(`Error during extraction: ${extractError.message}`));
547
- throw extractError;
548
- }
549
- finally {
550
- // Clean up the downloaded tar file from the VM directory regardless of extraction success/failure
551
- // unless it was copied from cache initially
552
- if (needsDownload && fs_1.default.existsSync(tarFile)) {
553
- try {
554
- fs_1.default.unlinkSync(tarFile);
555
- }
556
- catch (unlinkError) {
557
- console.warn(chalk_1.default.yellow(`Warning: Failed to remove temporary tar file ${tarFile}: ${unlinkError.message}`));
558
- }
559
- }
560
- }
561
- }
562
- if (!silent) {
563
- console.log(chalk_1.default.green(`PhyOS ${detectedVersion} ready in ${vmDir}`));
564
- }
565
- // Return paths to the main files
566
- return { version: detectedVersion, imageFile, diskFile, initramfsFile }; // Return initramfs path
567
- }
568
- /**
569
- * Creates a single VM with the specified parameters
570
- */
571
- async function createSingleVm(vmName, arch, memory, headless, versionStr, serialNumber, storageGB) {
572
- console.log(chalk_1.default.blue(`Creating VM: ${vmName}...`));
573
- // Download and extract PhyOS
574
- const downloadedFiles = await downloadPhyOS(vmName, arch, versionStr);
575
- // Get the actual disk size for display
576
- let actualDiskSizeGB = getCurrentDiskSizeGB(downloadedFiles.diskFile);
577
- // Expand disk size if specified
578
- if (storageGB !== undefined) {
579
- console.log(chalk_1.default.blue(`Current disk size: ${actualDiskSizeGB}GB`));
580
- if (storageGB < actualDiskSizeGB) {
581
- throw new Error(`Cannot shrink disk size. Current size: ${actualDiskSizeGB}GB, requested: ${storageGB}GB. Please specify a size larger than or equal to ${actualDiskSizeGB}GB.`);
582
- }
583
- if (storageGB > actualDiskSizeGB) {
584
- await expandDiskSize(downloadedFiles.diskFile, storageGB);
585
- // Update the actual disk size for display
586
- const finalDiskSizeGB = getCurrentDiskSizeGB(downloadedFiles.diskFile);
587
- actualDiskSizeGB = finalDiskSizeGB;
588
- }
589
- // If storageGB === actualDiskSizeGB, no expansion needed
590
- }
591
- // Get timestamp for creation
592
- const createdTime = new Date().toISOString();
593
- // Create VM configuration (legacy, to maintain backward compatibility)
594
- const vmConfig = {
595
- name: vmName,
596
- version: downloadedFiles.version,
597
- arch,
598
- memory,
599
- image: path_1.default.basename(downloadedFiles.imageFile),
600
- disk: path_1.default.basename(downloadedFiles.diskFile),
601
- initramfs: path_1.default.basename(downloadedFiles.initramfsFile),
602
- created: createdTime
603
- };
604
- // Save VM configuration (legacy format)
605
- const configPath = (0, vm_1.getVmConfigPath)(vmName);
606
- fs_1.default.writeFileSync(configPath, JSON.stringify(vmConfig, null, 2));
607
- // Create and save VM settings with memory and serial in the new format
608
- const vmSettings = {
609
- memory,
610
- serial: serialNumber,
611
- arch,
612
- version: downloadedFiles.version,
613
- created: createdTime,
614
- headless,
615
- storage: storageGB
616
- };
617
- // Save settings to settings.json
618
- const settingsPath = (0, vm_1.getVmSettingsPath)(vmName);
619
- fs_1.default.writeFileSync(settingsPath, JSON.stringify(vmSettings, null, 2));
620
- // For backward compatibility, also save serial to serial.txt
621
- const serialPath = (0, vm_1.getVmSerialPath)(vmName);
622
- fs_1.default.writeFileSync(serialPath, serialNumber);
623
- return { version: downloadedFiles.version, actualDiskSizeGB };
624
- }
625
- /**
626
- * Command to create one or more PhyOS VMs with interactive prompts for missing options
627
- */
628
- async function createVm(name, options) {
629
- try {
630
- // Import inquirer dynamically since it's a heavy dependency
631
- const inquirer = (await Promise.resolve().then(() => __importStar(require('inquirer')))).default;
632
- // Ensure VM directory exists
633
- (0, vm_1.ensureVmDir)();
634
- // Parse count parameter (default to 1)
635
- const count = options.count ? parseInt(options.count, 10) : 1;
636
- if (isNaN(count) || count < 1 || count > 50) {
637
- throw new Error('Count must be a number between 1 and 50');
638
- }
639
- // Get existing VMs for validation and suggestions
640
- const existingVms = (0, vm_1.listVms)();
641
- const vmNameValidator = (input) => {
642
- if (!input || input.trim() === '') {
643
- return 'VM name is required';
644
- }
645
- if (input.includes(' ')) {
646
- return 'VM name cannot contain spaces';
647
- }
648
- if ((0, vm_1.vmExists)(input)) {
649
- return `VM with name "${input}" already exists`;
650
- }
651
- return true;
652
- };
653
- // If name is not provided via command line, prompt for it
654
- if (!name) {
655
- const answers = await inquirer.prompt([
656
- {
657
- type: 'input',
658
- name: 'name',
659
- message: count > 1 ? 'Enter a base name for the VMs:' : 'Enter a name for the VM:',
660
- validate: (input) => {
661
- if (!input || input.trim() === '') {
662
- return 'VM name is required';
663
- }
664
- if (input.includes(' ')) {
665
- return 'VM name cannot contain spaces';
666
- }
667
- // For multiple VMs, check if any of the suffixed names would conflict
668
- if (count > 1) {
669
- for (let i = 1; i <= count; i++) {
670
- const suffixedName = `${input}-${i}`;
671
- if ((0, vm_1.vmExists)(suffixedName)) {
672
- return `VM with name "${suffixedName}" already exists`;
673
- }
674
- }
675
- }
676
- else {
677
- if ((0, vm_1.vmExists)(input)) {
678
- return `VM with name "${input}" already exists`;
679
- }
680
- }
681
- return true;
682
- }
683
- }
684
- ]);
685
- name = answers.name;
686
- }
687
- else {
688
- // Validate the base name and check for conflicts with suffixed names
689
- if (count > 1) {
690
- for (let i = 1; i <= count; i++) {
691
- const suffixedName = `${name}-${i}`;
692
- if ((0, vm_1.vmExists)(suffixedName)) {
693
- throw new Error(`VM with name "${suffixedName}" already exists`);
694
- }
695
- }
696
- }
697
- else {
698
- if (vmNameValidator(name) !== true) {
699
- throw new Error(typeof vmNameValidator(name) === 'string' ? vmNameValidator(name) : 'Invalid VM name');
700
- }
701
- }
702
- }
703
- console.log(chalk_1.default.blue(`Creating ${count} PhyOS VM${count > 1 ? 's' : ''} with base name: ${name}`));
704
- // Prompt for architecture if not provided
705
- let arch;
706
- if (!options.arch) {
707
- const hostArch = getHostArch();
708
- const answers = await inquirer.prompt([
709
- {
710
- type: 'list',
711
- name: 'arch',
712
- message: 'Select VM architecture (can be different from host):',
713
- default: hostArch,
714
- choices: [
715
- {
716
- name: `amd64 (x86_64) ${hostArch === 'amd64' ? '[Host Architecture]' : '[Emulated]'}`,
717
- value: 'amd64'
718
- },
719
- {
720
- name: `arm64 (AArch64) ${hostArch === 'arm64' ? '[Host Architecture]' : '[Emulated]'}`,
721
- value: 'arm64'
722
- }
723
- ]
724
- }
725
- ]);
726
- arch = answers.arch;
727
- }
728
- else {
729
- const validArch = validateArch(options.arch);
730
- if (!validArch) {
731
- throw new Error(`Unsupported architecture: ${options.arch}. Supported values are: amd64, arm64`);
732
- }
733
- arch = validArch;
734
- }
735
- const hostArch = getHostArch();
736
- const isEmulated = arch !== hostArch;
737
- console.log(chalk_1.default.blue(`Using architecture: ${arch}${isEmulated ? ' (emulated)' : ' (native)'}`));
738
- // Force headless mode for cross-architecture emulation in both directions
739
- const forceHeadless = isEmulated && ((arch === 'amd64' && hostArch === 'arm64') ||
740
- (arch === 'arm64' && hostArch === 'amd64'));
741
- // If we're going to force headless mode, inform the user
742
- if (forceHeadless) {
743
- console.log(chalk_1.default.yellow(`⚠️ Note: Cross-architecture emulation (${arch} on ${hostArch}) requires headless mode`));
744
- console.log(chalk_1.default.yellow(` This VM will be configured as headless automatically`));
745
- }
746
- // Prompt for version if not provided
747
- let versionStr;
748
- if (!options.phyosVersion) {
749
- const answers = await inquirer.prompt([
750
- {
751
- type: 'confirm',
752
- name: 'useLatest',
753
- message: 'Use latest PhyOS version?',
754
- default: true
755
- },
756
- {
757
- type: 'input',
758
- name: 'version',
759
- message: 'Enter specific PhyOS version (e.g. 5.0.127):',
760
- when: (answers) => !answers.useLatest,
761
- validate: (input) => {
762
- if (!input || !/^\d+\.\d+\.\d+$/.test(input)) {
763
- return 'Please enter a valid version number (format: X.Y.Z)';
764
- }
765
- return true;
766
- }
767
- }
768
- ]);
769
- versionStr = answers.useLatest ? undefined : answers.version;
770
- }
771
- else {
772
- versionStr = options.phyosVersion;
773
- }
774
- // Prompt for memory if not provided
775
- let memory;
776
- if (!options.memory) {
777
- const answers = await inquirer.prompt([
778
- {
779
- type: 'input',
780
- name: 'memory',
781
- message: 'Enter memory allocation in MB:',
782
- default: '2048',
783
- validate: (input) => {
784
- const mem = parseInt(input, 10);
785
- if (isNaN(mem) || mem < 512 || mem > 32768) {
786
- return 'Please enter a valid memory size between 512MB and 32GB';
787
- }
788
- return true;
789
- }
790
- }
791
- ]);
792
- memory = parseInt(answers.memory, 10);
793
- }
794
- else {
795
- memory = parseInt(options.memory, 10);
796
- if (isNaN(memory) || memory < 512 || memory > 32768) {
797
- throw new Error('Memory must be a number between 512MB and 32GB');
798
- }
799
- }
800
- // Prompt for storage if not provided
801
- let storageGB;
802
- if (!options.storage) {
803
- // For multiple VMs, don't prompt - use current image size
804
- if (count > 1) {
805
- storageGB = undefined; // Use current size, no expansion
806
- }
807
- else {
808
- // Get the actual disk size for the default value
809
- // We need to download/extract first to know the current size
810
- const tempDownloadedFiles = await downloadPhyOS('temp', arch, versionStr, 0, true);
811
- const currentDiskSizeGB = getCurrentDiskSizeGB(tempDownloadedFiles.diskFile);
812
- // Clean up temp files
813
- const tempDir = (0, vm_1.getVmDir)('temp');
814
- if (fs_1.default.existsSync(tempDir)) {
815
- (0, rimraf_1.rimrafSync)(tempDir);
816
- }
817
- const answers = await inquirer.prompt([
818
- {
819
- type: 'input',
820
- name: 'storage',
821
- message: 'Enter storage allocation in GB (press Enter to use current size):',
822
- default: String(currentDiskSizeGB),
823
- validate: (input) => {
824
- if (!input || input.trim() === '') {
825
- return true; // Allow empty input to use default
826
- }
827
- const storage = parseFloat(input);
828
- if (isNaN(storage) || storage < 1 || storage > 100) {
829
- return 'Please enter a valid storage size between 1GB and 100GB';
830
- }
831
- return true;
832
- }
833
- }
834
- ]);
835
- // If user left it empty or entered the same as current size, don't expand
836
- if (!answers.storage || answers.storage.trim() === '' || parseFloat(answers.storage) === currentDiskSizeGB) {
837
- storageGB = undefined; // Use current size, no expansion
838
- }
839
- else {
840
- storageGB = parseFloat(answers.storage);
841
- }
842
- }
843
- }
844
- else {
845
- storageGB = parseFloat(options.storage);
846
- if (isNaN(storageGB) || storageGB < 1 || storageGB > 100) {
847
- throw new Error('Storage must be a number between 1GB and 100GB');
848
- }
849
- }
850
- // Determine headless mode setting
851
- let headless;
852
- // If x86 on ARM, force headless mode
853
- if (forceHeadless) {
854
- headless = true;
855
- }
856
- // Otherwise use options or default to non-headless
857
- else if (options.headless !== undefined) {
858
- headless = options.headless;
859
- }
860
- else {
861
- headless = false; // Default to non-headless mode
862
- console.log(chalk_1.default.yellow(`⚠️ VM will run with graphical interface (non-headless mode by default)`));
863
- console.log(chalk_1.default.yellow(` Use --headless flag to run without display`));
864
- }
865
- // Create VMs in a loop
866
- const createdVms = [];
867
- for (let i = 1; i <= count; i++) {
868
- const vmName = count > 1 ? `${name}-${i}` : name;
869
- const serialNumber = `PHY${(0, uuid_1.v4)().replace(/-/g, '').toUpperCase()}`;
870
- console.log(chalk_1.default.blue(`\n[${i}/${count}] Generating unique serial number for ${vmName}: ${serialNumber}`));
871
- try {
872
- const result = await createSingleVm(vmName, arch, memory, headless, versionStr, serialNumber, storageGB);
873
- createdVms.push({
874
- name: vmName,
875
- serial: serialNumber,
876
- version: result.version,
877
- diskSizeGB: result.actualDiskSizeGB
878
- });
879
- console.log(chalk_1.default.green(`✅ VM "${vmName}" created successfully!`));
880
- }
881
- catch (error) {
882
- console.error(chalk_1.default.red(`❗️Failed to create VM "${vmName}": ${error.message}`));
883
- // Attempt to clean up VM directory if creation failed
884
- const vmDir = (0, vm_1.getVmDir)(vmName);
885
- if (fs_1.default.existsSync(vmDir)) {
886
- try {
887
- (0, rimraf_1.rimrafSync)(vmDir);
888
- console.log(chalk_1.default.yellow(`Cleaned up incomplete VM directory: ${vmDir}`));
889
- }
890
- catch (cleanupError) {
891
- console.warn(chalk_1.default.yellow(`Warning: Failed to clean up VM directory ${vmDir}: ${cleanupError.message}`));
892
- }
893
- }
894
- // Continue with other VMs instead of exiting
895
- continue;
896
- }
897
- }
898
- if (createdVms.length === 0) {
899
- throw new Error('Failed to create any VMs');
900
- }
901
- // Show success message(s)
902
- if (count === 1) {
903
- const vm = createdVms[0];
904
- const startupInfo = headless
905
- ? `\nSince this VM is in headless mode:
906
- - Use 'phy vm shell ${vm.name}' to connect to the console
907
- - Use 'phy vm logs ${vm.name}' to view logs`
908
- : `\nTo view logs: phy vm logs ${vm.name}`;
909
- console.log(chalk_1.default.green(`
910
- ✅ PhyOS VM "${vm.name}" (v${vm.version}) has been successfully created!
911
-
912
- 📋 VM Details:
913
- Memory allocated: ${memory}MB
914
- Storage allocated: ${vm.diskSizeGB}GB
915
- Architecture: ${arch}
916
- Headless mode: ${headless ? 'Yes' : 'No'}
917
-
918
- 🚀 Next steps:
919
- 1. Start the VM: ${chalk_1.default.cyan(`phy vm start ${vm.name}`)}
920
- 2. Add the VM to Phystack console using this serial number:
921
-
922
- 🆔 Serial Number: ${chalk_1.default.bold(vm.serial)}
923
-
924
- 💡 Use this serial number or the 6 letter provisioning code to connect the VM in the Phystack console.${startupInfo}
925
- `));
926
- }
927
- else {
928
- // Multiple VMs success message
929
- const startupInfo = headless
930
- ? `\nSince these VMs are in headless mode:
931
- - Use 'phy vm shell <vm-name>' to connect to each console
932
- - Use 'phy vm logs <vm-name>' to view logs`
933
- : `\nTo view logs: phy vm logs <vm-name>`;
934
- console.log(chalk_1.default.green(`
935
- ✅ Successfully created ${createdVms.length} PhyOS VM${createdVms.length > 1 ? 's' : ''}!
936
-
937
- 📋 VM Details:
938
- Memory allocated: ${memory}MB (each)
939
- Storage allocated: ${createdVms[0].diskSizeGB}GB (each)
940
- Architecture: ${arch}
941
- Headless mode: ${headless ? 'Yes' : 'No'}
942
-
943
- 🚀 Next steps:
944
- 1. Start VMs using: ${chalk_1.default.cyan('phy vm start <vm-name>')}
945
- 2. Add VMs to Phystack console using their respective serial numbers:
946
-
947
- 🆔 Serial Numbers:
948
- ${createdVms.map(vm => ` ${chalk_1.default.bold(vm.name)}: ${chalk_1.default.bold(vm.serial)}`).join('\n')}
949
-
950
- 💡 Use these serial numbers or the 6 letter provisioning codes to connect the VMs in the Phystack console.${startupInfo}
951
- `));
952
- }
953
- }
954
- catch (error) {
955
- console.error(chalk_1.default.red(`❗️There was an error: ${error.message}`));
956
- throw error;
957
- }
958
- }
959
- //# sourceMappingURL=create.js.map