@cicore/cli 1.0.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 (337) hide show
  1. package/bin/ci.js +13 -0
  2. package/dist/commands/addon/api-actions.d.ts +45 -0
  3. package/dist/commands/addon/api-actions.d.ts.map +1 -0
  4. package/dist/commands/addon/api-actions.js +281 -0
  5. package/dist/commands/addon/api-actions.js.map +1 -0
  6. package/dist/commands/addon/build.d.ts +11 -0
  7. package/dist/commands/addon/build.d.ts.map +1 -0
  8. package/dist/commands/addon/build.js +182 -0
  9. package/dist/commands/addon/build.js.map +1 -0
  10. package/dist/commands/addon/create.d.ts +11 -0
  11. package/dist/commands/addon/create.d.ts.map +1 -0
  12. package/dist/commands/addon/create.js +1186 -0
  13. package/dist/commands/addon/create.js.map +1 -0
  14. package/dist/commands/addon/delete.d.ts +13 -0
  15. package/dist/commands/addon/delete.d.ts.map +1 -0
  16. package/dist/commands/addon/delete.js +83 -0
  17. package/dist/commands/addon/delete.js.map +1 -0
  18. package/dist/commands/addon/deploy.d.ts +27 -0
  19. package/dist/commands/addon/deploy.d.ts.map +1 -0
  20. package/dist/commands/addon/deploy.js +459 -0
  21. package/dist/commands/addon/deploy.js.map +1 -0
  22. package/dist/commands/addon/dev-deploy.d.ts +31 -0
  23. package/dist/commands/addon/dev-deploy.d.ts.map +1 -0
  24. package/dist/commands/addon/dev-deploy.js +128 -0
  25. package/dist/commands/addon/dev-deploy.js.map +1 -0
  26. package/dist/commands/addon/dev.d.ts +36 -0
  27. package/dist/commands/addon/dev.d.ts.map +1 -0
  28. package/dist/commands/addon/dev.js +323 -0
  29. package/dist/commands/addon/dev.js.map +1 -0
  30. package/dist/commands/addon/extract-classes.d.ts +23 -0
  31. package/dist/commands/addon/extract-classes.d.ts.map +1 -0
  32. package/dist/commands/addon/extract-classes.js +281 -0
  33. package/dist/commands/addon/extract-classes.js.map +1 -0
  34. package/dist/commands/addon/generate-safelist.d.ts +24 -0
  35. package/dist/commands/addon/generate-safelist.d.ts.map +1 -0
  36. package/dist/commands/addon/generate-safelist.js +276 -0
  37. package/dist/commands/addon/generate-safelist.js.map +1 -0
  38. package/dist/commands/addon/index.d.ts +19 -0
  39. package/dist/commands/addon/index.d.ts.map +1 -0
  40. package/dist/commands/addon/index.js +296 -0
  41. package/dist/commands/addon/index.js.map +1 -0
  42. package/dist/commands/addon/init-repo.d.ts +25 -0
  43. package/dist/commands/addon/init-repo.d.ts.map +1 -0
  44. package/dist/commands/addon/init-repo.js +171 -0
  45. package/dist/commands/addon/init-repo.js.map +1 -0
  46. package/dist/commands/addon/install.d.ts +23 -0
  47. package/dist/commands/addon/install.d.ts.map +1 -0
  48. package/dist/commands/addon/install.js +84 -0
  49. package/dist/commands/addon/install.js.map +1 -0
  50. package/dist/commands/addon/list.d.ts +10 -0
  51. package/dist/commands/addon/list.d.ts.map +1 -0
  52. package/dist/commands/addon/list.js +102 -0
  53. package/dist/commands/addon/list.js.map +1 -0
  54. package/dist/commands/addon/manifest-refresh.d.ts +17 -0
  55. package/dist/commands/addon/manifest-refresh.d.ts.map +1 -0
  56. package/dist/commands/addon/manifest-refresh.js +48 -0
  57. package/dist/commands/addon/manifest-refresh.js.map +1 -0
  58. package/dist/commands/addon/migrate.d.ts +40 -0
  59. package/dist/commands/addon/migrate.d.ts.map +1 -0
  60. package/dist/commands/addon/migrate.js +236 -0
  61. package/dist/commands/addon/migrate.js.map +1 -0
  62. package/dist/commands/addon/publish.d.ts +33 -0
  63. package/dist/commands/addon/publish.d.ts.map +1 -0
  64. package/dist/commands/addon/publish.js +236 -0
  65. package/dist/commands/addon/publish.js.map +1 -0
  66. package/dist/commands/addon/scaffold-quality.d.ts +21 -0
  67. package/dist/commands/addon/scaffold-quality.d.ts.map +1 -0
  68. package/dist/commands/addon/scaffold-quality.js +90 -0
  69. package/dist/commands/addon/scaffold-quality.js.map +1 -0
  70. package/dist/commands/addon/sign.d.ts +9 -0
  71. package/dist/commands/addon/sign.d.ts.map +1 -0
  72. package/dist/commands/addon/sign.js +83 -0
  73. package/dist/commands/addon/sign.js.map +1 -0
  74. package/dist/commands/addon/toggle.d.ts +6 -0
  75. package/dist/commands/addon/toggle.d.ts.map +1 -0
  76. package/dist/commands/addon/toggle.js +46 -0
  77. package/dist/commands/addon/toggle.js.map +1 -0
  78. package/dist/commands/agent/index.d.ts +34 -0
  79. package/dist/commands/agent/index.d.ts.map +1 -0
  80. package/dist/commands/agent/index.js +564 -0
  81. package/dist/commands/agent/index.js.map +1 -0
  82. package/dist/commands/brand/index.d.ts +54 -0
  83. package/dist/commands/brand/index.d.ts.map +1 -0
  84. package/dist/commands/brand/index.js +367 -0
  85. package/dist/commands/brand/index.js.map +1 -0
  86. package/dist/commands/build/index.d.ts +53 -0
  87. package/dist/commands/build/index.d.ts.map +1 -0
  88. package/dist/commands/build/index.js +726 -0
  89. package/dist/commands/build/index.js.map +1 -0
  90. package/dist/commands/cache/flush-local.d.ts +31 -0
  91. package/dist/commands/cache/flush-local.d.ts.map +1 -0
  92. package/dist/commands/cache/flush-local.js +161 -0
  93. package/dist/commands/cache/flush-local.js.map +1 -0
  94. package/dist/commands/cache/index.d.ts +14 -0
  95. package/dist/commands/cache/index.d.ts.map +1 -0
  96. package/dist/commands/cache/index.js +453 -0
  97. package/dist/commands/cache/index.js.map +1 -0
  98. package/dist/commands/check/index.d.ts +8 -0
  99. package/dist/commands/check/index.d.ts.map +1 -0
  100. package/dist/commands/check/index.js +1316 -0
  101. package/dist/commands/check/index.js.map +1 -0
  102. package/dist/commands/cloudflare/index.d.ts +8 -0
  103. package/dist/commands/cloudflare/index.d.ts.map +1 -0
  104. package/dist/commands/cloudflare/index.js +453 -0
  105. package/dist/commands/cloudflare/index.js.map +1 -0
  106. package/dist/commands/core/create.d.ts +12 -0
  107. package/dist/commands/core/create.d.ts.map +1 -0
  108. package/dist/commands/core/create.js +206 -0
  109. package/dist/commands/core/create.js.map +1 -0
  110. package/dist/commands/core/delete.d.ts +11 -0
  111. package/dist/commands/core/delete.d.ts.map +1 -0
  112. package/dist/commands/core/delete.js +64 -0
  113. package/dist/commands/core/delete.js.map +1 -0
  114. package/dist/commands/core/env.d.ts +12 -0
  115. package/dist/commands/core/env.d.ts.map +1 -0
  116. package/dist/commands/core/env.js +95 -0
  117. package/dist/commands/core/env.js.map +1 -0
  118. package/dist/commands/core/health.d.ts +6 -0
  119. package/dist/commands/core/health.d.ts.map +1 -0
  120. package/dist/commands/core/health.js +215 -0
  121. package/dist/commands/core/health.js.map +1 -0
  122. package/dist/commands/core/index.d.ts +15 -0
  123. package/dist/commands/core/index.d.ts.map +1 -0
  124. package/dist/commands/core/index.js +86 -0
  125. package/dist/commands/core/index.js.map +1 -0
  126. package/dist/commands/core/list.d.ts +11 -0
  127. package/dist/commands/core/list.d.ts.map +1 -0
  128. package/dist/commands/core/list.js +58 -0
  129. package/dist/commands/core/list.js.map +1 -0
  130. package/dist/commands/core/rebuild.d.ts +13 -0
  131. package/dist/commands/core/rebuild.d.ts.map +1 -0
  132. package/dist/commands/core/rebuild.js +119 -0
  133. package/dist/commands/core/rebuild.js.map +1 -0
  134. package/dist/commands/db/index.d.ts +23 -0
  135. package/dist/commands/db/index.d.ts.map +1 -0
  136. package/dist/commands/db/index.js +355 -0
  137. package/dist/commands/db/index.js.map +1 -0
  138. package/dist/commands/db/promote-silo.d.ts +320 -0
  139. package/dist/commands/db/promote-silo.d.ts.map +1 -0
  140. package/dist/commands/db/promote-silo.js +930 -0
  141. package/dist/commands/db/promote-silo.js.map +1 -0
  142. package/dist/commands/db/relocate.d.ts +41 -0
  143. package/dist/commands/db/relocate.d.ts.map +1 -0
  144. package/dist/commands/db/relocate.js +482 -0
  145. package/dist/commands/db/relocate.js.map +1 -0
  146. package/dist/commands/db/rollback-silo.d.ts +44 -0
  147. package/dist/commands/db/rollback-silo.d.ts.map +1 -0
  148. package/dist/commands/db/rollback-silo.js +402 -0
  149. package/dist/commands/db/rollback-silo.js.map +1 -0
  150. package/dist/commands/deploy/index.d.ts +26 -0
  151. package/dist/commands/deploy/index.d.ts.map +1 -0
  152. package/dist/commands/deploy/index.js +107 -0
  153. package/dist/commands/deploy/index.js.map +1 -0
  154. package/dist/commands/devops/index.d.ts +6 -0
  155. package/dist/commands/devops/index.d.ts.map +1 -0
  156. package/dist/commands/devops/index.js +220 -0
  157. package/dist/commands/devops/index.js.map +1 -0
  158. package/dist/commands/domain/index.d.ts +8 -0
  159. package/dist/commands/domain/index.d.ts.map +1 -0
  160. package/dist/commands/domain/index.js +386 -0
  161. package/dist/commands/domain/index.js.map +1 -0
  162. package/dist/commands/image/index.d.ts +8 -0
  163. package/dist/commands/image/index.d.ts.map +1 -0
  164. package/dist/commands/image/index.js +308 -0
  165. package/dist/commands/image/index.js.map +1 -0
  166. package/dist/commands/install/factory-reset.d.ts +21 -0
  167. package/dist/commands/install/factory-reset.d.ts.map +1 -0
  168. package/dist/commands/install/factory-reset.js +83 -0
  169. package/dist/commands/install/factory-reset.js.map +1 -0
  170. package/dist/commands/install/index.d.ts +17 -0
  171. package/dist/commands/install/index.d.ts.map +1 -0
  172. package/dist/commands/install/index.js +44 -0
  173. package/dist/commands/install/index.js.map +1 -0
  174. package/dist/commands/install/install.d.ts +35 -0
  175. package/dist/commands/install/install.d.ts.map +1 -0
  176. package/dist/commands/install/install.js +171 -0
  177. package/dist/commands/install/install.js.map +1 -0
  178. package/dist/commands/login/index.d.ts +15 -0
  179. package/dist/commands/login/index.d.ts.map +1 -0
  180. package/dist/commands/login/index.js +58 -0
  181. package/dist/commands/login/index.js.map +1 -0
  182. package/dist/commands/nginx/index.d.ts +11 -0
  183. package/dist/commands/nginx/index.d.ts.map +1 -0
  184. package/dist/commands/nginx/index.js +580 -0
  185. package/dist/commands/nginx/index.js.map +1 -0
  186. package/dist/commands/server/bootstrap.d.ts +25 -0
  187. package/dist/commands/server/bootstrap.d.ts.map +1 -0
  188. package/dist/commands/server/bootstrap.js +260 -0
  189. package/dist/commands/server/bootstrap.js.map +1 -0
  190. package/dist/commands/server/index.d.ts +8 -0
  191. package/dist/commands/server/index.d.ts.map +1 -0
  192. package/dist/commands/server/index.js +2524 -0
  193. package/dist/commands/server/index.js.map +1 -0
  194. package/dist/commands/setup/index.d.ts +34 -0
  195. package/dist/commands/setup/index.d.ts.map +1 -0
  196. package/dist/commands/setup/index.js +423 -0
  197. package/dist/commands/setup/index.js.map +1 -0
  198. package/dist/commands/ssl/index.d.ts +8 -0
  199. package/dist/commands/ssl/index.d.ts.map +1 -0
  200. package/dist/commands/ssl/index.js +275 -0
  201. package/dist/commands/ssl/index.js.map +1 -0
  202. package/dist/commands/superadmin/index.d.ts +16 -0
  203. package/dist/commands/superadmin/index.d.ts.map +1 -0
  204. package/dist/commands/superadmin/index.js +81 -0
  205. package/dist/commands/superadmin/index.js.map +1 -0
  206. package/dist/commands/tenant/index.d.ts +6 -0
  207. package/dist/commands/tenant/index.d.ts.map +1 -0
  208. package/dist/commands/tenant/index.js +192 -0
  209. package/dist/commands/tenant/index.js.map +1 -0
  210. package/dist/index.d.ts +11 -0
  211. package/dist/index.d.ts.map +1 -0
  212. package/dist/index.js +107 -0
  213. package/dist/index.js.map +1 -0
  214. package/dist/lib/addon-sign.d.ts +23 -0
  215. package/dist/lib/addon-sign.d.ts.map +1 -0
  216. package/dist/lib/addon-sign.js +39 -0
  217. package/dist/lib/addon-sign.js.map +1 -0
  218. package/dist/lib/addon-sign.test.d.ts +2 -0
  219. package/dist/lib/addon-sign.test.d.ts.map +1 -0
  220. package/dist/lib/addon-sign.test.js +27 -0
  221. package/dist/lib/addon-sign.test.js.map +1 -0
  222. package/dist/lib/cdn.d.ts +25 -0
  223. package/dist/lib/cdn.d.ts.map +1 -0
  224. package/dist/lib/cdn.js +131 -0
  225. package/dist/lib/cdn.js.map +1 -0
  226. package/dist/lib/cloudflare.d.ts +133 -0
  227. package/dist/lib/cloudflare.d.ts.map +1 -0
  228. package/dist/lib/cloudflare.js +435 -0
  229. package/dist/lib/cloudflare.js.map +1 -0
  230. package/dist/lib/config.d.ts +96 -0
  231. package/dist/lib/config.d.ts.map +1 -0
  232. package/dist/lib/config.js +132 -0
  233. package/dist/lib/config.js.map +1 -0
  234. package/dist/lib/env.d.ts +8 -0
  235. package/dist/lib/env.d.ts.map +1 -0
  236. package/dist/lib/env.js +64 -0
  237. package/dist/lib/env.js.map +1 -0
  238. package/dist/lib/hosts.d.ts +194 -0
  239. package/dist/lib/hosts.d.ts.map +1 -0
  240. package/dist/lib/hosts.js +183 -0
  241. package/dist/lib/hosts.js.map +1 -0
  242. package/dist/lib/logger.d.ts +68 -0
  243. package/dist/lib/logger.d.ts.map +1 -0
  244. package/dist/lib/logger.js +130 -0
  245. package/dist/lib/logger.js.map +1 -0
  246. package/dist/lib/nginx-config.d.ts +78 -0
  247. package/dist/lib/nginx-config.d.ts.map +1 -0
  248. package/dist/lib/nginx-config.js +736 -0
  249. package/dist/lib/nginx-config.js.map +1 -0
  250. package/dist/lib/ops/addon-dev.d.ts +93 -0
  251. package/dist/lib/ops/addon-dev.d.ts.map +1 -0
  252. package/dist/lib/ops/addon-dev.js +237 -0
  253. package/dist/lib/ops/addon-dev.js.map +1 -0
  254. package/dist/lib/ops/addon-quality.d.ts +38 -0
  255. package/dist/lib/ops/addon-quality.d.ts.map +1 -0
  256. package/dist/lib/ops/addon-quality.js +338 -0
  257. package/dist/lib/ops/addon-quality.js.map +1 -0
  258. package/dist/lib/ops/addon-routes.d.ts +49 -0
  259. package/dist/lib/ops/addon-routes.d.ts.map +1 -0
  260. package/dist/lib/ops/addon-routes.js +189 -0
  261. package/dist/lib/ops/addon-routes.js.map +1 -0
  262. package/dist/lib/ops/addon.d.ts +120 -0
  263. package/dist/lib/ops/addon.d.ts.map +1 -0
  264. package/dist/lib/ops/addon.js +260 -0
  265. package/dist/lib/ops/addon.js.map +1 -0
  266. package/dist/lib/ops/cdn.d.ts +87 -0
  267. package/dist/lib/ops/cdn.d.ts.map +1 -0
  268. package/dist/lib/ops/cdn.js +170 -0
  269. package/dist/lib/ops/cdn.js.map +1 -0
  270. package/dist/lib/ops/cf.d.ts +36 -0
  271. package/dist/lib/ops/cf.d.ts.map +1 -0
  272. package/dist/lib/ops/cf.js +114 -0
  273. package/dist/lib/ops/cf.js.map +1 -0
  274. package/dist/lib/ops/compose.d.ts +95 -0
  275. package/dist/lib/ops/compose.d.ts.map +1 -0
  276. package/dist/lib/ops/compose.js +165 -0
  277. package/dist/lib/ops/compose.js.map +1 -0
  278. package/dist/lib/ops/core.d.ts +117 -0
  279. package/dist/lib/ops/core.d.ts.map +1 -0
  280. package/dist/lib/ops/core.js +322 -0
  281. package/dist/lib/ops/core.js.map +1 -0
  282. package/dist/lib/ops/db.d.ts +116 -0
  283. package/dist/lib/ops/db.d.ts.map +1 -0
  284. package/dist/lib/ops/db.js +351 -0
  285. package/dist/lib/ops/db.js.map +1 -0
  286. package/dist/lib/ops/dns.d.ts +111 -0
  287. package/dist/lib/ops/dns.d.ts.map +1 -0
  288. package/dist/lib/ops/dns.js +306 -0
  289. package/dist/lib/ops/dns.js.map +1 -0
  290. package/dist/lib/ops/image.d.ts +94 -0
  291. package/dist/lib/ops/image.d.ts.map +1 -0
  292. package/dist/lib/ops/image.js +159 -0
  293. package/dist/lib/ops/image.js.map +1 -0
  294. package/dist/lib/ops/nginx.d.ts +114 -0
  295. package/dist/lib/ops/nginx.d.ts.map +1 -0
  296. package/dist/lib/ops/nginx.js +388 -0
  297. package/dist/lib/ops/nginx.js.map +1 -0
  298. package/dist/lib/ops/redis.d.ts +7 -0
  299. package/dist/lib/ops/redis.d.ts.map +1 -0
  300. package/dist/lib/ops/redis.js +35 -0
  301. package/dist/lib/ops/redis.js.map +1 -0
  302. package/dist/lib/ops/ssh.d.ts +127 -0
  303. package/dist/lib/ops/ssh.d.ts.map +1 -0
  304. package/dist/lib/ops/ssh.js +269 -0
  305. package/dist/lib/ops/ssh.js.map +1 -0
  306. package/dist/lib/prompts.d.ts +46 -0
  307. package/dist/lib/prompts.d.ts.map +1 -0
  308. package/dist/lib/prompts.js +113 -0
  309. package/dist/lib/prompts.js.map +1 -0
  310. package/dist/lib/sast.d.ts +43 -0
  311. package/dist/lib/sast.d.ts.map +1 -0
  312. package/dist/lib/sast.js +79 -0
  313. package/dist/lib/sast.js.map +1 -0
  314. package/dist/lib/sast.test.d.ts +2 -0
  315. package/dist/lib/sast.test.d.ts.map +1 -0
  316. package/dist/lib/sast.test.js +33 -0
  317. package/dist/lib/sast.test.js.map +1 -0
  318. package/dist/lib/shell.d.ts +61 -0
  319. package/dist/lib/shell.d.ts.map +1 -0
  320. package/dist/lib/shell.js +183 -0
  321. package/dist/lib/shell.js.map +1 -0
  322. package/dist/lib/ssh-config.d.ts +37 -0
  323. package/dist/lib/ssh-config.d.ts.map +1 -0
  324. package/dist/lib/ssh-config.js +122 -0
  325. package/dist/lib/ssh-config.js.map +1 -0
  326. package/dist/lib/tenant-scope.d.ts +38 -0
  327. package/dist/lib/tenant-scope.d.ts.map +1 -0
  328. package/dist/lib/tenant-scope.js +129 -0
  329. package/dist/lib/tenant-scope.js.map +1 -0
  330. package/dist/lib/tenant-scope.test.d.ts +2 -0
  331. package/dist/lib/tenant-scope.test.d.ts.map +1 -0
  332. package/dist/lib/tenant-scope.test.js +223 -0
  333. package/dist/lib/tenant-scope.test.js.map +1 -0
  334. package/package.json +58 -0
  335. package/templates/bootstrap/.env.template +54 -0
  336. package/templates/bootstrap/docker-compose.yml +145 -0
  337. package/templates/vhost.conf.tmpl +446 -0
