@phystack/cli 4.3.40-dev

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 (192) hide show
  1. package/LICENSE.md +19 -0
  2. package/README.md +24 -0
  3. package/bin/index.js +2 -0
  4. package/dist/commands/app/build-apps.js +66 -0
  5. package/dist/commands/app/build-apps.js.map +1 -0
  6. package/dist/commands/app/build-container-remote.js +171 -0
  7. package/dist/commands/app/build-container-remote.js.map +1 -0
  8. package/dist/commands/app/build-container.js +330 -0
  9. package/dist/commands/app/build-container.js.map +1 -0
  10. package/dist/commands/app/build.js +393 -0
  11. package/dist/commands/app/build.js.map +1 -0
  12. package/dist/commands/app/create.js +451 -0
  13. package/dist/commands/app/create.js.map +1 -0
  14. package/dist/commands/app/deploy.js +176 -0
  15. package/dist/commands/app/deploy.js.map +1 -0
  16. package/dist/commands/app/device-picker.js +150 -0
  17. package/dist/commands/app/device-picker.js.map +1 -0
  18. package/dist/commands/app/import.js +303 -0
  19. package/dist/commands/app/import.js.map +1 -0
  20. package/dist/commands/app/list.js +26 -0
  21. package/dist/commands/app/list.js.map +1 -0
  22. package/dist/commands/app/publish.js +327 -0
  23. package/dist/commands/app/publish.js.map +1 -0
  24. package/dist/commands/app/settings.js +129 -0
  25. package/dist/commands/app/settings.js.map +1 -0
  26. package/dist/commands/app/types.js +13 -0
  27. package/dist/commands/app/types.js.map +1 -0
  28. package/dist/commands/app/upload-description.js +139 -0
  29. package/dist/commands/app/upload-description.js.map +1 -0
  30. package/dist/commands/app/upload-settings.js +29 -0
  31. package/dist/commands/app/upload-settings.js.map +1 -0
  32. package/dist/commands/app/utils.js +86 -0
  33. package/dist/commands/app/utils.js.map +1 -0
  34. package/dist/commands/auth/login.js +111 -0
  35. package/dist/commands/auth/login.js.map +1 -0
  36. package/dist/commands/auth/logout.js +19 -0
  37. package/dist/commands/auth/logout.js.map +1 -0
  38. package/dist/commands/descriptor/create.js +143 -0
  39. package/dist/commands/descriptor/create.js.map +1 -0
  40. package/dist/commands/descriptor/index.js +36 -0
  41. package/dist/commands/descriptor/index.js.map +1 -0
  42. package/dist/commands/descriptor/publish.js +163 -0
  43. package/dist/commands/descriptor/publish.js.map +1 -0
  44. package/dist/commands/descriptor/show.js +68 -0
  45. package/dist/commands/descriptor/show.js.map +1 -0
  46. package/dist/commands/dev/develop.js +175 -0
  47. package/dist/commands/dev/develop.js.map +1 -0
  48. package/dist/commands/dev/forward.js +118 -0
  49. package/dist/commands/dev/forward.js.map +1 -0
  50. package/dist/commands/dev/index.js +66 -0
  51. package/dist/commands/dev/index.js.map +1 -0
  52. package/dist/commands/dev/list.js +96 -0
  53. package/dist/commands/dev/list.js.map +1 -0
  54. package/dist/commands/dev/screen-devtools.js +156 -0
  55. package/dist/commands/dev/screen-devtools.js.map +1 -0
  56. package/dist/commands/dev/select.js +118 -0
  57. package/dist/commands/dev/select.js.map +1 -0
  58. package/dist/commands/dev/shell.js +171 -0
  59. package/dist/commands/dev/shell.js.map +1 -0
  60. package/dist/commands/dev/vnc.js +75 -0
  61. package/dist/commands/dev/vnc.js.map +1 -0
  62. package/dist/commands/device/select.js +118 -0
  63. package/dist/commands/device/select.js.map +1 -0
  64. package/dist/commands/flash.js +1120 -0
  65. package/dist/commands/flash.js.map +1 -0
  66. package/dist/commands/inst/create.js +55 -0
  67. package/dist/commands/inst/create.js.map +1 -0
  68. package/dist/commands/inst/index.js +15 -0
  69. package/dist/commands/inst/index.js.map +1 -0
  70. package/dist/commands/inst/list.js +26 -0
  71. package/dist/commands/inst/list.js.map +1 -0
  72. package/dist/commands/legacydev/debug.js +11 -0
  73. package/dist/commands/legacydev/debug.js.map +1 -0
  74. package/dist/commands/legacydev/deploy.js +15 -0
  75. package/dist/commands/legacydev/deploy.js.map +1 -0
  76. package/dist/commands/legacydev/dumpTwin.js +27 -0
  77. package/dist/commands/legacydev/dumpTwin.js.map +1 -0
  78. package/dist/commands/legacydev/forward.js +104 -0
  79. package/dist/commands/legacydev/forward.js.map +1 -0
  80. package/dist/commands/legacydev/index.js +188 -0
  81. package/dist/commands/legacydev/index.js.map +1 -0
  82. package/dist/commands/legacydev/invoke.js +29 -0
  83. package/dist/commands/legacydev/invoke.js.map +1 -0
  84. package/dist/commands/legacydev/js.js +69 -0
  85. package/dist/commands/legacydev/js.js.map +1 -0
  86. package/dist/commands/legacydev/list.js +196 -0
  87. package/dist/commands/legacydev/list.js.map +1 -0
  88. package/dist/commands/legacydev/logs.js +60 -0
  89. package/dist/commands/legacydev/logs.js.map +1 -0
  90. package/dist/commands/legacydev/modules.js +50 -0
  91. package/dist/commands/legacydev/modules.js.map +1 -0
  92. package/dist/commands/legacydev/move.js +23 -0
  93. package/dist/commands/legacydev/move.js.map +1 -0
  94. package/dist/commands/legacydev/ota.js +88 -0
  95. package/dist/commands/legacydev/ota.js.map +1 -0
  96. package/dist/commands/legacydev/patchTwin.js +21 -0
  97. package/dist/commands/legacydev/patchTwin.js.map +1 -0
  98. package/dist/commands/legacydev/pin.js +23 -0
  99. package/dist/commands/legacydev/pin.js.map +1 -0
  100. package/dist/commands/legacydev/pub.js +25 -0
  101. package/dist/commands/legacydev/pub.js.map +1 -0
  102. package/dist/commands/legacydev/rdp.js +64 -0
  103. package/dist/commands/legacydev/rdp.js.map +1 -0
  104. package/dist/commands/legacydev/screen-devtools.js +142 -0
  105. package/dist/commands/legacydev/screen-devtools.js.map +1 -0
  106. package/dist/commands/legacydev/settingsShow.js +89 -0
  107. package/dist/commands/legacydev/settingsShow.js.map +1 -0
  108. package/dist/commands/legacydev/settingsUpdate.js +114 -0
  109. package/dist/commands/legacydev/settingsUpdate.js.map +1 -0
  110. package/dist/commands/legacydev/shell.js +167 -0
  111. package/dist/commands/legacydev/shell.js.map +1 -0
  112. package/dist/commands/legacydev/showTwin.js +9 -0
  113. package/dist/commands/legacydev/showTwin.js.map +1 -0
  114. package/dist/commands/legacydev/statusLog.js +56 -0
  115. package/dist/commands/legacydev/statusLog.js.map +1 -0
  116. package/dist/commands/legacydev/sub.js +39 -0
  117. package/dist/commands/legacydev/sub.js.map +1 -0
  118. package/dist/commands/legacydev/vnc.js +61 -0
  119. package/dist/commands/legacydev/vnc.js.map +1 -0
  120. package/dist/commands/tenant/index.js +21 -0
  121. package/dist/commands/tenant/index.js.map +1 -0
  122. package/dist/commands/tenant/list.js +14 -0
  123. package/dist/commands/tenant/list.js.map +1 -0
  124. package/dist/commands/tenant/select.js +87 -0
  125. package/dist/commands/tenant/select.js.map +1 -0
  126. package/dist/commands/vm/create.js +718 -0
  127. package/dist/commands/vm/create.js.map +1 -0
  128. package/dist/commands/vm/index.js +130 -0
  129. package/dist/commands/vm/index.js.map +1 -0
  130. package/dist/commands/vm/list.js +124 -0
  131. package/dist/commands/vm/list.js.map +1 -0
  132. package/dist/commands/vm/logs.js +66 -0
  133. package/dist/commands/vm/logs.js.map +1 -0
  134. package/dist/commands/vm/remove.js +180 -0
  135. package/dist/commands/vm/remove.js.map +1 -0
  136. package/dist/commands/vm/shell.js +400 -0
  137. package/dist/commands/vm/shell.js.map +1 -0
  138. package/dist/commands/vm/start.js +861 -0
  139. package/dist/commands/vm/start.js.map +1 -0
  140. package/dist/commands/vm/stop.js +232 -0
  141. package/dist/commands/vm/stop.js.map +1 -0
  142. package/dist/index.js +158 -0
  143. package/dist/index.js.map +1 -0
  144. package/dist/services/admin-api/admin-api.types.js +3 -0
  145. package/dist/services/admin-api/admin-api.types.js.map +1 -0
  146. package/dist/services/admin-api/device-modules.admin-api.service.js +58 -0
  147. package/dist/services/admin-api/device-modules.admin-api.service.js.map +1 -0
  148. package/dist/services/admin-api/devices-admin-api.service.js +213 -0
  149. package/dist/services/admin-api/devices-admin-api.service.js.map +1 -0
  150. package/dist/services/admin-api/gridapps-admin-api.service.js +59 -0
  151. package/dist/services/admin-api/gridapps-admin-api.service.js.map +1 -0
  152. package/dist/services/admin-api/index.js +157 -0
  153. package/dist/services/admin-api/index.js.map +1 -0
  154. package/dist/services/admin-api/installations-admin-api.service.js +29 -0
  155. package/dist/services/admin-api/installations-admin-api.service.js.map +1 -0
  156. package/dist/services/admin-api/organizations-admin-api.service.js +53 -0
  157. package/dist/services/admin-api/organizations-admin-api.service.js.map +1 -0
  158. package/dist/services/auth/device-grant-auth.service.js +224 -0
  159. package/dist/services/auth/device-grant-auth.service.js.map +1 -0
  160. package/dist/services/phyhub/index.js +200 -0
  161. package/dist/services/phyhub/index.js.map +1 -0
  162. package/dist/services/phyhub/phyhub.types.js +3 -0
  163. package/dist/services/phyhub/phyhub.types.js.map +1 -0
  164. package/dist/utils/device-fetcher.js +92 -0
  165. package/dist/utils/device-fetcher.js.map +1 -0
  166. package/dist/utils/devices.js +41 -0
  167. package/dist/utils/devices.js.map +1 -0
  168. package/dist/utils/docker-credentials.js +720 -0
  169. package/dist/utils/docker-credentials.js.map +1 -0
  170. package/dist/utils/emulated-device.js +91 -0
  171. package/dist/utils/emulated-device.js.map +1 -0
  172. package/dist/utils/index.js +180 -0
  173. package/dist/utils/index.js.map +1 -0
  174. package/dist/utils/modules.js +36 -0
  175. package/dist/utils/modules.js.map +1 -0
  176. package/dist/utils/org-selector.js +108 -0
  177. package/dist/utils/org-selector.js.map +1 -0
  178. package/dist/utils/proxy.js +31 -0
  179. package/dist/utils/proxy.js.map +1 -0
  180. package/dist/utils/registry-credentials.js +113 -0
  181. package/dist/utils/registry-credentials.js.map +1 -0
  182. package/dist/utils/statuses.js +124 -0
  183. package/dist/utils/statuses.js.map +1 -0
  184. package/dist/utils/templates.js +197 -0
  185. package/dist/utils/templates.js.map +1 -0
  186. package/dist/utils/tenant-storage.js +88 -0
  187. package/dist/utils/tenant-storage.js.map +1 -0
  188. package/dist/utils/vm.js +434 -0
  189. package/dist/utils/vm.js.map +1 -0
  190. package/dist/utils/with-spinner.js +20 -0
  191. package/dist/utils/with-spinner.js.map +1 -0
  192. package/package.json +103 -0
@@ -0,0 +1,718 @@
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 vm_1 = require("../../utils/vm");
47
+ /**
48
+ * Determines the architecture to use for the VM
49
+ * Returns the specified architecture if it's valid, or null if no valid arch was provided
50
+ */
51
+ function validateArch(userArch) {
52
+ if (userArch) {
53
+ if (['amd64', 'arm64'].includes(userArch)) {
54
+ return userArch;
55
+ }
56
+ console.warn(chalk_1.default.yellow(`Unsupported architecture: ${userArch}, please choose a supported option`));
57
+ }
58
+ return null;
59
+ }
60
+ /**
61
+ * Detect the host system architecture
62
+ */
63
+ function getHostArch() {
64
+ const systemArch = process.arch;
65
+ if (systemArch === 'x64')
66
+ return 'amd64';
67
+ if (systemArch === 'arm64')
68
+ return 'arm64';
69
+ console.warn(chalk_1.default.yellow(`Unusual host architecture detected: ${systemArch}`));
70
+ return 'amd64'; // Default to amd64 as fallback
71
+ }
72
+ /**
73
+ * Gets the download URL and version for PhyOS based on architecture
74
+ */
75
+ async function getPhyOsDownloadUrl(arch, versionStr) {
76
+ // If a specific version is provided, use it directly (only if it's a valid string)
77
+ if (versionStr && typeof versionStr === 'string' && /^\d+\.\d+\.\d+$/.test(versionStr)) {
78
+ const url = `https://os.phygrid.com/phyos.${versionStr}.qemu${arch}.tar.gz`;
79
+ return { url, version: versionStr };
80
+ }
81
+ // Otherwise, use the short URL and follow redirects to get the latest version
82
+ const shortUrl = `https://qr.run/phyos-qemu${arch}-latest`;
83
+ console.log(chalk_1.default.blue(`Getting latest version URL from ${shortUrl}...`));
84
+ try {
85
+ // Use curl to follow redirect and get the final URL
86
+ const curlOutput = (0, child_process_1.execSync)(`curl -sI "${shortUrl}" | grep -i "^location:" | tail -n1`, { encoding: 'utf8' });
87
+ const redirectUrl = curlOutput.trim().split(/:\s+/)[1];
88
+ if (!redirectUrl) {
89
+ throw new Error('Failed to get redirect URL for PhyOS');
90
+ }
91
+ console.log(chalk_1.default.blue(`Redirect URL: ${redirectUrl}`));
92
+ // Extract version from the URL (format: phyos.1.2.3.qemuXXX.tar.gz)
93
+ const versionMatch = redirectUrl.match(/phyos\.([0-9]+\.[0-9]+\.[0-9]+)\.qemu/i);
94
+ if (!versionMatch || !versionMatch[1]) {
95
+ throw new Error(`Failed to extract version from URL: ${redirectUrl}`);
96
+ }
97
+ const version = versionMatch[1];
98
+ console.log(chalk_1.default.blue(`Detected version: ${version}`));
99
+ return { url: redirectUrl, version };
100
+ }
101
+ catch (error) {
102
+ console.error(chalk_1.default.red(`Error following redirect: ${error.message}`));
103
+ throw new Error(`Failed to determine latest PhyOS version: ${error.message}`);
104
+ }
105
+ }
106
+ /**
107
+ * Downloads a file using curl with progress bar
108
+ */
109
+ function downloadWithCurl(url, outputPath) {
110
+ return new Promise((resolve, reject) => {
111
+ console.log(chalk_1.default.blue(`Downloading from ${url}...`));
112
+ // Create curl process with progress bar
113
+ const curl = (0, child_process_1.spawn)('curl', [
114
+ '--progress-bar', // Show progress bar
115
+ '-L', // Follow redirects
116
+ '-o', outputPath, // Output file
117
+ url // URL to download
118
+ ], {
119
+ stdio: ['ignore', 'inherit', 'inherit'] // Show progress in terminal
120
+ });
121
+ curl.on('close', (code) => {
122
+ if (code === 0) {
123
+ resolve();
124
+ }
125
+ else {
126
+ reject(new Error(`curl exited with code ${code}`));
127
+ }
128
+ });
129
+ curl.on('error', (err) => {
130
+ reject(new Error(`Failed to execute curl: ${err.message}`));
131
+ });
132
+ });
133
+ }
134
+ /**
135
+ * Copy a file from source to destination
136
+ */
137
+ function copyFile(sourcePath, destinationPath) {
138
+ fs_1.default.copyFileSync(sourcePath, destinationPath);
139
+ }
140
+ /**
141
+ * Extract tar file using system tar command
142
+ */
143
+ function extractTarFile(tarFilePath, outputDir) {
144
+ return new Promise((resolve, reject) => {
145
+ console.log(chalk_1.default.blue(`Extracting ${path_1.default.basename(tarFilePath)} to ${outputDir}...`));
146
+ // Use system tar command which handles large files better than the Node.js tar library
147
+ const tar = (0, child_process_1.spawn)('tar', [
148
+ '-xzf', // Extract gzipped tar
149
+ tarFilePath, // Input file
150
+ '-C', outputDir // Output directory
151
+ ], {
152
+ stdio: ['ignore', 'inherit', 'inherit'] // Show output in terminal
153
+ });
154
+ tar.on('close', (code) => {
155
+ if (code === 0) {
156
+ resolve();
157
+ }
158
+ else {
159
+ reject(new Error(`tar extraction failed with code ${code}`));
160
+ }
161
+ });
162
+ tar.on('error', (err) => {
163
+ reject(new Error(`Failed to execute tar: ${err.message}`));
164
+ });
165
+ });
166
+ }
167
+ /**
168
+ * Gets the current disk size in GB
169
+ */
170
+ function getCurrentDiskSizeGB(diskFilePath) {
171
+ if (!fs_1.default.existsSync(diskFilePath)) {
172
+ return 0;
173
+ }
174
+ const stats = fs_1.default.statSync(diskFilePath);
175
+ const sizeInBytes = stats.size;
176
+ const sizeInGB = sizeInBytes / (1024 * 1024 * 1024);
177
+ return Math.round(sizeInGB * 100) / 100; // Round to 2 decimal places
178
+ }
179
+ /**
180
+ * Expands the disk size using dd command
181
+ */
182
+ function expandDiskSize(diskFilePath, targetSizeGB) {
183
+ return new Promise((resolve, reject) => {
184
+ console.log(chalk_1.default.blue(`Expanding disk to ${targetSizeGB}GB...`));
185
+ // Calculate the additional size needed in bytes
186
+ const currentSizeGB = getCurrentDiskSizeGB(diskFilePath);
187
+ const additionalSizeBytes = Math.ceil((targetSizeGB - currentSizeGB) * 1024 * 1024 * 1024);
188
+ if (additionalSizeBytes <= 0) {
189
+ resolve(); // No expansion needed
190
+ return;
191
+ }
192
+ // Use a more compatible dd command approach
193
+ // Calculate block size and count to avoid the '1m' format issue
194
+ const blockSize = 1024 * 1024; // 1MB in bytes
195
+ const count = Math.ceil(additionalSizeBytes / blockSize);
196
+ const dd = (0, child_process_1.spawn)('dd', [
197
+ 'if=/dev/zero',
198
+ `bs=${blockSize}`,
199
+ `count=${count}`,
200
+ '>>',
201
+ diskFilePath
202
+ ], {
203
+ stdio: ['ignore', 'inherit', 'inherit'],
204
+ shell: true
205
+ });
206
+ dd.on('close', (code) => {
207
+ if (code === 0) {
208
+ console.log(chalk_1.default.green(`✅ Disk expanded to ${targetSizeGB}GB`));
209
+ resolve();
210
+ }
211
+ else {
212
+ reject(new Error(`dd command failed with code ${code}`));
213
+ }
214
+ });
215
+ dd.on('error', (err) => {
216
+ reject(new Error(`Failed to execute dd: ${err.message}`));
217
+ });
218
+ });
219
+ }
220
+ /**
221
+ * Downloads and extracts a PhyOS image
222
+ */
223
+ async function downloadPhyOS(name, arch, versionStr) {
224
+ console.log(chalk_1.default.blue('Determining PhyOS version and download URL...'));
225
+ // Get the download URL and version
226
+ const { url, version: detectedVersion } = await getPhyOsDownloadUrl(arch, versionStr);
227
+ // Get paths for cache and target VM
228
+ const vmDir = (0, vm_1.getVmDir)(name);
229
+ const cacheTarPath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'tar');
230
+ const cacheImagePath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'image');
231
+ const cacheDiskPath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'disk', 'zfs');
232
+ const cacheInitramfsPath = (0, vm_1.getPhyOsCachePath)(detectedVersion, arch, 'initramfs');
233
+ const tarFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.tar.gz`);
234
+ const imageFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.Image`);
235
+ const diskFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.zfs`);
236
+ const initramfsFile = path_1.default.join(vmDir, `phyos.${detectedVersion}.qemu${arch}.cpio.gz`);
237
+ // Ensure the VM directory exists
238
+ if (!fs_1.default.existsSync(vmDir)) {
239
+ fs_1.default.mkdirSync(vmDir, { recursive: true });
240
+ }
241
+ // Check if we need to download or can use cache
242
+ let needsDownload = true;
243
+ let needsExtract = true;
244
+ // Check if cache has the files already
245
+ if (fs_1.default.existsSync(cacheImagePath) && fs_1.default.existsSync(cacheDiskPath) && fs_1.default.existsSync(cacheInitramfsPath)) {
246
+ console.log(chalk_1.default.green(`Found cached PhyOS ${detectedVersion} files, using cache...`));
247
+ needsDownload = false;
248
+ needsExtract = false;
249
+ // Copy files from cache to VM directory
250
+ console.log(chalk_1.default.blue('Copying files from cache to VM directory...'));
251
+ copyFile(cacheImagePath, imageFile);
252
+ copyFile(cacheDiskPath, diskFile);
253
+ copyFile(cacheInitramfsPath, initramfsFile);
254
+ }
255
+ // Check if we have the tar file but still need to extract
256
+ else if (fs_1.default.existsSync(cacheTarPath)) {
257
+ console.log(chalk_1.default.green(`Found cached PhyOS ${detectedVersion} tar file, extracting...`));
258
+ needsDownload = false;
259
+ // Copy tar file from cache to VM directory for extraction
260
+ copyFile(cacheTarPath, tarFile);
261
+ }
262
+ // Download if needed
263
+ if (needsDownload) {
264
+ // Download the tar.gz file using curl with progress bar
265
+ console.log(chalk_1.default.blue(`Downloading PhyOS ${detectedVersion}...`));
266
+ await downloadWithCurl(url, cacheTarPath);
267
+ // Verify the download was successful
268
+ if (!fs_1.default.existsSync(cacheTarPath) || fs_1.default.statSync(cacheTarPath).size === 0) {
269
+ throw new Error('Download failed or resulted in an empty file');
270
+ }
271
+ // Copy to VM directory for extraction
272
+ copyFile(cacheTarPath, tarFile);
273
+ }
274
+ // Extract if needed
275
+ if (needsExtract) {
276
+ console.log(chalk_1.default.green('Extracting files...'));
277
+ try {
278
+ // Extract the tar.gz file using system tar command
279
+ await extractTarFile(tarFile, vmDir);
280
+ // Check if the expected files exist after extraction
281
+ if (!fs_1.default.existsSync(imageFile) || !fs_1.default.existsSync(diskFile) || !fs_1.default.existsSync(initramfsFile)) {
282
+ 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}`));
283
+ // Clean up potentially corrupt extraction? Maybe remove extracted files or vmDir?
284
+ try {
285
+ if (fs_1.default.existsSync(imageFile))
286
+ fs_1.default.unlinkSync(imageFile);
287
+ if (fs_1.default.existsSync(diskFile))
288
+ fs_1.default.unlinkSync(diskFile);
289
+ if (fs_1.default.existsSync(initramfsFile))
290
+ fs_1.default.unlinkSync(initramfsFile);
291
+ // Consider removing tarFile as well if it came from cache but extraction failed
292
+ // if (!needsDownload && fs.existsSync(tarFile)) fs.unlinkSync(tarFile);
293
+ }
294
+ catch (cleanupError) {
295
+ console.warn(chalk_1.default.yellow(`Warning: Failed to clean up incomplete files: ${cleanupError.message}`));
296
+ }
297
+ throw new Error('Required files not found after extraction');
298
+ }
299
+ // Cache the extracted files
300
+ console.log(chalk_1.default.green('Caching extracted files for future use...'));
301
+ copyFile(imageFile, cacheImagePath);
302
+ copyFile(diskFile, cacheDiskPath);
303
+ copyFile(initramfsFile, cacheInitramfsPath); // Cache initramfs
304
+ }
305
+ catch (extractError) {
306
+ // Log extraction error and re-throw
307
+ console.error(chalk_1.default.red(`Error during extraction: ${extractError.message}`));
308
+ throw extractError; // Re-throw the error to be caught by the caller
309
+ }
310
+ finally {
311
+ // Clean up the downloaded tar file from the VM directory regardless of extraction success/failure
312
+ // unless it was copied from cache initially
313
+ if (needsDownload && fs_1.default.existsSync(tarFile)) {
314
+ try {
315
+ fs_1.default.unlinkSync(tarFile);
316
+ }
317
+ catch (unlinkError) {
318
+ console.warn(chalk_1.default.yellow(`Warning: Failed to remove temporary tar file ${tarFile}: ${unlinkError.message}`));
319
+ }
320
+ }
321
+ }
322
+ }
323
+ console.log(chalk_1.default.green(`PhyOS ${detectedVersion} ready in ${vmDir}`));
324
+ // Return paths to the main files
325
+ return { version: detectedVersion, imageFile, diskFile, initramfsFile }; // Return initramfs path
326
+ }
327
+ /**
328
+ * Creates a single VM with the specified parameters
329
+ */
330
+ async function createSingleVm(vmName, arch, memory, headless, versionStr, serialNumber, storageGB) {
331
+ console.log(chalk_1.default.blue(`Creating VM: ${vmName}...`));
332
+ // Download and extract PhyOS
333
+ const downloadedFiles = await downloadPhyOS(vmName, arch, versionStr);
334
+ // Get the actual disk size for display
335
+ let actualDiskSizeGB = getCurrentDiskSizeGB(downloadedFiles.diskFile);
336
+ // Expand disk size if specified
337
+ if (storageGB !== undefined) {
338
+ console.log(chalk_1.default.blue(`Current disk size: ${actualDiskSizeGB}GB`));
339
+ if (storageGB < actualDiskSizeGB) {
340
+ 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.`);
341
+ }
342
+ if (storageGB > actualDiskSizeGB) {
343
+ await expandDiskSize(downloadedFiles.diskFile, storageGB);
344
+ // Update the actual disk size for display
345
+ const finalDiskSizeGB = getCurrentDiskSizeGB(downloadedFiles.diskFile);
346
+ actualDiskSizeGB = finalDiskSizeGB;
347
+ }
348
+ // If storageGB === actualDiskSizeGB, no expansion needed
349
+ }
350
+ // Get timestamp for creation
351
+ const createdTime = new Date().toISOString();
352
+ // Create VM configuration (legacy, to maintain backward compatibility)
353
+ const vmConfig = {
354
+ name: vmName,
355
+ version: downloadedFiles.version,
356
+ arch,
357
+ memory,
358
+ image: path_1.default.basename(downloadedFiles.imageFile),
359
+ disk: path_1.default.basename(downloadedFiles.diskFile),
360
+ initramfs: path_1.default.basename(downloadedFiles.initramfsFile),
361
+ created: createdTime
362
+ };
363
+ // Save VM configuration (legacy format)
364
+ const configPath = (0, vm_1.getVmConfigPath)(vmName);
365
+ fs_1.default.writeFileSync(configPath, JSON.stringify(vmConfig, null, 2));
366
+ // Create and save VM settings with memory and serial in the new format
367
+ const vmSettings = {
368
+ memory,
369
+ serial: serialNumber,
370
+ arch,
371
+ version: downloadedFiles.version,
372
+ created: createdTime,
373
+ headless,
374
+ storage: storageGB
375
+ };
376
+ // Save settings to settings.json
377
+ const settingsPath = (0, vm_1.getVmSettingsPath)(vmName);
378
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(vmSettings, null, 2));
379
+ // For backward compatibility, also save serial to serial.txt
380
+ const serialPath = (0, vm_1.getVmSerialPath)(vmName);
381
+ fs_1.default.writeFileSync(serialPath, serialNumber);
382
+ return { version: downloadedFiles.version, actualDiskSizeGB };
383
+ }
384
+ /**
385
+ * Command to create one or more PhyOS VMs with interactive prompts for missing options
386
+ */
387
+ async function createVm(name, options) {
388
+ try {
389
+ // Import inquirer dynamically since it's a heavy dependency
390
+ const inquirer = (await Promise.resolve().then(() => __importStar(require('inquirer')))).default;
391
+ // Ensure VM directory exists
392
+ (0, vm_1.ensureVmDir)();
393
+ // Parse count parameter (default to 1)
394
+ const count = options.count ? parseInt(options.count, 10) : 1;
395
+ if (isNaN(count) || count < 1 || count > 50) {
396
+ throw new Error('Count must be a number between 1 and 50');
397
+ }
398
+ // Get existing VMs for validation and suggestions
399
+ const existingVms = (0, vm_1.listVms)();
400
+ const vmNameValidator = (input) => {
401
+ if (!input || input.trim() === '') {
402
+ return 'VM name is required';
403
+ }
404
+ if (input.includes(' ')) {
405
+ return 'VM name cannot contain spaces';
406
+ }
407
+ if ((0, vm_1.vmExists)(input)) {
408
+ return `VM with name "${input}" already exists`;
409
+ }
410
+ return true;
411
+ };
412
+ // If name is not provided via command line, prompt for it
413
+ if (!name) {
414
+ const answers = await inquirer.prompt([
415
+ {
416
+ type: 'input',
417
+ name: 'name',
418
+ message: count > 1 ? 'Enter a base name for the VMs:' : 'Enter a name for the VM:',
419
+ validate: (input) => {
420
+ if (!input || input.trim() === '') {
421
+ return 'VM name is required';
422
+ }
423
+ if (input.includes(' ')) {
424
+ return 'VM name cannot contain spaces';
425
+ }
426
+ // For multiple VMs, check if any of the suffixed names would conflict
427
+ if (count > 1) {
428
+ for (let i = 1; i <= count; i++) {
429
+ const suffixedName = `${input}-${i}`;
430
+ if ((0, vm_1.vmExists)(suffixedName)) {
431
+ return `VM with name "${suffixedName}" already exists`;
432
+ }
433
+ }
434
+ }
435
+ else {
436
+ if ((0, vm_1.vmExists)(input)) {
437
+ return `VM with name "${input}" already exists`;
438
+ }
439
+ }
440
+ return true;
441
+ }
442
+ }
443
+ ]);
444
+ name = answers.name;
445
+ }
446
+ else {
447
+ // Validate the base name and check for conflicts with suffixed names
448
+ if (count > 1) {
449
+ for (let i = 1; i <= count; i++) {
450
+ const suffixedName = `${name}-${i}`;
451
+ if ((0, vm_1.vmExists)(suffixedName)) {
452
+ throw new Error(`VM with name "${suffixedName}" already exists`);
453
+ }
454
+ }
455
+ }
456
+ else {
457
+ if (vmNameValidator(name) !== true) {
458
+ throw new Error(typeof vmNameValidator(name) === 'string' ? vmNameValidator(name) : 'Invalid VM name');
459
+ }
460
+ }
461
+ }
462
+ console.log(chalk_1.default.blue(`Creating ${count} PhyOS VM${count > 1 ? 's' : ''} with base name: ${name}`));
463
+ // Prompt for architecture if not provided
464
+ let arch;
465
+ if (!options.arch) {
466
+ const hostArch = getHostArch();
467
+ const answers = await inquirer.prompt([
468
+ {
469
+ type: 'list',
470
+ name: 'arch',
471
+ message: 'Select VM architecture (can be different from host):',
472
+ default: hostArch,
473
+ choices: [
474
+ {
475
+ name: `amd64 (x86_64) ${hostArch === 'amd64' ? '[Host Architecture]' : '[Emulated]'}`,
476
+ value: 'amd64'
477
+ },
478
+ {
479
+ name: `arm64 (AArch64) ${hostArch === 'arm64' ? '[Host Architecture]' : '[Emulated]'}`,
480
+ value: 'arm64'
481
+ }
482
+ ]
483
+ }
484
+ ]);
485
+ arch = answers.arch;
486
+ }
487
+ else {
488
+ const validArch = validateArch(options.arch);
489
+ if (!validArch) {
490
+ throw new Error(`Unsupported architecture: ${options.arch}. Supported values are: amd64, arm64`);
491
+ }
492
+ arch = validArch;
493
+ }
494
+ const hostArch = getHostArch();
495
+ const isEmulated = arch !== hostArch;
496
+ console.log(chalk_1.default.blue(`Using architecture: ${arch}${isEmulated ? ' (emulated)' : ' (native)'}`));
497
+ // Force headless mode for cross-architecture emulation in both directions
498
+ const forceHeadless = isEmulated && ((arch === 'amd64' && hostArch === 'arm64') ||
499
+ (arch === 'arm64' && hostArch === 'amd64'));
500
+ // If we're going to force headless mode, inform the user
501
+ if (forceHeadless) {
502
+ console.log(chalk_1.default.yellow(`⚠️ Note: Cross-architecture emulation (${arch} on ${hostArch}) requires headless mode`));
503
+ console.log(chalk_1.default.yellow(` This VM will be configured as headless automatically`));
504
+ }
505
+ // Prompt for version if not provided
506
+ let versionStr;
507
+ if (!options.phyosVersion) {
508
+ const answers = await inquirer.prompt([
509
+ {
510
+ type: 'confirm',
511
+ name: 'useLatest',
512
+ message: 'Use latest PhyOS version?',
513
+ default: true
514
+ },
515
+ {
516
+ type: 'input',
517
+ name: 'version',
518
+ message: 'Enter specific PhyOS version (e.g. 5.0.127):',
519
+ when: (answers) => !answers.useLatest,
520
+ validate: (input) => {
521
+ if (!input || !/^\d+\.\d+\.\d+$/.test(input)) {
522
+ return 'Please enter a valid version number (format: X.Y.Z)';
523
+ }
524
+ return true;
525
+ }
526
+ }
527
+ ]);
528
+ versionStr = answers.useLatest ? undefined : answers.version;
529
+ }
530
+ else {
531
+ versionStr = options.phyosVersion;
532
+ }
533
+ // Prompt for memory if not provided
534
+ let memory;
535
+ if (!options.memory) {
536
+ const answers = await inquirer.prompt([
537
+ {
538
+ type: 'input',
539
+ name: 'memory',
540
+ message: 'Enter memory allocation in MB:',
541
+ default: '2048',
542
+ validate: (input) => {
543
+ const mem = parseInt(input, 10);
544
+ if (isNaN(mem) || mem < 512 || mem > 32768) {
545
+ return 'Please enter a valid memory size between 512MB and 32GB';
546
+ }
547
+ return true;
548
+ }
549
+ }
550
+ ]);
551
+ memory = parseInt(answers.memory, 10);
552
+ }
553
+ else {
554
+ memory = parseInt(options.memory, 10);
555
+ if (isNaN(memory) || memory < 512 || memory > 32768) {
556
+ throw new Error('Memory must be a number between 512MB and 32GB');
557
+ }
558
+ }
559
+ // Prompt for storage if not provided
560
+ let storageGB;
561
+ if (!options.storage) {
562
+ // For multiple VMs, don't prompt - use current image size
563
+ if (count > 1) {
564
+ storageGB = undefined; // Use current size, no expansion
565
+ }
566
+ else {
567
+ // Get the actual disk size for the default value
568
+ // We need to download/extract first to know the current size
569
+ const tempDownloadedFiles = await downloadPhyOS('temp', arch, versionStr);
570
+ const currentDiskSizeGB = getCurrentDiskSizeGB(tempDownloadedFiles.diskFile);
571
+ // Clean up temp files
572
+ const tempDir = (0, vm_1.getVmDir)('temp');
573
+ if (fs_1.default.existsSync(tempDir)) {
574
+ (0, rimraf_1.rimrafSync)(tempDir);
575
+ }
576
+ const answers = await inquirer.prompt([
577
+ {
578
+ type: 'input',
579
+ name: 'storage',
580
+ message: 'Enter storage allocation in GB (press Enter to use current size):',
581
+ default: String(currentDiskSizeGB),
582
+ validate: (input) => {
583
+ if (!input || input.trim() === '') {
584
+ return true; // Allow empty input to use default
585
+ }
586
+ const storage = parseFloat(input);
587
+ if (isNaN(storage) || storage < 1 || storage > 100) {
588
+ return 'Please enter a valid storage size between 1GB and 100GB';
589
+ }
590
+ return true;
591
+ }
592
+ }
593
+ ]);
594
+ // If user left it empty or entered the same as current size, don't expand
595
+ if (!answers.storage || answers.storage.trim() === '' || parseFloat(answers.storage) === currentDiskSizeGB) {
596
+ storageGB = undefined; // Use current size, no expansion
597
+ }
598
+ else {
599
+ storageGB = parseFloat(answers.storage);
600
+ }
601
+ }
602
+ }
603
+ else {
604
+ storageGB = parseFloat(options.storage);
605
+ if (isNaN(storageGB) || storageGB < 1 || storageGB > 100) {
606
+ throw new Error('Storage must be a number between 1GB and 100GB');
607
+ }
608
+ }
609
+ // Determine headless mode setting
610
+ let headless;
611
+ // If x86 on ARM, force headless mode
612
+ if (forceHeadless) {
613
+ headless = true;
614
+ }
615
+ // Otherwise use options or default to non-headless
616
+ else if (options.headless !== undefined) {
617
+ headless = options.headless;
618
+ }
619
+ else {
620
+ headless = false; // Default to non-headless mode
621
+ console.log(chalk_1.default.yellow(`⚠️ VM will run with graphical interface (non-headless mode by default)`));
622
+ console.log(chalk_1.default.yellow(` Use --headless flag to run without display`));
623
+ }
624
+ // Create VMs in a loop
625
+ const createdVms = [];
626
+ for (let i = 1; i <= count; i++) {
627
+ const vmName = count > 1 ? `${name}-${i}` : name;
628
+ const serialNumber = `PHY${(0, uuid_1.v4)().replace(/-/g, '').toUpperCase()}`;
629
+ console.log(chalk_1.default.blue(`\n[${i}/${count}] Generating unique serial number for ${vmName}: ${serialNumber}`));
630
+ try {
631
+ const result = await createSingleVm(vmName, arch, memory, headless, versionStr, serialNumber, storageGB);
632
+ createdVms.push({
633
+ name: vmName,
634
+ serial: serialNumber,
635
+ version: result.version,
636
+ diskSizeGB: result.actualDiskSizeGB
637
+ });
638
+ console.log(chalk_1.default.green(`✅ VM "${vmName}" created successfully!`));
639
+ }
640
+ catch (error) {
641
+ console.error(chalk_1.default.red(`❗️Failed to create VM "${vmName}": ${error.message}`));
642
+ // Attempt to clean up VM directory if creation failed
643
+ const vmDir = (0, vm_1.getVmDir)(vmName);
644
+ if (fs_1.default.existsSync(vmDir)) {
645
+ try {
646
+ (0, rimraf_1.rimrafSync)(vmDir);
647
+ console.log(chalk_1.default.yellow(`Cleaned up incomplete VM directory: ${vmDir}`));
648
+ }
649
+ catch (cleanupError) {
650
+ console.warn(chalk_1.default.yellow(`Warning: Failed to clean up VM directory ${vmDir}: ${cleanupError.message}`));
651
+ }
652
+ }
653
+ // Continue with other VMs instead of exiting
654
+ continue;
655
+ }
656
+ }
657
+ if (createdVms.length === 0) {
658
+ throw new Error('Failed to create any VMs');
659
+ }
660
+ // Show success message(s)
661
+ if (count === 1) {
662
+ const vm = createdVms[0];
663
+ const startupInfo = headless
664
+ ? `\nSince this VM is in headless mode:
665
+ - Use 'phy vm shell ${vm.name}' to connect to the console
666
+ - Use 'phy vm logs ${vm.name}' to view logs`
667
+ : `\nTo view logs: phy vm logs ${vm.name}`;
668
+ console.log(chalk_1.default.green(`
669
+ ✅ PhyOS VM "${vm.name}" (v${vm.version}) has been successfully created!
670
+
671
+ 📋 VM Details:
672
+ Memory allocated: ${memory}MB
673
+ Storage allocated: ${vm.diskSizeGB}GB
674
+ Architecture: ${arch}
675
+ Headless mode: ${headless ? 'Yes' : 'No'}
676
+
677
+ 🚀 Next steps:
678
+ 1. Start the VM: ${chalk_1.default.cyan(`phy vm start ${vm.name}`)}
679
+ 2. Add the VM to Phygrid console using this serial number:
680
+
681
+ 🆔 Serial Number: ${chalk_1.default.bold(vm.serial)}
682
+
683
+ 💡 Use this serial number or the 6 letter provisioning code to connect the VM in the Phygrid console.${startupInfo}
684
+ `));
685
+ }
686
+ else {
687
+ // Multiple VMs success message
688
+ const startupInfo = headless
689
+ ? `\nSince these VMs are in headless mode:
690
+ - Use 'phy vm shell <vm-name>' to connect to each console
691
+ - Use 'phy vm logs <vm-name>' to view logs`
692
+ : `\nTo view logs: phy vm logs <vm-name>`;
693
+ console.log(chalk_1.default.green(`
694
+ ✅ Successfully created ${createdVms.length} PhyOS VM${createdVms.length > 1 ? 's' : ''}!
695
+
696
+ 📋 VM Details:
697
+ Memory allocated: ${memory}MB (each)
698
+ Storage allocated: ${createdVms[0].diskSizeGB}GB (each)
699
+ Architecture: ${arch}
700
+ Headless mode: ${headless ? 'Yes' : 'No'}
701
+
702
+ 🚀 Next steps:
703
+ 1. Start VMs using: ${chalk_1.default.cyan('phy vm start <vm-name>')}
704
+ 2. Add VMs to Phygrid console using their respective serial numbers:
705
+
706
+ 🆔 Serial Numbers:
707
+ ${createdVms.map(vm => ` ${chalk_1.default.bold(vm.name)}: ${chalk_1.default.bold(vm.serial)}`).join('\n')}
708
+
709
+ 💡 Use these serial numbers or the 6 letter provisioning codes to connect the VMs in the Phygrid console.${startupInfo}
710
+ `));
711
+ }
712
+ }
713
+ catch (error) {
714
+ console.error(chalk_1.default.red(`❗️There was an error: ${error.message}`));
715
+ throw error;
716
+ }
717
+ }
718
+ //# sourceMappingURL=create.js.map