@@ -0,0 +1,1316 @@
1
+ /**
2
+ * CiCore CLI - Health Check & Diagnostics
3
+ *
4
+ * Comprehensive system checks for ciCore deployments
5
+ */
6
+ import { log, spinner, formatTable } from '../../lib/logger.js';
7
+ import { exec } from '../../lib/shell.js';
8
+ import { paths, getDockerRegistry } from '../../lib/config.js';
9
+ import { getSSHHost, buildSSHCommand, listSSHHosts } from '../../lib/ssh-config.js';
10
+ import fs from 'fs-extra';
11
+ import path from 'path';
12
+ export function registerCheckCommands(program) {
13
+ const check = program
14
+ .command('check')
15
+ .description('Health checks and diagnostics');
16
+ // vu check:all
17
+ check
18
+ .command('all')
19
+ .description('Run all checks')
20
+ .option('-h, --host <host>', 'SSH host for server checks', 'cicore')
21
+ .option('--local', 'Only run local checks')
22
+ .option('--server', 'Only run server checks')
23
+ .action(async (options) => {
24
+ await runAllChecks(options);
25
+ });
26
+ // vu check:local
27
+ check
28
+ .command('local')
29
+ .description('Check local development environment')
30
+ .action(async () => {
31
+ await checkLocal();
32
+ });
33
+ // vu check:server
34
+ check
35
+ .command('server')
36
+ .description('Check server health')
37
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
38
+ .action(async (options) => {
39
+ await checkServer(options);
40
+ });
41
+ // vu check:docker
42
+ check
43
+ .command('docker')
44
+ .description('Check Docker images and containers')
45
+ .option('-h, --host <host>', 'SSH host (or "local")', 'local')
46
+ .action(async (options) => {
47
+ await checkDocker(options);
48
+ });
49
+ // vu check:files
50
+ check
51
+ .command('files')
52
+ .description('Check ciCore file structure')
53
+ .option('-h, --host <host>', 'SSH host for server check', 'cicore')
54
+ .option('-c, --core <core>', 'Core name to check', 'core1')
55
+ .action(async (options) => {
56
+ await checkFiles(options);
57
+ });
58
+ // vu check:addons
59
+ check
60
+ .command('addons')
61
+ .description('Validate addon configurations')
62
+ .option('-h, --host <host>', 'SSH host (or "local")', 'local')
63
+ .option('-c, --core <core>', 'Core name', 'core1')
64
+ .action(async (options) => {
65
+ await checkAddons(options);
66
+ });
67
+ // vu check:ssl
68
+ check
69
+ .command('ssl')
70
+ .description('Check SSL certificates')
71
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
72
+ .option('-d, --domain <domain>', 'Domain to check')
73
+ .action(async (options) => {
74
+ await checkSSL(options);
75
+ });
76
+ // vu check:connectivity
77
+ check
78
+ .command('connectivity')
79
+ .description('Check network connectivity')
80
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
81
+ .action(async (options) => {
82
+ await checkConnectivity(options);
83
+ });
84
+ // vu check:requirements
85
+ check
86
+ .command('requirements')
87
+ .alias('req')
88
+ .description('Check server requirements for ciCore installation')
89
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
90
+ .action(async (options) => {
91
+ await checkRequirements(options);
92
+ });
93
+ // vu check:tree
94
+ check
95
+ .command('tree')
96
+ .description('Show server directory structure')
97
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
98
+ .option('-c, --core <core>', 'Core name to show')
99
+ .option('-d, --depth <depth>', 'Tree depth', '3')
100
+ .action(async (options) => {
101
+ await showTree(options);
102
+ });
103
+ // vu check:api
104
+ check
105
+ .command('api')
106
+ .description('Check API endpoints')
107
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
108
+ .option('-d, --domain <domain>', 'Domain to check')
109
+ .action(async (options) => {
110
+ await checkAPI(options);
111
+ });
112
+ // vu check:services
113
+ check
114
+ .command('services')
115
+ .description('Check all ciCore services status')
116
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
117
+ .action(async (options) => {
118
+ await checkServices(options);
119
+ });
120
+ // vu check:ports
121
+ check
122
+ .command('ports')
123
+ .description('Check required ports')
124
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
125
+ .action(async (options) => {
126
+ await checkPorts(options);
127
+ });
128
+ // vu check:install
129
+ check
130
+ .command('install')
131
+ .alias('pre')
132
+ .description('Pre-installation check (all requirements)')
133
+ .option('-h, --host <host>', 'SSH host name', 'cicore')
134
+ .action(async (options) => {
135
+ await preInstallCheck(options);
136
+ });
137
+ }
138
+ // ============================================
139
+ // RUN ALL CHECKS
140
+ // ============================================
141
+ async function runAllChecks(options) {
142
+ log.title('🔍 ciCore System Diagnostics');
143
+ log.blank();
144
+ const results = [];
145
+ if (!options.server) {
146
+ log.info('=== Local Environment ===');
147
+ const localResults = await runLocalChecks();
148
+ results.push(...localResults);
149
+ log.blank();
150
+ }
151
+ if (!options.local) {
152
+ log.info('=== Server Environment ===');
153
+ const serverResults = await runServerChecks(options.host);
154
+ results.push(...serverResults);
155
+ log.blank();
156
+ }
157
+ // Summary
158
+ printCheckSummary(results);
159
+ }
160
+ // ============================================
161
+ // LOCAL CHECKS
162
+ // ============================================
163
+ async function checkLocal() {
164
+ log.title('🖥️ Local Environment Check');
165
+ log.blank();
166
+ const results = await runLocalChecks();
167
+ printCheckSummary(results);
168
+ }
169
+ async function runLocalChecks() {
170
+ const results = [];
171
+ // Check Node.js
172
+ const nodeSpinner = spinner('Checking Node.js...').start();
173
+ const nodeResult = await exec('node', ['--version'], { silent: true });
174
+ if (nodeResult.success) {
175
+ const version = nodeResult.stdout.trim();
176
+ const major = parseInt(version.replace('v', '').split('.')[0]);
177
+ if (major >= 18) {
178
+ nodeSpinner.succeed(`Node.js: ${version}`);
179
+ results.push({ name: 'Node.js', status: 'pass', message: version });
180
+ }
181
+ else {
182
+ nodeSpinner.warn(`Node.js: ${version} (recommend v18+)`);
183
+ results.push({ name: 'Node.js', status: 'warn', message: `${version} - recommend v18+` });
184
+ }
185
+ }
186
+ else {
187
+ nodeSpinner.fail('Node.js not found');
188
+ results.push({ name: 'Node.js', status: 'fail', message: 'Not installed' });
189
+ }
190
+ // Check npm
191
+ const npmSpinner = spinner('Checking npm...').start();
192
+ const npmResult = await exec('npm', ['--version'], { silent: true });
193
+ if (npmResult.success) {
194
+ npmSpinner.succeed(`npm: v${npmResult.stdout.trim()}`);
195
+ results.push({ name: 'npm', status: 'pass', message: `v${npmResult.stdout.trim()}` });
196
+ }
197
+ else {
198
+ npmSpinner.fail('npm not found');
199
+ results.push({ name: 'npm', status: 'fail', message: 'Not installed' });
200
+ }
201
+ // Check Docker
202
+ const dockerSpinner = spinner('Checking Docker...').start();
203
+ const dockerResult = await exec('docker', ['--version'], { silent: true });
204
+ if (dockerResult.success) {
205
+ dockerSpinner.succeed(`Docker: ${dockerResult.stdout.trim().split(' ')[2]?.replace(',', '')}`);
206
+ results.push({ name: 'Docker', status: 'pass', message: 'Installed' });
207
+ // Check if Docker is running
208
+ const dockerPsResult = await exec('docker', ['ps'], { silent: true });
209
+ if (!dockerPsResult.success) {
210
+ results.push({ name: 'Docker Daemon', status: 'warn', message: 'Not running' });
211
+ }
212
+ }
213
+ else {
214
+ dockerSpinner.fail('Docker not found');
215
+ results.push({ name: 'Docker', status: 'fail', message: 'Not installed' });
216
+ }
217
+ // Check Git
218
+ const gitSpinner = spinner('Checking Git...').start();
219
+ const gitResult = await exec('git', ['--version'], { silent: true });
220
+ if (gitResult.success) {
221
+ gitSpinner.succeed(`Git: ${gitResult.stdout.trim().split(' ')[2]}`);
222
+ results.push({ name: 'Git', status: 'pass', message: 'Installed' });
223
+ }
224
+ else {
225
+ gitSpinner.fail('Git not found');
226
+ results.push({ name: 'Git', status: 'fail', message: 'Not installed' });
227
+ }
228
+ // Check SSH config
229
+ const sshSpinner = spinner('Checking SSH config...').start();
230
+ const hosts = await listSSHHosts();
231
+ const vucoreHost = hosts.find(h => h.name === 'cicore');
232
+ if (vucoreHost) {
233
+ sshSpinner.succeed(`SSH: vucore host configured (${vucoreHost.hostname})`);
234
+ results.push({ name: 'SSH Config', status: 'pass', message: `vucore → ${vucoreHost.hostname}` });
235
+ }
236
+ else if (hosts.length > 0) {
237
+ sshSpinner.warn(`SSH: ${hosts.length} hosts found, but no 'cicore' host`);
238
+ results.push({ name: 'SSH Config', status: 'warn', message: 'No vucore host' });
239
+ }
240
+ else {
241
+ sshSpinner.fail('SSH: No hosts configured');
242
+ results.push({ name: 'SSH Config', status: 'fail', message: 'No hosts in ~/.ssh/config' });
243
+ }
244
+ // Check ciCore project structure
245
+ const projectSpinner = spinner('Checking project structure...').start();
246
+ const requiredFiles = [
247
+ 'package.json',
248
+ 'nuxt.config.ts',
249
+ 'docker-compose.dev.yml',
250
+ 'Dockerfile.nuxt.production',
251
+ 'Dockerfile.php.production',
252
+ 'addons',
253
+ ];
254
+ const missingFiles = [];
255
+ for (const file of requiredFiles) {
256
+ if (!await fs.pathExists(path.join(paths.dev.root, file))) {
257
+ missingFiles.push(file);
258
+ }
259
+ }
260
+ if (missingFiles.length === 0) {
261
+ projectSpinner.succeed('Project structure: OK');
262
+ results.push({ name: 'Project Structure', status: 'pass', message: 'All files present' });
263
+ }
264
+ else {
265
+ projectSpinner.warn(`Project structure: Missing ${missingFiles.length} files`);
266
+ results.push({
267
+ name: 'Project Structure',
268
+ status: 'warn',
269
+ message: `Missing: ${missingFiles.join(', ')}`
270
+ });
271
+ }
272
+ // Check local containers
273
+ const containerSpinner = spinner('Checking local containers...').start();
274
+ const psResult = await exec('docker', ['ps', '--filter', 'name=vucore_dev', '--format', '{{.Names}}'], { silent: true });
275
+ if (psResult.success && psResult.stdout.trim()) {
276
+ const containers = psResult.stdout.trim().split('\n').length;
277
+ containerSpinner.succeed(`Local containers: ${containers} running`);
278
+ results.push({ name: 'Local Containers', status: 'pass', message: `${containers} running` });
279
+ }
280
+ else {
281
+ containerSpinner.warn('Local containers: None running');
282
+ results.push({ name: 'Local Containers', status: 'warn', message: 'None running' });
283
+ }
284
+ return results;
285
+ }
286
+ // ============================================
287
+ // SERVER CHECKS
288
+ // ============================================
289
+ async function checkServer(options) {
290
+ log.title('🌐 Server Health Check');
291
+ log.blank();
292
+ const results = await runServerChecks(options.host);
293
+ printCheckSummary(results);
294
+ }
295
+ async function runServerChecks(hostName) {
296
+ const results = [];
297
+ const host = await getSSHHost(hostName);
298
+ if (!host) {
299
+ results.push({ name: 'SSH Connection', status: 'fail', message: `Host "${hostName}" not found` });
300
+ return results;
301
+ }
302
+ // Test SSH connection
303
+ const sshSpinner = spinner(`Connecting to ${host.hostname}...`).start();
304
+ const sshResult = await runSSH(host, 'echo "OK"');
305
+ if (!sshResult.success) {
306
+ sshSpinner.fail('SSH connection failed');
307
+ results.push({ name: 'SSH Connection', status: 'fail', message: 'Connection failed' });
308
+ return results;
309
+ }
310
+ sshSpinner.succeed(`Connected to ${host.hostname}`);
311
+ results.push({ name: 'SSH Connection', status: 'pass', message: host.hostname });
312
+ // Check Docker
313
+ const dockerSpinner = spinner('Checking Docker...').start();
314
+ const dockerResult = await runSSH(host, 'docker --version');
315
+ if (dockerResult.success) {
316
+ dockerSpinner.succeed('Docker: Installed');
317
+ results.push({ name: 'Docker', status: 'pass', message: 'Installed' });
318
+ }
319
+ else {
320
+ dockerSpinner.fail('Docker: Not installed');
321
+ results.push({ name: 'Docker', status: 'fail', message: 'Not installed' });
322
+ }
323
+ // Check containers
324
+ const containerSpinner = spinner('Checking containers...').start();
325
+ const psResult = await runSSH(host, 'docker ps --format "{{.Names}}:{{.Status}}" | grep vucore');
326
+ if (psResult.success && psResult.stdout.trim()) {
327
+ const containers = psResult.stdout.trim().split('\n');
328
+ const running = containers.filter(c => c.includes('Up')).length;
329
+ const total = containers.length;
330
+ if (running === total) {
331
+ containerSpinner.succeed(`Containers: ${running}/${total} running`);
332
+ results.push({ name: 'Containers', status: 'pass', message: `${running}/${total} running` });
333
+ }
334
+ else {
335
+ containerSpinner.warn(`Containers: ${running}/${total} running`);
336
+ results.push({ name: 'Containers', status: 'warn', message: `${running}/${total} running` });
337
+ }
338
+ }
339
+ else {
340
+ containerSpinner.fail('Containers: None found');
341
+ results.push({ name: 'Containers', status: 'fail', message: 'No ciCore containers' });
342
+ }
343
+ // Check disk space
344
+ const diskSpinner = spinner('Checking disk space...').start();
345
+ const diskResult = await runSSH(host, "df -h / | tail -1 | awk '{print $5}'");
346
+ if (diskResult.success) {
347
+ const usage = parseInt(diskResult.stdout.trim().replace('%', ''));
348
+ if (usage < 80) {
349
+ diskSpinner.succeed(`Disk: ${usage}% used`);
350
+ results.push({ name: 'Disk Space', status: 'pass', message: `${usage}% used` });
351
+ }
352
+ else if (usage < 90) {
353
+ diskSpinner.warn(`Disk: ${usage}% used`);
354
+ results.push({ name: 'Disk Space', status: 'warn', message: `${usage}% used - consider cleanup` });
355
+ }
356
+ else {
357
+ diskSpinner.fail(`Disk: ${usage}% used`);
358
+ results.push({ name: 'Disk Space', status: 'fail', message: `${usage}% used - CRITICAL` });
359
+ }
360
+ }
361
+ // Check memory
362
+ const memSpinner = spinner('Checking memory...').start();
363
+ const memResult = await runSSH(host, "free | grep Mem | awk '{printf \"%.0f\", $3/$2 * 100}'");
364
+ if (memResult.success) {
365
+ const usage = parseInt(memResult.stdout.trim());
366
+ if (usage < 80) {
367
+ memSpinner.succeed(`Memory: ${usage}% used`);
368
+ results.push({ name: 'Memory', status: 'pass', message: `${usage}% used` });
369
+ }
370
+ else if (usage < 90) {
371
+ memSpinner.warn(`Memory: ${usage}% used`);
372
+ results.push({ name: 'Memory', status: 'warn', message: `${usage}% used` });
373
+ }
374
+ else {
375
+ memSpinner.fail(`Memory: ${usage}% used`);
376
+ results.push({ name: 'Memory', status: 'fail', message: `${usage}% used - CRITICAL` });
377
+ }
378
+ }
379
+ // Check Nginx
380
+ const nginxSpinner = spinner('Checking Nginx...').start();
381
+ const nginxResult = await runSSH(host, 'systemctl is-active nginx');
382
+ if (nginxResult.success && nginxResult.stdout.trim() === 'active') {
383
+ nginxSpinner.succeed('Nginx: Active');
384
+ results.push({ name: 'Nginx', status: 'pass', message: 'Active' });
385
+ }
386
+ else {
387
+ nginxSpinner.fail('Nginx: Not active');
388
+ results.push({ name: 'Nginx', status: 'fail', message: 'Not active' });
389
+ }
390
+ // Check API health
391
+ const apiSpinner = spinner('Checking API health...').start();
392
+ const apiResult = await runSSH(host, 'curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/health 2>/dev/null || echo "000"');
393
+ const statusCode = apiResult.stdout.trim();
394
+ if (statusCode === '200') {
395
+ apiSpinner.succeed('API: Healthy');
396
+ results.push({ name: 'API Health', status: 'pass', message: 'HTTP 200' });
397
+ }
398
+ else if (statusCode === '000') {
399
+ apiSpinner.fail('API: Not responding');
400
+ results.push({ name: 'API Health', status: 'fail', message: 'Not responding' });
401
+ }
402
+ else {
403
+ apiSpinner.warn(`API: HTTP ${statusCode}`);
404
+ results.push({ name: 'API Health', status: 'warn', message: `HTTP ${statusCode}` });
405
+ }
406
+ return results;
407
+ }
408
+ // ============================================
409
+ // DOCKER CHECKS
410
+ // ============================================
411
+ async function checkDocker(options) {
412
+ log.title('🐳 Docker Check');
413
+ log.blank();
414
+ const results = [];
415
+ const registry = getDockerRegistry();
416
+ if (options.host === 'local') {
417
+ // Local Docker check
418
+ const imagesSpinner = spinner('Checking local images...').start();
419
+ const imagesResult = await exec('docker', ['images', '--format', '{{.Repository}}:{{.Tag}}\t{{.Size}}', '--filter', `reference=${registry}/vucore-*`], { silent: true });
420
+ if (imagesResult.success && imagesResult.stdout.trim()) {
421
+ const images = imagesResult.stdout.trim().split('\n');
422
+ imagesSpinner.succeed(`Found ${images.length} ciCore images`);
423
+ log.blank();
424
+ images.forEach(img => {
425
+ const [name, size] = img.split('\t');
426
+ log.item(`${name} (${size})`);
427
+ });
428
+ results.push({ name: 'Local Images', status: 'pass', message: `${images.length} images` });
429
+ }
430
+ else {
431
+ imagesSpinner.warn('No ciCore images found');
432
+ results.push({ name: 'Local Images', status: 'warn', message: 'None found' });
433
+ }
434
+ }
435
+ else {
436
+ // Remote Docker check
437
+ const host = await getSSHHost(options.host);
438
+ if (!host) {
439
+ log.error(`SSH host "${options.host}" not found`);
440
+ return;
441
+ }
442
+ const imagesSpinner = spinner('Checking server images...').start();
443
+ const imagesResult = await runSSH(host, `docker images --format "{{.Repository}}:{{.Tag}}\t{{.Size}}" | grep vucore`);
444
+ if (imagesResult.success && imagesResult.stdout.trim()) {
445
+ const images = imagesResult.stdout.trim().split('\n');
446
+ imagesSpinner.succeed(`Found ${images.length} ciCore images`);
447
+ log.blank();
448
+ images.forEach(img => {
449
+ const [name, size] = img.split('\t');
450
+ log.item(`${name} (${size})`);
451
+ });
452
+ results.push({ name: 'Server Images', status: 'pass', message: `${images.length} images` });
453
+ }
454
+ else {
455
+ imagesSpinner.warn('No ciCore images found');
456
+ results.push({ name: 'Server Images', status: 'warn', message: 'None found' });
457
+ }
458
+ }
459
+ log.blank();
460
+ printCheckSummary(results);
461
+ }
462
+ // ============================================
463
+ // FILE STRUCTURE CHECKS
464
+ // ============================================
465
+ async function checkFiles(options) {
466
+ log.title('📁 File Structure Check');
467
+ log.blank();
468
+ const results = [];
469
+ const host = await getSSHHost(options.host);
470
+ if (!host) {
471
+ log.error(`SSH host "${options.host}" not found`);
472
+ return;
473
+ }
474
+ const coreName = options.core;
475
+ const requiredDirs = [
476
+ `/home/cores/${coreName}`,
477
+ `/home/cores/${coreName}/addons`,
478
+ `/home/cores/${coreName}/storage`,
479
+ '/home/services',
480
+ '/home/scripts',
481
+ '/home/nginx/conf.d',
482
+ ];
483
+ const requiredFiles = [
484
+ `/home/cores/${coreName}/.env`,
485
+ '/home/services/docker-core-nuxt.yml',
486
+ '/home/services/docker-core-php.yml',
487
+ '/home/scripts/cleanup-and-rebuild-nuxt.sh',
488
+ '/home/scripts/cleanup-and-rebuild-php.sh',
489
+ ];
490
+ // Check directories
491
+ for (const dir of requiredDirs) {
492
+ const checkSpinner = spinner(`Checking ${dir}...`).start();
493
+ const result = await runSSH(host, `test -d "${dir}" && echo "OK" || echo "MISSING"`);
494
+ if (result.stdout.trim() === 'OK') {
495
+ checkSpinner.succeed(`${dir}: OK`);
496
+ results.push({ name: dir, status: 'pass', message: 'Exists' });
497
+ }
498
+ else {
499
+ checkSpinner.fail(`${dir}: MISSING`);
500
+ results.push({ name: dir, status: 'fail', message: 'Missing' });
501
+ }
502
+ }
503
+ // Check files
504
+ for (const file of requiredFiles) {
505
+ const checkSpinner = spinner(`Checking ${file}...`).start();
506
+ const result = await runSSH(host, `test -f "${file}" && echo "OK" || echo "MISSING"`);
507
+ if (result.stdout.trim() === 'OK') {
508
+ checkSpinner.succeed(`${file}: OK`);
509
+ results.push({ name: file, status: 'pass', message: 'Exists' });
510
+ }
511
+ else {
512
+ checkSpinner.fail(`${file}: MISSING`);
513
+ results.push({ name: file, status: 'fail', message: 'Missing' });
514
+ }
515
+ }
516
+ log.blank();
517
+ printCheckSummary(results);
518
+ }
519
+ // ============================================
520
+ // ADDON CHECKS
521
+ // ============================================
522
+ async function checkAddons(options) {
523
+ log.title('🧩 Addon Validation');
524
+ log.blank();
525
+ const results = [];
526
+ if (options.host === 'local') {
527
+ // Local addon check
528
+ const addonsPath = paths.dev.addons;
529
+ if (!await fs.pathExists(addonsPath)) {
530
+ log.error('Addons directory not found');
531
+ return;
532
+ }
533
+ const entries = await fs.readdir(addonsPath, { withFileTypes: true });
534
+ const addons = entries.filter(e => e.isDirectory() && !e.name.startsWith('.'));
535
+ for (const addon of addons) {
536
+ const addonPath = path.join(addonsPath, addon.name);
537
+ const addonSpinner = spinner(`Checking ${addon.name}...`).start();
538
+ const issues = [];
539
+ // Check addon.json
540
+ const addonJsonPath = path.join(addonPath, 'addon.json');
541
+ if (!await fs.pathExists(addonJsonPath)) {
542
+ issues.push('Missing addon.json');
543
+ }
544
+ else {
545
+ try {
546
+ const config = await fs.readJson(addonJsonPath);
547
+ if (!config.name)
548
+ issues.push('addon.json: missing name');
549
+ if (!config.version)
550
+ issues.push('addon.json: missing version');
551
+ }
552
+ catch {
553
+ issues.push('addon.json: invalid JSON');
554
+ }
555
+ }
556
+ // Check Backend
557
+ if (!await fs.pathExists(path.join(addonPath, 'Backend'))) {
558
+ issues.push('Missing Backend/');
559
+ }
560
+ // Check entry point
561
+ const hasEntry = await fs.pathExists(path.join(addonPath, 'ui', 'entry.js')) ||
562
+ await fs.pathExists(path.join(addonPath, 'ui', 'entry.ts'));
563
+ if (!hasEntry) {
564
+ issues.push('Missing ui/entry.js or entry.ts');
565
+ }
566
+ if (issues.length === 0) {
567
+ addonSpinner.succeed(`${addon.name}: Valid`);
568
+ results.push({ name: addon.name, status: 'pass', message: 'Valid' });
569
+ }
570
+ else {
571
+ addonSpinner.warn(`${addon.name}: ${issues.length} issue(s)`);
572
+ results.push({ name: addon.name, status: 'warn', message: issues.join(', ') });
573
+ }
574
+ }
575
+ }
576
+ else {
577
+ // Server addon check
578
+ const host = await getSSHHost(options.host);
579
+ if (!host) {
580
+ log.error(`SSH host "${options.host}" not found`);
581
+ return;
582
+ }
583
+ const addonsResult = await runSSH(host, `ls -1 /home/cores/${options.core}/addons/ 2>/dev/null`);
584
+ if (!addonsResult.success || !addonsResult.stdout.trim()) {
585
+ log.info('No addons found on server');
586
+ return;
587
+ }
588
+ const addons = addonsResult.stdout.trim().split('\n');
589
+ for (const addon of addons) {
590
+ const addonSpinner = spinner(`Checking ${addon}...`).start();
591
+ const addonPath = `/home/cores/${options.core}/addons/${addon}`;
592
+ const issues = [];
593
+ // Check addon.json
594
+ const jsonResult = await runSSH(host, `test -f "${addonPath}/addon.json" && echo "OK"`);
595
+ if (jsonResult.stdout.trim() !== 'OK') {
596
+ issues.push('Missing addon.json');
597
+ }
598
+ // Check ui-manifest.json
599
+ const manifestResult = await runSSH(host, `test -f "${addonPath}/ui-manifest.json" && echo "OK"`);
600
+ if (manifestResult.stdout.trim() !== 'OK') {
601
+ issues.push('Missing ui-manifest.json');
602
+ }
603
+ // Check Backend
604
+ const backendResult = await runSSH(host, `test -d "${addonPath}/Backend" && echo "OK"`);
605
+ if (backendResult.stdout.trim() !== 'OK') {
606
+ issues.push('Missing Backend/');
607
+ }
608
+ if (issues.length === 0) {
609
+ addonSpinner.succeed(`${addon}: Valid`);
610
+ results.push({ name: addon, status: 'pass', message: 'Valid' });
611
+ }
612
+ else {
613
+ addonSpinner.warn(`${addon}: ${issues.length} issue(s)`);
614
+ results.push({ name: addon, status: 'warn', message: issues.join(', ') });
615
+ }
616
+ }
617
+ }
618
+ log.blank();
619
+ printCheckSummary(results);
620
+ }
621
+ // ============================================
622
+ // SSL CHECKS
623
+ // ============================================
624
+ async function checkSSL(options) {
625
+ log.title('🔒 SSL Certificate Check');
626
+ log.blank();
627
+ const host = await getSSHHost(options.host);
628
+ if (!host) {
629
+ log.error(`SSH host "${options.host}" not found`);
630
+ return;
631
+ }
632
+ const results = [];
633
+ // List certificates
634
+ const certSpinner = spinner('Checking certificates...').start();
635
+ const certResult = await runSSH(host, 'certbot certificates 2>/dev/null | grep -E "(Certificate Name|Expiry Date|Domains)"');
636
+ if (certResult.success && certResult.stdout.trim()) {
637
+ certSpinner.succeed('Certificates found');
638
+ log.blank();
639
+ console.log(certResult.stdout);
640
+ results.push({ name: 'SSL Certificates', status: 'pass', message: 'Found' });
641
+ }
642
+ else {
643
+ certSpinner.warn('No certificates found');
644
+ results.push({ name: 'SSL Certificates', status: 'warn', message: 'None found' });
645
+ }
646
+ // Check specific domain if provided
647
+ if (options.domain) {
648
+ const domainSpinner = spinner(`Checking ${options.domain}...`).start();
649
+ const domainResult = await runSSH(host, `openssl s_client -connect ${options.domain}:443 -servername ${options.domain} </dev/null 2>/dev/null | openssl x509 -noout -dates 2>/dev/null`);
650
+ if (domainResult.success && domainResult.stdout.trim()) {
651
+ domainSpinner.succeed(`${options.domain}: Valid`);
652
+ log.blank();
653
+ console.log(domainResult.stdout);
654
+ results.push({ name: options.domain, status: 'pass', message: 'Valid' });
655
+ }
656
+ else {
657
+ domainSpinner.fail(`${options.domain}: Invalid or not found`);
658
+ results.push({ name: options.domain, status: 'fail', message: 'Invalid' });
659
+ }
660
+ }
661
+ log.blank();
662
+ printCheckSummary(results);
663
+ }
664
+ // ============================================
665
+ // CONNECTIVITY CHECKS
666
+ // ============================================
667
+ async function checkConnectivity(options) {
668
+ log.title('🌐 Connectivity Check');
669
+ log.blank();
670
+ const host = await getSSHHost(options.host);
671
+ if (!host) {
672
+ log.error(`SSH host "${options.host}" not found`);
673
+ return;
674
+ }
675
+ const results = [];
676
+ // Check internet
677
+ const internetSpinner = spinner('Checking internet...').start();
678
+ const internetResult = await runSSH(host, 'curl -s -o /dev/null -w "%{http_code}" https://google.com');
679
+ if (internetResult.stdout.trim() === '200' || internetResult.stdout.trim() === '301') {
680
+ internetSpinner.succeed('Internet: Connected');
681
+ results.push({ name: 'Internet', status: 'pass', message: 'Connected' });
682
+ }
683
+ else {
684
+ internetSpinner.fail('Internet: Not connected');
685
+ results.push({ name: 'Internet', status: 'fail', message: 'Not connected' });
686
+ }
687
+ // Check Docker Hub
688
+ const dockerHubSpinner = spinner('Checking Docker Hub...').start();
689
+ const dockerHubResult = await runSSH(host, 'curl -s -o /dev/null -w "%{http_code}" https://hub.docker.com');
690
+ if (dockerHubResult.stdout.trim() === '200') {
691
+ dockerHubSpinner.succeed('Docker Hub: Accessible');
692
+ results.push({ name: 'Docker Hub', status: 'pass', message: 'Accessible' });
693
+ }
694
+ else {
695
+ dockerHubSpinner.fail('Docker Hub: Not accessible');
696
+ results.push({ name: 'Docker Hub', status: 'fail', message: 'Not accessible' });
697
+ }
698
+ // Check CDN
699
+ const cdnSpinner = spinner('Checking CDN...').start();
700
+ const cdnResult = await runSSH(host, 'curl -s -o /dev/null -w "%{http_code}" https://cdn.cicore.com.tr');
701
+ if (cdnResult.stdout.trim() === '200') {
702
+ cdnSpinner.succeed('CDN: Accessible');
703
+ results.push({ name: 'CDN', status: 'pass', message: 'Accessible' });
704
+ }
705
+ else {
706
+ cdnSpinner.warn('CDN: Not accessible');
707
+ results.push({ name: 'CDN', status: 'warn', message: 'Not accessible' });
708
+ }
709
+ log.blank();
710
+ printCheckSummary(results);
711
+ }
712
+ // ============================================
713
+ // HELPER FUNCTIONS
714
+ // ============================================
715
+ async function runSSH(host, command) {
716
+ const args = buildSSHCommand(host, command);
717
+ return await exec('ssh', args, { silent: true });
718
+ }
719
+ function printCheckSummary(results) {
720
+ const passed = results.filter(r => r.status === 'pass').length;
721
+ const warned = results.filter(r => r.status === 'warn').length;
722
+ const failed = results.filter(r => r.status === 'fail').length;
723
+ log.blank();
724
+ log.info('=== Summary ===');
725
+ if (failed > 0) {
726
+ log.error(`❌ ${failed} failed`);
727
+ }
728
+ if (warned > 0) {
729
+ log.warn(`⚠️ ${warned} warnings`);
730
+ }
731
+ if (passed > 0) {
732
+ log.success(`✅ ${passed} passed`);
733
+ }
734
+ log.blank();
735
+ if (failed > 0) {
736
+ log.info('Failed checks:');
737
+ results.filter(r => r.status === 'fail').forEach(r => {
738
+ log.item(`${r.name}: ${r.message}`);
739
+ });
740
+ }
741
+ if (warned > 0) {
742
+ log.info('Warnings:');
743
+ results.filter(r => r.status === 'warn').forEach(r => {
744
+ log.item(`${r.name}: ${r.message}`);
745
+ });
746
+ }
747
+ }
748
+ // ============================================
749
+ // SERVER REQUIREMENTS CHECK
750
+ // ============================================
751
+ async function checkRequirements(options) {
752
+ log.title('📋 Server Requirements Check');
753
+ log.blank();
754
+ const host = await getSSHHost(options.host);
755
+ if (!host) {
756
+ log.error(`SSH host "${options.host}" not found`);
757
+ return;
758
+ }
759
+ log.kv('Server', `${host.name} (${host.hostname})`);
760
+ log.blank();
761
+ const results = [];
762
+ // Required programs with minimum versions
763
+ const programs = [
764
+ { name: 'docker', cmd: 'docker --version', minVersion: '20.0.0', required: true },
765
+ { name: 'docker-compose', cmd: 'docker compose version 2>/dev/null || docker-compose --version', minVersion: '2.0.0', required: true },
766
+ { name: 'curl', cmd: 'curl --version | head -1', minVersion: null, required: true },
767
+ { name: 'tree', cmd: 'tree --version 2>/dev/null || echo "not installed"', minVersion: null, required: false },
768
+ { name: 'git', cmd: 'git --version', minVersion: null, required: false },
769
+ { name: 'openssl', cmd: 'openssl version', minVersion: null, required: true },
770
+ { name: 'certbot', cmd: 'certbot --version 2>/dev/null || echo "not installed"', minVersion: null, required: false },
771
+ ];
772
+ log.info('=== Required Programs ===');
773
+ for (const prog of programs) {
774
+ const progSpinner = spinner(`Checking ${prog.name}...`).start();
775
+ const result = await runSSH(host, prog.cmd);
776
+ if (result.success && !result.stdout.includes('not installed') && !result.stdout.includes('not found')) {
777
+ const version = result.stdout.trim().split('\n')[0];
778
+ progSpinner.succeed(`${prog.name}: ${version}`);
779
+ results.push({ name: prog.name, status: 'pass', message: version });
780
+ }
781
+ else {
782
+ if (prog.required) {
783
+ progSpinner.fail(`${prog.name}: NOT INSTALLED (required)`);
784
+ results.push({ name: prog.name, status: 'fail', message: 'Not installed (required)' });
785
+ }
786
+ else {
787
+ progSpinner.warn(`${prog.name}: not installed (optional)`);
788
+ results.push({ name: prog.name, status: 'warn', message: 'Not installed (optional)' });
789
+ }
790
+ }
791
+ }
792
+ log.blank();
793
+ log.info('=== System Resources ===');
794
+ // Check CPU
795
+ const cpuSpinner = spinner('Checking CPU...').start();
796
+ const cpuResult = await runSSH(host, 'nproc');
797
+ if (cpuResult.success) {
798
+ const cores = parseInt(cpuResult.stdout.trim());
799
+ if (cores >= 2) {
800
+ cpuSpinner.succeed(`CPU: ${cores} cores`);
801
+ results.push({ name: 'CPU', status: 'pass', message: `${cores} cores` });
802
+ }
803
+ else {
804
+ cpuSpinner.warn(`CPU: ${cores} core (recommend 2+)`);
805
+ results.push({ name: 'CPU', status: 'warn', message: `${cores} core - recommend 2+` });
806
+ }
807
+ }
808
+ // Check RAM
809
+ const ramSpinner = spinner('Checking RAM...').start();
810
+ const ramResult = await runSSH(host, "free -g | grep Mem | awk '{print $2}'");
811
+ if (ramResult.success) {
812
+ const gb = parseInt(ramResult.stdout.trim());
813
+ if (gb >= 4) {
814
+ ramSpinner.succeed(`RAM: ${gb}GB`);
815
+ results.push({ name: 'RAM', status: 'pass', message: `${gb}GB` });
816
+ }
817
+ else if (gb >= 2) {
818
+ ramSpinner.warn(`RAM: ${gb}GB (recommend 4GB+)`);
819
+ results.push({ name: 'RAM', status: 'warn', message: `${gb}GB - recommend 4GB+` });
820
+ }
821
+ else {
822
+ ramSpinner.fail(`RAM: ${gb}GB (minimum 2GB required)`);
823
+ results.push({ name: 'RAM', status: 'fail', message: `${gb}GB - minimum 2GB required` });
824
+ }
825
+ }
826
+ // Check Disk
827
+ const diskSpinner = spinner('Checking disk space...').start();
828
+ const diskResult = await runSSH(host, "df -BG / | tail -1 | awk '{print $4}' | tr -d 'G'");
829
+ if (diskResult.success) {
830
+ const freeGB = parseInt(diskResult.stdout.trim());
831
+ if (freeGB >= 20) {
832
+ diskSpinner.succeed(`Disk: ${freeGB}GB free`);
833
+ results.push({ name: 'Disk', status: 'pass', message: `${freeGB}GB free` });
834
+ }
835
+ else if (freeGB >= 10) {
836
+ diskSpinner.warn(`Disk: ${freeGB}GB free (recommend 20GB+)`);
837
+ results.push({ name: 'Disk', status: 'warn', message: `${freeGB}GB free - recommend 20GB+` });
838
+ }
839
+ else {
840
+ diskSpinner.fail(`Disk: ${freeGB}GB free (minimum 10GB required)`);
841
+ results.push({ name: 'Disk', status: 'fail', message: `${freeGB}GB free - minimum 10GB required` });
842
+ }
843
+ }
844
+ // Check OS
845
+ const osSpinner = spinner('Checking OS...').start();
846
+ const osResult = await runSSH(host, 'cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d \'"\'');
847
+ if (osResult.success) {
848
+ osSpinner.succeed(`OS: ${osResult.stdout.trim()}`);
849
+ results.push({ name: 'OS', status: 'pass', message: osResult.stdout.trim() });
850
+ }
851
+ log.blank();
852
+ log.info('=== Docker Status ===');
853
+ // Check Docker daemon
854
+ const dockerDaemonSpinner = spinner('Checking Docker daemon...').start();
855
+ const dockerDaemonResult = await runSSH(host, 'docker info >/dev/null 2>&1 && echo "running" || echo "not running"');
856
+ if (dockerDaemonResult.stdout.trim() === 'running') {
857
+ dockerDaemonSpinner.succeed('Docker daemon: Running');
858
+ results.push({ name: 'Docker Daemon', status: 'pass', message: 'Running' });
859
+ }
860
+ else {
861
+ dockerDaemonSpinner.fail('Docker daemon: Not running');
862
+ results.push({ name: 'Docker Daemon', status: 'fail', message: 'Not running' });
863
+ }
864
+ // Check Docker networks
865
+ const networkSpinner = spinner('Checking Docker networks...').start();
866
+ const networkResult = await runSSH(host, 'docker network ls --format "{{.Name}}" | grep -E "vucore" || echo "none"');
867
+ if (networkResult.stdout.trim() !== 'none') {
868
+ const networks = networkResult.stdout.trim().split('\n');
869
+ networkSpinner.succeed(`Docker networks: ${networks.length} ciCore networks`);
870
+ results.push({ name: 'Docker Networks', status: 'pass', message: networks.join(', ') });
871
+ }
872
+ else {
873
+ networkSpinner.warn('Docker networks: No ciCore networks found');
874
+ results.push({ name: 'Docker Networks', status: 'warn', message: 'None found - will be created during install' });
875
+ }
876
+ log.blank();
877
+ printCheckSummary(results);
878
+ // Print installation commands if needed
879
+ const failed = results.filter(r => r.status === 'fail');
880
+ if (failed.length > 0) {
881
+ log.blank();
882
+ log.info('=== Installation Commands ===');
883
+ if (failed.some(r => r.name === 'docker')) {
884
+ log.item('Docker: curl -fsSL https://get.docker.com | sh');
885
+ }
886
+ if (failed.some(r => r.name === 'tree')) {
887
+ log.item('Tree: apt-get install -y tree');
888
+ }
889
+ if (failed.some(r => r.name === 'certbot')) {
890
+ log.item('Certbot: apt-get install -y certbot');
891
+ }
892
+ }
893
+ }
894
+ // ============================================
895
+ // SHOW TREE
896
+ // ============================================
897
+ async function showTree(options) {
898
+ log.title('🌳 Server Directory Structure');
899
+ log.blank();
900
+ const host = await getSSHHost(options.host);
901
+ if (!host) {
902
+ log.error(`SSH host "${options.host}" not found`);
903
+ return;
904
+ }
905
+ const depth = options.depth || '3';
906
+ // Check if tree is installed
907
+ const treeCheck = await runSSH(host, 'which tree 2>/dev/null || echo "not found"');
908
+ const hasTree = !treeCheck.stdout.includes('not found');
909
+ if (options.core) {
910
+ // Show specific core structure
911
+ log.info(`Core: ${options.core}`);
912
+ log.blank();
913
+ const corePath = `/home/cores/${options.core}`;
914
+ if (hasTree) {
915
+ const result = await runSSH(host, `tree -L ${depth} --dirsfirst ${corePath} 2>/dev/null || echo "Directory not found"`);
916
+ console.log(result.stdout);
917
+ }
918
+ else {
919
+ const result = await runSSH(host, `find ${corePath} -maxdepth ${depth} -type d 2>/dev/null | head -50`);
920
+ console.log(result.stdout);
921
+ }
922
+ }
923
+ else {
924
+ // Show full /home structure
925
+ log.info('Full /home structure:');
926
+ log.blank();
927
+ if (hasTree) {
928
+ const result = await runSSH(host, `tree -L ${depth} --dirsfirst /home 2>/dev/null || echo "Directory not found"`);
929
+ console.log(result.stdout);
930
+ }
931
+ else {
932
+ log.warn('tree command not installed. Using find instead...');
933
+ log.info('Install tree: apt-get install -y tree');
934
+ log.blank();
935
+ const result = await runSSH(host, `find /home -maxdepth ${depth} -type d 2>/dev/null | head -100`);
936
+ console.log(result.stdout);
937
+ }
938
+ }
939
+ // Show expected structure
940
+ log.blank();
941
+ log.info('=== Expected ciCore Structure ===');
942
+ console.log(`
943
+ /home/
944
+ ├── cores/
945
+ │ ├── core1/
946
+ │ │ ├── .env
947
+ │ │ ├── addons/
948
+ │ │ │ └── AddonName/
949
+ │ │ │ ├── Backend/
950
+ │ │ │ └── ui/
951
+ │ │ ├── shared/
952
+ │ │ └── storage/
953
+ │ └── core2/
954
+ │ └── ...
955
+ ├── services/
956
+ │ ├── docker-core-nuxt.yml
957
+ │ ├── docker-core-php.yml
958
+ │ ├── docker-postgresql.yml
959
+ │ ├── docker-redis.yml
960
+ │ ├── docker-server-nginx.yml
961
+ │ ├── .env
962
+ │ ├── nginx/
963
+ │ │ ├── conf.d/
964
+ │ │ │ └── vucore-global-router.conf
965
+ │ │ └── ssl/
966
+ │ │ ├── domain.crt
967
+ │ │ └── domain.key
968
+ │ ├── postgres/
969
+ │ │ ├── backups/
970
+ │ │ ├── data/
971
+ │ │ └── init/
972
+ │ ├── redis/
973
+ │ │ └── data/
974
+ │ └── scripts/
975
+ │ ├── clear-all-cache.sh
976
+ │ └── addons/
977
+ │ └── build-addon-esm-master.sh
978
+ `);
979
+ }
980
+ // ============================================
981
+ // CHECK API ENDPOINTS
982
+ // ============================================
983
+ async function checkAPI(options) {
984
+ log.title('🔌 API Endpoint Check');
985
+ log.blank();
986
+ const host = await getSSHHost(options.host);
987
+ if (!host) {
988
+ log.error(`SSH host "${options.host}" not found`);
989
+ return;
990
+ }
991
+ const results = [];
992
+ const domain = options.domain;
993
+ // Internal endpoints (via localhost)
994
+ log.info('=== Internal Endpoints ===');
995
+ const internalEndpoints = [
996
+ { name: 'Nuxt Health', url: 'http://localhost:3000', expected: 200 },
997
+ { name: 'PHP Health', url: 'http://localhost:9000/health', expected: null }, // FPM doesn't respond to HTTP
998
+ { name: 'Nginx', url: 'http://localhost:80', expected: [200, 301, 302] },
999
+ ];
1000
+ for (const endpoint of internalEndpoints) {
1001
+ const epSpinner = spinner(`Checking ${endpoint.name}...`).start();
1002
+ const result = await runSSH(host, `curl -s -o /dev/null -w "%{http_code}" ${endpoint.url} 2>/dev/null || echo "000"`);
1003
+ const status = parseInt(result.stdout.trim());
1004
+ if (endpoint.expected === null) {
1005
+ epSpinner.warn(`${endpoint.name}: N/A (FPM)`);
1006
+ results.push({ name: endpoint.name, status: 'warn', message: 'FPM - use docker logs' });
1007
+ }
1008
+ else if (Array.isArray(endpoint.expected) ? endpoint.expected.includes(status) : status === endpoint.expected) {
1009
+ epSpinner.succeed(`${endpoint.name}: HTTP ${status}`);
1010
+ results.push({ name: endpoint.name, status: 'pass', message: `HTTP ${status}` });
1011
+ }
1012
+ else if (status === 0) {
1013
+ epSpinner.fail(`${endpoint.name}: Not responding`);
1014
+ results.push({ name: endpoint.name, status: 'fail', message: 'Not responding' });
1015
+ }
1016
+ else {
1017
+ epSpinner.warn(`${endpoint.name}: HTTP ${status}`);
1018
+ results.push({ name: endpoint.name, status: 'warn', message: `HTTP ${status}` });
1019
+ }
1020
+ }
1021
+ // External endpoints (if domain provided)
1022
+ if (domain) {
1023
+ log.blank();
1024
+ log.info(`=== External Endpoints (${domain}) ===`);
1025
+ const externalEndpoints = [
1026
+ { name: 'Main Site', url: `https://${domain}`, expected: 200 },
1027
+ { name: 'WWW', url: `https://www.${domain}`, expected: [200, 301] },
1028
+ { name: 'API Health', url: `https://api.${domain}/api/health`, expected: 200 },
1029
+ { name: 'API System', url: `https://api.${domain}/api/system/health`, expected: 200 },
1030
+ ];
1031
+ for (const endpoint of externalEndpoints) {
1032
+ const epSpinner = spinner(`Checking ${endpoint.name}...`).start();
1033
+ const result = await runSSH(host, `curl -sk -o /dev/null -w "%{http_code}" ${endpoint.url} 2>/dev/null || echo "000"`);
1034
+ const status = parseInt(result.stdout.trim());
1035
+ if (Array.isArray(endpoint.expected) ? endpoint.expected.includes(status) : status === endpoint.expected) {
1036
+ epSpinner.succeed(`${endpoint.name}: HTTP ${status}`);
1037
+ results.push({ name: `${endpoint.name} (${domain})`, status: 'pass', message: `HTTP ${status}` });
1038
+ }
1039
+ else if (status === 0) {
1040
+ epSpinner.fail(`${endpoint.name}: Not responding`);
1041
+ results.push({ name: `${endpoint.name} (${domain})`, status: 'fail', message: 'Not responding' });
1042
+ }
1043
+ else {
1044
+ epSpinner.warn(`${endpoint.name}: HTTP ${status}`);
1045
+ results.push({ name: `${endpoint.name} (${domain})`, status: 'warn', message: `HTTP ${status}` });
1046
+ }
1047
+ }
1048
+ }
1049
+ log.blank();
1050
+ printCheckSummary(results);
1051
+ }
1052
+ // ============================================
1053
+ // CHECK SERVICES
1054
+ // ============================================
1055
+ async function checkServices(options) {
1056
+ log.title('🐳 ciCore Services Status');
1057
+ log.blank();
1058
+ const host = await getSSHHost(options.host);
1059
+ if (!host) {
1060
+ log.error(`SSH host "${options.host}" not found`);
1061
+ return;
1062
+ }
1063
+ const results = [];
1064
+ // Expected containers
1065
+ const expectedContainers = [
1066
+ { name: 'cicore_global_nginx', port: '80, 443', critical: true },
1067
+ { name: 'cicore_nuxt', port: '3000', critical: true },
1068
+ { name: 'cicore_php', port: '9000', critical: true },
1069
+ { name: 'cicore_global_postgres', port: '5432', critical: true },
1070
+ { name: 'cicore_global_redis', port: '6379', critical: true },
1071
+ ];
1072
+ // Get all container statuses
1073
+ const psResult = await runSSH(host, 'docker ps -a --format "{{.Names}}|{{.Status}}|{{.Ports}}" | grep vucore');
1074
+ const runningContainers = new Map();
1075
+ if (psResult.success && psResult.stdout.trim()) {
1076
+ psResult.stdout.trim().split('\n').forEach(line => {
1077
+ const [name, status, ports] = line.split('|');
1078
+ runningContainers.set(name, { status, ports: ports || '' });
1079
+ });
1080
+ }
1081
+ log.info('=== Container Status ===');
1082
+ formatTable(['Container', 'Status', 'Ports', 'Health'], expectedContainers.map(c => {
1083
+ const container = runningContainers.get(c.name);
1084
+ if (container) {
1085
+ const isUp = container.status.includes('Up');
1086
+ const health = isUp ? '✅' : '⚠️';
1087
+ results.push({
1088
+ name: c.name,
1089
+ status: isUp ? 'pass' : 'warn',
1090
+ message: container.status
1091
+ });
1092
+ return [c.name, container.status.substring(0, 20), container.ports.substring(0, 30), health];
1093
+ }
1094
+ else {
1095
+ results.push({
1096
+ name: c.name,
1097
+ status: c.critical ? 'fail' : 'warn',
1098
+ message: 'Not found'
1099
+ });
1100
+ return [c.name, 'NOT FOUND', '-', '❌'];
1101
+ }
1102
+ }));
1103
+ // Check Docker networks
1104
+ log.blank();
1105
+ log.info('=== Docker Networks ===');
1106
+ const networkResult = await runSSH(host, 'docker network ls --format "{{.Name}}\t{{.Driver}}" | grep vucore');
1107
+ if (networkResult.success && networkResult.stdout.trim()) {
1108
+ console.log(networkResult.stdout);
1109
+ results.push({ name: 'Docker Networks', status: 'pass', message: 'Found' });
1110
+ }
1111
+ else {
1112
+ log.warn('No ciCore networks found');
1113
+ results.push({ name: 'Docker Networks', status: 'warn', message: 'None found' });
1114
+ }
1115
+ // Check Docker volumes
1116
+ log.blank();
1117
+ log.info('=== Docker Volumes ===');
1118
+ const volumeResult = await runSSH(host, 'docker volume ls --format "{{.Name}}" | grep -E "postgres|redis" | head -10');
1119
+ if (volumeResult.success && volumeResult.stdout.trim()) {
1120
+ volumeResult.stdout.trim().split('\n').forEach(v => log.item(v));
1121
+ results.push({ name: 'Docker Volumes', status: 'pass', message: 'Found' });
1122
+ }
1123
+ else {
1124
+ log.warn('No data volumes found');
1125
+ results.push({ name: 'Docker Volumes', status: 'warn', message: 'None found' });
1126
+ }
1127
+ log.blank();
1128
+ printCheckSummary(results);
1129
+ }
1130
+ // ============================================
1131
+ // CHECK PORTS
1132
+ // ============================================
1133
+ async function checkPorts(options) {
1134
+ log.title('🔌 Port Check');
1135
+ log.blank();
1136
+ const host = await getSSHHost(options.host);
1137
+ if (!host) {
1138
+ log.error(`SSH host "${options.host}" not found`);
1139
+ return;
1140
+ }
1141
+ const results = [];
1142
+ const requiredPorts = [
1143
+ { port: 22, name: 'SSH', required: true },
1144
+ { port: 80, name: 'HTTP', required: true },
1145
+ { port: 443, name: 'HTTPS', required: true },
1146
+ { port: 3000, name: 'Nuxt (internal)', required: false },
1147
+ { port: 9000, name: 'PHP-FPM (internal)', required: false },
1148
+ { port: 5432, name: 'PostgreSQL (internal)', required: false },
1149
+ { port: 6379, name: 'Redis (internal)', required: false },
1150
+ ];
1151
+ log.info('=== Listening Ports ===');
1152
+ for (const p of requiredPorts) {
1153
+ const portSpinner = spinner(`Checking port ${p.port} (${p.name})...`).start();
1154
+ const result = await runSSH(host, `ss -tlnp | grep ":${p.port} " | head -1`);
1155
+ if (result.success && result.stdout.trim()) {
1156
+ portSpinner.succeed(`Port ${p.port} (${p.name}): LISTENING`);
1157
+ results.push({ name: `Port ${p.port}`, status: 'pass', message: `${p.name} - Listening` });
1158
+ }
1159
+ else {
1160
+ if (p.required) {
1161
+ portSpinner.fail(`Port ${p.port} (${p.name}): NOT LISTENING`);
1162
+ results.push({ name: `Port ${p.port}`, status: 'fail', message: `${p.name} - Not listening` });
1163
+ }
1164
+ else {
1165
+ portSpinner.warn(`Port ${p.port} (${p.name}): not listening`);
1166
+ results.push({ name: `Port ${p.port}`, status: 'warn', message: `${p.name} - Not listening (internal)` });
1167
+ }
1168
+ }
1169
+ }
1170
+ // Check firewall
1171
+ log.blank();
1172
+ log.info('=== Firewall Status ===');
1173
+ const ufwResult = await runSSH(host, 'ufw status 2>/dev/null || echo "ufw not installed"');
1174
+ if (ufwResult.stdout.includes('not installed')) {
1175
+ log.info('UFW: Not installed');
1176
+ }
1177
+ else {
1178
+ console.log(ufwResult.stdout);
1179
+ }
1180
+ log.blank();
1181
+ printCheckSummary(results);
1182
+ }
1183
+ // ============================================
1184
+ // PRE-INSTALLATION CHECK
1185
+ // ============================================
1186
+ async function preInstallCheck(options) {
1187
+ log.title('🚀 Pre-Installation Check');
1188
+ log.blank();
1189
+ const host = await getSSHHost(options.host);
1190
+ if (!host) {
1191
+ log.error(`SSH host "${options.host}" not found`);
1192
+ return;
1193
+ }
1194
+ log.kv('Server', `${host.name} (${host.hostname})`);
1195
+ log.blank();
1196
+ const results = [];
1197
+ // 1. SSH Connection
1198
+ log.info('=== 1. SSH Connection ===');
1199
+ const sshSpinner = spinner('Testing SSH connection...').start();
1200
+ const sshResult = await runSSH(host, 'echo "OK"');
1201
+ if (sshResult.success) {
1202
+ sshSpinner.succeed('SSH: Connected');
1203
+ results.push({ name: 'SSH Connection', status: 'pass', message: 'Connected' });
1204
+ }
1205
+ else {
1206
+ sshSpinner.fail('SSH: Connection failed');
1207
+ results.push({ name: 'SSH Connection', status: 'fail', message: 'Failed' });
1208
+ printCheckSummary(results);
1209
+ return;
1210
+ }
1211
+ // 2. OS Check
1212
+ log.blank();
1213
+ log.info('=== 2. Operating System ===');
1214
+ const osSpinner = spinner('Checking OS...').start();
1215
+ const osResult = await runSSH(host, 'cat /etc/os-release | grep -E "^(NAME|VERSION)=" | head -2');
1216
+ if (osResult.success) {
1217
+ osSpinner.succeed('OS: ' + osResult.stdout.trim().replace(/\n/g, ' '));
1218
+ results.push({ name: 'Operating System', status: 'pass', message: 'Linux' });
1219
+ }
1220
+ // 3. Docker
1221
+ log.blank();
1222
+ log.info('=== 3. Docker ===');
1223
+ const dockerSpinner = spinner('Checking Docker...').start();
1224
+ const dockerResult = await runSSH(host, 'docker --version 2>/dev/null');
1225
+ if (dockerResult.success) {
1226
+ dockerSpinner.succeed('Docker: ' + dockerResult.stdout.trim());
1227
+ results.push({ name: 'Docker', status: 'pass', message: 'Installed' });
1228
+ // Check Docker daemon
1229
+ const daemonResult = await runSSH(host, 'docker info >/dev/null 2>&1 && echo "running"');
1230
+ if (daemonResult.stdout.includes('running')) {
1231
+ log.item('Docker daemon: Running');
1232
+ results.push({ name: 'Docker Daemon', status: 'pass', message: 'Running' });
1233
+ }
1234
+ else {
1235
+ log.warn('Docker daemon: Not running');
1236
+ results.push({ name: 'Docker Daemon', status: 'fail', message: 'Not running' });
1237
+ }
1238
+ }
1239
+ else {
1240
+ dockerSpinner.fail('Docker: NOT INSTALLED');
1241
+ results.push({ name: 'Docker', status: 'fail', message: 'Not installed' });
1242
+ log.item('Install: curl -fsSL https://get.docker.com | sh');
1243
+ }
1244
+ // 4. Resources
1245
+ log.blank();
1246
+ log.info('=== 4. System Resources ===');
1247
+ // CPU
1248
+ const cpuResult = await runSSH(host, 'nproc');
1249
+ const cores = parseInt(cpuResult.stdout.trim()) || 0;
1250
+ log.item(`CPU: ${cores} cores ${cores >= 2 ? '✅' : '⚠️'}`);
1251
+ results.push({ name: 'CPU', status: cores >= 2 ? 'pass' : 'warn', message: `${cores} cores` });
1252
+ // RAM
1253
+ const ramResult = await runSSH(host, "free -g | grep Mem | awk '{print $2}'");
1254
+ const ram = parseInt(ramResult.stdout.trim()) || 0;
1255
+ log.item(`RAM: ${ram}GB ${ram >= 4 ? '✅' : ram >= 2 ? '⚠️' : '❌'}`);
1256
+ results.push({ name: 'RAM', status: ram >= 4 ? 'pass' : ram >= 2 ? 'warn' : 'fail', message: `${ram}GB` });
1257
+ // Disk
1258
+ const diskResult = await runSSH(host, "df -BG / | tail -1 | awk '{print $4}' | tr -d 'G'");
1259
+ const disk = parseInt(diskResult.stdout.trim()) || 0;
1260
+ log.item(`Disk Free: ${disk}GB ${disk >= 20 ? '✅' : disk >= 10 ? '⚠️' : '❌'}`);
1261
+ results.push({ name: 'Disk', status: disk >= 20 ? 'pass' : disk >= 10 ? 'warn' : 'fail', message: `${disk}GB free` });
1262
+ // 5. Network
1263
+ log.blank();
1264
+ log.info('=== 5. Network ===');
1265
+ // Internet
1266
+ const internetResult = await runSSH(host, 'curl -s -o /dev/null -w "%{http_code}" https://google.com');
1267
+ const hasInternet = internetResult.stdout.trim() === '200' || internetResult.stdout.trim() === '301';
1268
+ log.item(`Internet: ${hasInternet ? '✅ Connected' : '❌ Not connected'}`);
1269
+ results.push({ name: 'Internet', status: hasInternet ? 'pass' : 'fail', message: hasInternet ? 'Connected' : 'Not connected' });
1270
+ // Docker Hub
1271
+ const dockerHubResult = await runSSH(host, 'curl -s -o /dev/null -w "%{http_code}" https://hub.docker.com');
1272
+ const hasDockerHub = dockerHubResult.stdout.trim() === '200';
1273
+ log.item(`Docker Hub: ${hasDockerHub ? '✅ Accessible' : '⚠️ Not accessible'}`);
1274
+ results.push({ name: 'Docker Hub', status: hasDockerHub ? 'pass' : 'warn', message: hasDockerHub ? 'Accessible' : 'Not accessible' });
1275
+ // 6. Ports
1276
+ log.blank();
1277
+ log.info('=== 6. Required Ports ===');
1278
+ const portsToCheck = [80, 443];
1279
+ for (const port of portsToCheck) {
1280
+ const portResult = await runSSH(host, `ss -tlnp | grep ":${port} " | head -1 || echo "free"`);
1281
+ const isFree = portResult.stdout.includes('free') || !portResult.stdout.trim();
1282
+ const isVucore = portResult.stdout.includes('vucore') || portResult.stdout.includes('nginx');
1283
+ if (isFree) {
1284
+ log.item(`Port ${port}: ✅ Available`);
1285
+ results.push({ name: `Port ${port}`, status: 'pass', message: 'Available' });
1286
+ }
1287
+ else if (isVucore) {
1288
+ log.item(`Port ${port}: ✅ Used by VuCore`);
1289
+ results.push({ name: `Port ${port}`, status: 'pass', message: 'VuCore' });
1290
+ }
1291
+ else {
1292
+ log.item(`Port ${port}: ⚠️ In use by another service`);
1293
+ results.push({ name: `Port ${port}`, status: 'warn', message: 'In use' });
1294
+ }
1295
+ }
1296
+ // Summary
1297
+ log.blank();
1298
+ printCheckSummary(results);
1299
+ // Final verdict
1300
+ const failed = results.filter(r => r.status === 'fail').length;
1301
+ const warned = results.filter(r => r.status === 'warn').length;
1302
+ log.blank();
1303
+ if (failed === 0) {
1304
+ log.box('✅ Server Ready for Installation', [
1305
+ 'All critical requirements met.',
1306
+ `Run: vu server install --host ${host.name} --core core1 --domain yourdomain.com`,
1307
+ ]);
1308
+ }
1309
+ else {
1310
+ log.box('❌ Server Not Ready', [
1311
+ `${failed} critical issue(s) must be resolved.`,
1312
+ 'Fix the issues above and run this check again.',
1313
+ ]);
1314
+ }
1315
+ }
1316
+ //# sourceMappingURL=index.js.map