cyberia 3.0.3 → 3.2.5

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 (296) hide show
  1. package/{.env.production → .env.example} +20 -4
  2. package/.github/workflows/engine-cyberia.cd.yml +43 -10
  3. package/.github/workflows/engine-cyberia.ci.yml +48 -26
  4. package/.github/workflows/ghpkg.ci.yml +5 -5
  5. package/.github/workflows/gitlab.ci.yml +1 -1
  6. package/.github/workflows/hardhat.ci.yml +82 -0
  7. package/.github/workflows/npmpkg.ci.yml +60 -14
  8. package/.github/workflows/publish.ci.yml +26 -7
  9. package/.github/workflows/publish.cyberia.ci.yml +5 -5
  10. package/.github/workflows/pwa-microservices-template-page.cd.yml +6 -7
  11. package/.github/workflows/pwa-microservices-template-test.ci.yml +4 -4
  12. package/.github/workflows/release.cd.yml +14 -8
  13. package/.vscode/extensions.json +9 -8
  14. package/.vscode/settings.json +3 -2
  15. package/CHANGELOG.md +643 -1
  16. package/CLI-HELP.md +132 -57
  17. package/Dockerfile +4 -2
  18. package/README.md +347 -22
  19. package/WHITE-PAPER.md +1540 -0
  20. package/bin/build.js +21 -12
  21. package/bin/cyberia.js +2640 -106
  22. package/bin/deploy.js +258 -372
  23. package/bin/file.js +5 -1
  24. package/bin/index.js +2640 -106
  25. package/bin/vs.js +3 -3
  26. package/conf.js +169 -105
  27. package/deployment.yaml +236 -20
  28. package/hardhat/.env.example +31 -0
  29. package/hardhat/README.md +531 -0
  30. package/hardhat/WHITE-PAPER.md +1540 -0
  31. package/hardhat/contracts/ObjectLayerToken.sol +391 -0
  32. package/hardhat/deployments/.gitkeep +0 -0
  33. package/hardhat/deployments/hardhat-ObjectLayerToken.json +11 -0
  34. package/hardhat/hardhat.config.js +136 -0
  35. package/hardhat/ignition/modules/ObjectLayerToken.js +21 -0
  36. package/hardhat/networks/besu-object-layer.network.json +138 -0
  37. package/hardhat/package-lock.json +4323 -0
  38. package/hardhat/package.json +36 -0
  39. package/hardhat/scripts/deployObjectLayerToken.js +98 -0
  40. package/hardhat/test/ObjectLayerToken.js +592 -0
  41. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  42. package/hardhat/types/ethers-contracts/common.ts +92 -0
  43. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  44. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  45. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  46. package/hardhat/types/ethers-contracts/index.ts +6 -0
  47. package/jsdoc.dd-cyberia.json +68 -0
  48. package/jsdoc.json +65 -49
  49. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -4
  50. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -4
  51. package/manifests/deployment/dd-cyberia-development/deployment.yaml +562 -0
  52. package/manifests/deployment/dd-cyberia-development/proxy.yaml +297 -0
  53. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +132 -0
  54. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  55. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  56. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  57. package/manifests/deployment/playwright/deployment.yaml +1 -1
  58. package/manifests/pv-pvc-dd.yaml +1 -1
  59. package/nodemon.json +1 -1
  60. package/package.json +60 -48
  61. package/proxy.yaml +118 -10
  62. package/pv-pvc.yaml +132 -0
  63. package/scripts/k3s-node-setup.sh +1 -1
  64. package/scripts/ports-ls.sh +2 -0
  65. package/scripts/rhel-grpc-setup.sh +56 -0
  66. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +47 -1
  67. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +17 -2
  68. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  69. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +80 -7
  70. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  71. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  72. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  73. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  74. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  75. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  76. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  77. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  78. package/src/api/cyberia-instance/cyberia-fallback-world.js +368 -0
  79. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  80. package/src/api/cyberia-instance/cyberia-instance.model.js +84 -0
  81. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  82. package/src/api/cyberia-instance/cyberia-instance.service.js +191 -0
  83. package/src/api/cyberia-instance/cyberia-portal-connector.js +486 -0
  84. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  85. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +413 -0
  86. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +228 -0
  87. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  88. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +42 -0
  89. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  90. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  91. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  92. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  93. package/src/api/document/document.service.js +1 -1
  94. package/src/api/file/file.controller.js +3 -1
  95. package/src/api/file/file.ref.json +18 -0
  96. package/src/api/file/file.service.js +28 -5
  97. package/src/api/ipfs/ipfs.controller.js +4 -25
  98. package/src/api/ipfs/ipfs.model.js +43 -34
  99. package/src/api/ipfs/ipfs.router.js +8 -13
  100. package/src/api/ipfs/ipfs.service.js +56 -104
  101. package/src/api/object-layer/README.md +347 -22
  102. package/src/api/object-layer/object-layer.controller.js +6 -2
  103. package/src/api/object-layer/object-layer.model.js +12 -8
  104. package/src/api/object-layer/object-layer.router.js +698 -42
  105. package/src/api/object-layer/object-layer.service.js +119 -37
  106. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +1 -2
  107. package/src/api/user/user.router.js +10 -5
  108. package/src/api/user/user.service.js +15 -14
  109. package/src/cli/baremetal.js +6 -10
  110. package/src/cli/cloud-init.js +0 -3
  111. package/src/cli/cluster.js +7 -7
  112. package/src/cli/db.js +723 -857
  113. package/src/cli/deploy.js +215 -105
  114. package/src/cli/env.js +34 -5
  115. package/src/cli/fs.js +5 -4
  116. package/src/cli/image.js +0 -3
  117. package/src/cli/index.js +83 -15
  118. package/src/cli/kubectl.js +211 -0
  119. package/src/cli/monitor.js +5 -6
  120. package/src/cli/release.js +284 -0
  121. package/src/cli/repository.js +708 -62
  122. package/src/cli/run.js +371 -151
  123. package/src/cli/secrets.js +73 -2
  124. package/src/cli/ssh.js +1 -1
  125. package/src/cli/test.js +3 -3
  126. package/src/client/Cryptokoyn.index.js +3 -4
  127. package/src/client/CyberiaPortal.index.js +3 -4
  128. package/src/client/Default.index.js +3 -4
  129. package/src/client/Itemledger.index.js +4 -963
  130. package/src/client/Underpost.index.js +3 -4
  131. package/src/client/components/core/AgGrid.js +20 -5
  132. package/src/client/components/core/Alert.js +2 -2
  133. package/src/client/components/core/AppStore.js +69 -0
  134. package/src/client/components/core/CalendarCore.js +2 -2
  135. package/src/client/components/core/Content.js +22 -3
  136. package/src/client/components/core/Docs.js +30 -6
  137. package/src/client/components/core/DropDown.js +137 -17
  138. package/src/client/components/core/FileExplorer.js +71 -4
  139. package/src/client/components/core/Input.js +1 -1
  140. package/src/client/components/core/Keyboard.js +2 -2
  141. package/src/client/components/core/LogIn.js +2 -2
  142. package/src/client/components/core/LogOut.js +2 -2
  143. package/src/client/components/core/Modal.js +20 -7
  144. package/src/client/components/core/Panel.js +0 -1
  145. package/src/client/components/core/PanelForm.js +19 -19
  146. package/src/client/components/core/RichText.js +1 -2
  147. package/src/client/components/core/SocketIo.js +82 -29
  148. package/src/client/components/core/SocketIoHandler.js +75 -0
  149. package/src/client/components/core/Stream.js +143 -95
  150. package/src/client/components/core/Webhook.js +40 -7
  151. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  152. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +3 -3
  153. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +2 -2
  154. package/src/client/components/cryptokoyn/MenuCryptokoyn.js +3 -3
  155. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  156. package/src/client/components/cyberia/InstanceEngineCyberia.js +700 -0
  157. package/src/client/components/cyberia/MapEngineCyberia.js +1359 -2
  158. package/src/client/components/cyberia/ObjectLayerEngineModal.js +17 -6
  159. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +92 -54
  160. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  161. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +217 -30
  162. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +44 -2
  163. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +3 -4
  164. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +2 -2
  165. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +104 -9
  166. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +5 -0
  167. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  168. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  169. package/src/client/components/default/AppStoreDefault.js +5 -0
  170. package/src/client/components/default/LogInDefault.js +3 -3
  171. package/src/client/components/default/LogOutDefault.js +2 -2
  172. package/src/client/components/default/MenuDefault.js +5 -5
  173. package/src/client/components/default/SocketIoDefault.js +3 -51
  174. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  175. package/src/client/components/itemledger/LogInItemledger.js +3 -3
  176. package/src/client/components/itemledger/LogOutItemledger.js +2 -2
  177. package/src/client/components/itemledger/MenuItemledger.js +3 -3
  178. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  179. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  180. package/src/client/components/underpost/CssUnderpost.js +59 -0
  181. package/src/client/components/underpost/LogInUnderpost.js +6 -3
  182. package/src/client/components/underpost/LogOutUnderpost.js +4 -2
  183. package/src/client/components/underpost/MenuUnderpost.js +104 -18
  184. package/src/client/components/underpost/RoutesUnderpost.js +2 -0
  185. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  186. package/src/client/public/cryptokoyn/assets/logo/base-icon.png +0 -0
  187. package/src/client/public/cryptokoyn/browserconfig.xml +12 -0
  188. package/src/client/public/cryptokoyn/microdata.json +85 -0
  189. package/src/client/public/cryptokoyn/site.webmanifest +57 -0
  190. package/src/client/public/cryptokoyn/sitemap +3 -3
  191. package/src/client/public/default/sitemap +3 -3
  192. package/src/client/public/itemledger/browserconfig.xml +2 -2
  193. package/src/client/public/itemledger/manifest.webmanifest +4 -4
  194. package/src/client/public/itemledger/microdata.json +71 -0
  195. package/src/client/public/itemledger/sitemap +3 -3
  196. package/src/client/public/itemledger/yandex-browser-manifest.json +2 -2
  197. package/src/client/public/test/sitemap +3 -3
  198. package/src/client/services/core/core.service.js +20 -8
  199. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +105 -0
  200. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  201. package/src/client/services/cyberia-entity/cyberia-entity.service.js +105 -0
  202. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  203. package/src/client/services/cyberia-instance/cyberia-instance.service.js +122 -0
  204. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +105 -0
  205. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  206. package/src/client/services/cyberia-map/cyberia-map.service.js +126 -0
  207. package/src/client/services/instance/instance.management.js +2 -2
  208. package/src/client/services/ipfs/ipfs.service.js +3 -23
  209. package/src/client/services/object-layer/object-layer.management.js +3 -3
  210. package/src/client/services/object-layer/object-layer.service.js +21 -0
  211. package/src/client/services/user/user.management.js +2 -2
  212. package/src/client/ssr/body/404.js +15 -11
  213. package/src/client/ssr/body/500.js +15 -11
  214. package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
  215. package/src/client/ssr/head/PwaItemledger.js +60 -0
  216. package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
  217. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  218. package/src/client/ssr/pages/Test.js +11 -10
  219. package/src/client.build.js +0 -3
  220. package/src/client.dev.js +0 -3
  221. package/src/db/DataBaseProvider.js +17 -2
  222. package/src/db/mariadb/MariaDB.js +14 -9
  223. package/src/db/mongo/MongooseDB.js +17 -1
  224. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +305 -0
  225. package/src/grpc/cyberia/README.md +326 -0
  226. package/src/grpc/cyberia/grpc-server.js +530 -0
  227. package/src/index.js +24 -1
  228. package/src/proxy.js +0 -3
  229. package/src/runtime/express/Dockerfile +4 -0
  230. package/src/runtime/express/Express.js +33 -10
  231. package/src/runtime/lampp/Dockerfile +13 -2
  232. package/src/runtime/lampp/Lampp.js +33 -17
  233. package/src/runtime/wp/Dockerfile +68 -0
  234. package/src/runtime/wp/Wp.js +639 -0
  235. package/src/server/auth.js +36 -15
  236. package/src/server/backup.js +39 -12
  237. package/src/server/besu-genesis-generator.js +1630 -0
  238. package/src/server/client-build-docs.js +133 -17
  239. package/src/server/client-build-live.js +9 -18
  240. package/src/server/client-build.js +229 -101
  241. package/src/server/client-dev-server.js +14 -13
  242. package/src/server/client-formatted.js +109 -57
  243. package/src/server/conf.js +391 -164
  244. package/src/server/cron.js +27 -24
  245. package/src/server/dns.js +29 -12
  246. package/src/server/downloader.js +0 -2
  247. package/src/server/ipfs-client.js +24 -1
  248. package/src/server/logger.js +27 -9
  249. package/src/server/object-layer.js +217 -103
  250. package/src/server/peer.js +8 -2
  251. package/src/server/process.js +1 -50
  252. package/src/server/proxy.js +4 -8
  253. package/src/server/runtime.js +30 -9
  254. package/src/server/semantic-layer-generator-floor.js +359 -0
  255. package/src/server/semantic-layer-generator-skin.js +1294 -0
  256. package/src/server/semantic-layer-generator.js +116 -555
  257. package/src/server/ssr.js +0 -3
  258. package/src/server/start.js +19 -12
  259. package/src/server/tls.js +0 -2
  260. package/src/server.js +0 -4
  261. package/src/ws/IoInterface.js +1 -10
  262. package/src/ws/IoServer.js +14 -33
  263. package/src/ws/core/channels/core.ws.chat.js +65 -20
  264. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  265. package/src/ws/core/channels/core.ws.stream.js +90 -31
  266. package/src/ws/core/core.ws.connection.js +12 -33
  267. package/src/ws/core/core.ws.emit.js +10 -26
  268. package/src/ws/core/core.ws.server.js +25 -58
  269. package/src/ws/default/channels/default.ws.main.js +53 -12
  270. package/src/ws/default/default.ws.connection.js +26 -13
  271. package/src/ws/default/default.ws.server.js +30 -12
  272. package/.env.development +0 -43
  273. package/.env.test +0 -43
  274. package/hardhat/contracts/CryptoKoyn.sol +0 -59
  275. package/hardhat/contracts/ItemLedger.sol +0 -73
  276. package/hardhat/contracts/Lock.sol +0 -34
  277. package/hardhat/hardhat.config.cjs +0 -45
  278. package/hardhat/ignition/modules/Lock.js +0 -18
  279. package/hardhat/networks/cryptokoyn-itemledger.network.json +0 -29
  280. package/hardhat/scripts/deployCryptokoyn.cjs +0 -25
  281. package/hardhat/scripts/deployItemledger.cjs +0 -25
  282. package/hardhat/test/Lock.js +0 -126
  283. package/hardhat/white-paper.md +0 -581
  284. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  285. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  286. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  287. package/src/client/components/default/ElementsDefault.js +0 -38
  288. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  289. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  290. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  291. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  292. package/src/ws/core/management/core.ws.chat.js +0 -8
  293. package/src/ws/core/management/core.ws.mailer.js +0 -16
  294. package/src/ws/core/management/core.ws.stream.js +0 -8
  295. package/src/ws/default/management/default.ws.main.js +0 -8
  296. package/white-paper.md +0 -581
package/src/cli/run.js CHANGED
@@ -4,24 +4,45 @@
4
4
  * @namespace UnderpostRun
5
5
  */
6
6
 
7
- import { daemonProcess, getTerminalPid, openTerminal, shellCd, shellExec } from '../server/process.js';
7
+ import { daemonProcess, getTerminalPid, pbcopy, shellCd, shellExec } from '../server/process.js';
8
+ import crypto from 'crypto';
8
9
  import {
9
10
  awaitDeployMonitor,
10
11
  buildKindPorts,
11
12
  Config,
12
13
  getNpmRootPath,
13
14
  isDeployRunnerContext,
15
+ loadConfServerJson,
14
16
  writeEnv,
15
17
  } from '../server/conf.js';
16
18
  import { actionInitLog, loggerFactory } from '../server/logger.js';
17
19
 
18
20
  import fs from 'fs-extra';
21
+ import net from 'net';
19
22
  import { range, setPad, timer } from '../client/components/core/CommonJs.js';
20
23
 
21
24
  import os from 'os';
22
25
  import Underpost from '../index.js';
23
26
  import dotenv from 'dotenv';
24
27
 
28
+ const waitForPort = (port, host = '127.0.0.1', { maxAttempts = 30, interval = 2000 } = {}) =>
29
+ new Promise((resolve, reject) => {
30
+ let attempts = 0;
31
+ const tryConnect = () => {
32
+ attempts++;
33
+ const socket = net.createConnection({ port, host }, () => {
34
+ socket.destroy();
35
+ resolve();
36
+ });
37
+ socket.on('error', () => {
38
+ socket.destroy();
39
+ if (attempts >= maxAttempts) return reject(new Error(`Port ${port} not ready after ${maxAttempts} attempts`));
40
+ setTimeout(tryConnect, interval);
41
+ });
42
+ };
43
+ tryConnect();
44
+ });
45
+
25
46
  const logger = loggerFactory(import.meta);
26
47
 
27
48
  /**
@@ -55,7 +76,6 @@ const logger = loggerFactory(import.meta);
55
76
  * @property {string} apiVersion - The API version for the container.
56
77
  * @property {string} claimName - The claim name for the volume.
57
78
  * @property {string} kindType - The kind of resource to create.
58
- * @property {boolean} terminal - Whether to open a terminal.
59
79
  * @property {number} devProxyPortOffset - The port offset for the development proxy.
60
80
  * @property {boolean} hostNetwork - Whether to use host networking.
61
81
  * @property {string} requestsMemory - The memory request for the container.
@@ -87,6 +107,7 @@ const logger = loggerFactory(import.meta);
87
107
  * @property {boolean} logs - Whether to enable logs.
88
108
  * @property {boolean} dryRun - Whether to perform a dry run.
89
109
  * @property {boolean} createJobNow - Whether to create the job immediately.
110
+ * @property {number} fromNCommit - Number of commits back to use for message propagation (default: 1, last commit only).
90
111
  * @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
91
112
  * As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
92
113
  * As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
@@ -120,7 +141,6 @@ const DEFAULT_OPTION = {
120
141
  apiVersion: '',
121
142
  claimName: '',
122
143
  kindType: '',
123
- terminal: false,
124
144
  devProxyPortOffset: 0,
125
145
  hostNetwork: false,
126
146
  requestsMemory: '',
@@ -152,7 +172,10 @@ const DEFAULT_OPTION = {
152
172
  logs: false,
153
173
  dryRun: false,
154
174
  createJobNow: false,
175
+ fromNCommit: 0,
155
176
  hostAliases: '',
177
+ gitClean: false,
178
+ copy: false,
156
179
  };
157
180
 
158
181
  /**
@@ -195,7 +218,7 @@ class UnderpostRun {
195
218
  }
196
219
 
197
220
  {
198
- // Detect MongoDB primary pod using centralized method
221
+ // Detect MongoDB primary pod using method
199
222
  let primaryMongoHost = 'mongodb-0.mongodb-service';
200
223
  try {
201
224
  const primaryPodName = Underpost.db.getMongoPrimaryPodName({
@@ -245,8 +268,15 @@ class UnderpostRun {
245
268
  const ports = '6379,27017';
246
269
  shellExec(`node bin run kill '${ports}'`);
247
270
  shellExec(`node bin run dev-cluster --dev --expose --namespace ${options.namespace}`, { async: true });
248
- console.log('Loading fordward services...');
249
- await timer(5000);
271
+ logger.info('Waiting for port-forward services to be ready...');
272
+ try {
273
+ await Promise.all([waitForPort(27017), waitForPort(6379)]);
274
+ logger.info('Port-forward services are ready');
275
+ } catch (err) {
276
+ logger.error('Port-forward services failed to become ready', { error: err.message });
277
+ shellExec(`node bin run kill '${ports}'`);
278
+ throw err;
279
+ }
250
280
  shellExec(`node bin metadata --generate ${path}`);
251
281
  shellExec(`node bin db --dev --clean-fs-collection dd`);
252
282
  shellExec(`node bin run kill '${ports}'`);
@@ -355,58 +385,140 @@ class UnderpostRun {
355
385
  },
356
386
  /**
357
387
  * @method template-deploy
358
- * @description Cleans up, pushes `engine-private` and `engine` repositories with a commit tag `ci package-pwa-microservices-template`.
359
- * @param {string} path - The input value, identifier, or path for the operation.
388
+ * @description Pushes `engine-private`, dispatches CI workflow to build `pwa-microservices-template`,
389
+ * and optionally triggers engine-<conf-id> CI with sync/init which in turn dispatches the CD workflow
390
+ * after the build chain completes (template → ghpkg → engine-<conf-id> → CD).
391
+ * @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
360
392
  * @param {Object} options - The default underpost runner options for customizing workflow
361
393
  * @memberof UnderpostRun
362
394
  */
363
395
  'template-deploy': (path = '', options = DEFAULT_OPTION) => {
364
396
  const baseCommand = options.dev ? 'node bin' : 'underpost';
365
- const message = shellExec(`node bin cmt --changelog --changelog-no-hash`, { silent: true, stdout: true }).trim();
366
- shellExec(`${baseCommand} run clean`);
397
+ shellExec(`npm run security:secrets`);
398
+ const reportPath = './gitleaks-report.json';
399
+ if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
400
+ logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
401
+ return;
402
+ }
403
+ shellExec(`${baseCommand} run pull`);
404
+
405
+ // Capture last N commit messages for propagation.
406
+ // When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
407
+ const fromN =
408
+ options.fromNCommit && parseInt(options.fromNCommit) > 0
409
+ ? parseInt(options.fromNCommit)
410
+ : Underpost.repo.getUnpushedCount('.').count;
411
+ const message = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
412
+ silent: true,
413
+ stdout: true,
414
+ }).trim();
415
+
367
416
  shellExec(
368
417
  `${baseCommand} push ./engine-private ${options.force ? '-f ' : ''}${
369
418
  process.env.GITHUB_USERNAME
370
419
  }/engine-private`,
371
420
  );
372
421
  shellCd('/home/dd/engine');
422
+
423
+ const sanitizedMessage = Underpost.repo.sanitizeChangelogMessage(message);
424
+
425
+ // Push engine repo so workflow YAML changes reach GitHub
373
426
  shellExec(`git reset`);
374
- function replaceNthNewline(str, n, replacement = ' ') {
375
- let count = 0;
376
- return str.replace(/\r\n?|\n/g, (match) => {
377
- count++;
378
- return count === n ? replacement : match;
379
- });
380
- }
381
- shellExec(
382
- `${baseCommand} cmt . --empty ci package-pwa-microservices-template${
383
- path.startsWith('sync') ? `-${path}` : ''
384
- }${
385
- message
386
- ? ` "${replaceNthNewline(
387
- message.replaceAll('"', '').replaceAll('`', '').replaceAll('#', '').replaceAll('- ', ''),
388
- 2,
389
- )}"`
390
- : ''
391
- }`,
392
- );
393
427
  shellExec(`${baseCommand} push . ${options.force ? '-f ' : ''}${process.env.GITHUB_USERNAME}/engine`);
428
+
429
+ // Determine deploy conf and type from path (sync-engine-core, init-engine-core, etc.)
430
+ let deployConfId = '';
431
+ let deployType = '';
432
+ if (path.startsWith('sync-')) {
433
+ deployConfId = path.replace(/^sync-/, '');
434
+ deployType = 'sync-and-deploy';
435
+ } else if (path.startsWith('init-')) {
436
+ deployConfId = path.replace(/^init-/, '');
437
+ deployType = 'init';
438
+ }
439
+
440
+ // Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
441
+ // If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
442
+ // with sync=true after template build completes. The engine CI then dispatches
443
+ // the CD workflow after the engine repo build finishes — ensuring correct sequence:
444
+ // npmpkg.ci → engine-<id>.ci → engine-<id>.cd
445
+ const repo = `${process.env.GITHUB_USERNAME}/engine`;
446
+ const inputs = {};
447
+ if (sanitizedMessage) inputs.message = sanitizedMessage;
448
+ if (deployConfId) inputs.deploy_conf_id = deployConfId;
449
+ if (deployType) inputs.deploy_type = deployType;
450
+
451
+ Underpost.repo.dispatchWorkflow({
452
+ repo,
453
+ workflowFile: 'npmpkg.ci.yml',
454
+ ref: 'master',
455
+ inputs,
456
+ });
394
457
  },
395
458
 
459
+ /**
460
+ * @method template-deploy-local
461
+ * @description Similar to `template-deploy` but runs the workflow locally without dispatching GitHub Actions. It pulls the latest changes, pushes to GitHub, builds the template, and optionally triggers a local release with CI push.
462
+ * @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
463
+ * @param {Object} options - The default underpost runner options for customizing workflow
464
+ * @memberof UnderpostRun
465
+ */
466
+ 'template-deploy-local': async (path, options = DEFAULT_OPTION) => {
467
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
468
+ shellExec(`npm run security:secrets`);
469
+ const reportPath = './gitleaks-report.json';
470
+ if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
471
+ logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
472
+ return;
473
+ }
474
+ shellExec(`${baseCommand} run pull`);
475
+
476
+ // Capture last N commit messages from the engine repo.
477
+ // When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
478
+ const fromN =
479
+ options.fromNCommit && parseInt(options.fromNCommit) > 0
480
+ ? parseInt(options.fromNCommit)
481
+ : Underpost.repo.getUnpushedCount('.').count;
482
+ const rawMessage = shellExec(`node bin cmt --changelog ${fromN} --changelog-no-hash`, {
483
+ silent: true,
484
+ stdout: true,
485
+ }).trim();
486
+ const sanitizedMessage = Underpost.repo.sanitizeChangelogMessage(rawMessage);
487
+
488
+ const { triggerCmd } = path
489
+ ? await Underpost.release.ci(path, sanitizedMessage, options)
490
+ : await Underpost.release.pwa(sanitizedMessage, options);
491
+ pbcopy(triggerCmd + ' && cd /home/dd/engine');
492
+ },
396
493
  /**
397
494
  * @method template-deploy-image
398
- * @description Commits and pushes a Docker image deployment for the `engine` repository.
495
+ * @description Dispatches the Docker image CI workflow for the `engine` repository.
399
496
  * @param {string} path - The input value, identifier, or path for the operation.
400
497
  * @param {Object} options - The default underpost runner options for customizing workflow
401
498
  * @memberof UnderpostRun
402
499
  */
403
500
  'template-deploy-image': (path, options = DEFAULT_OPTION) => {
404
- // const baseCommand = options.dev ? 'node bin' : 'underpost';
405
- shellExec(
406
- `cd /home/dd/engine && git reset && underpost cmt . --empty ci docker-image 'underpost-engine:${
407
- Underpost.version
408
- }' && underpost push . ${options.force ? '-f ' : ''}${process.env.GITHUB_USERNAME}/engine`,
409
- );
501
+ Underpost.repo.dispatchWorkflow({
502
+ repo: `${process.env.GITHUB_USERNAME}/engine`,
503
+ workflowFile: 'docker-image.ci.yml',
504
+ ref: 'master',
505
+ inputs: {},
506
+ });
507
+ },
508
+ /**
509
+ * @method docker-image
510
+ * @description Dispatches the Docker image CI workflow (`docker-image.ci.yml`) for the `engine` repository via `workflow_dispatch`.
511
+ * @param {string} path - The input value, identifier, or path for the operation.
512
+ * @param {Object} options - The default underpost runner options for customizing workflow
513
+ * @memberof UnderpostRun
514
+ */
515
+ 'docker-image': (path, options = DEFAULT_OPTION) => {
516
+ Underpost.repo.dispatchWorkflow({
517
+ repo: `${process.env.GITHUB_USERNAME}/engine`,
518
+ workflowFile: 'docker-image.ci.yml',
519
+ ref: 'master',
520
+ inputs: {},
521
+ });
410
522
  },
411
523
  /**
412
524
  * @method clean
@@ -467,18 +579,30 @@ class UnderpostRun {
467
579
  },
468
580
  /**
469
581
  * @method ssh-deploy
470
- * @description Performs a Git reset, commits with a message `cd ssh-${path}`, and pushes the `engine` repository, likely triggering an SSH-based CD pipeline.
471
- * @param {string} path - The input value, identifier, or path for the operation (used as the deployment identifier for the commit message).
582
+ * @description Dispatches the corresponding CD workflow for SSH-based deployment, replacing empty commits with workflow_dispatch.
583
+ * @param {string} path - The deployment identifier (e.g., 'engine-core', 'sync-engine-core', 'init-engine-core').
472
584
  * @param {Object} options - The default underpost runner options for customizing workflow
473
585
  * @memberof UnderpostRun
474
586
  */
475
587
  'ssh-deploy': (path, options = DEFAULT_OPTION) => {
476
588
  actionInitLog();
477
- const baseCommand = options.dev ? 'node bin' : 'underpost';
478
- shellCd('/home/dd/engine');
479
- shellExec(`git reset`);
480
- shellExec(`${baseCommand} cmt . --empty cd ssh-${path}`);
481
- shellExec(`${baseCommand} push . ${options.force ? '-f ' : ''}${process.env.GITHUB_USERNAME}/engine`);
589
+
590
+ let job = 'deploy';
591
+ let confId = path;
592
+ if (path.startsWith('sync-')) {
593
+ job = 'sync-and-deploy';
594
+ confId = path.replace(/^sync-/, '');
595
+ } else if (path.startsWith('init-')) {
596
+ job = 'init';
597
+ confId = path.replace(/^init-/, '');
598
+ }
599
+
600
+ Underpost.repo.dispatchWorkflow({
601
+ repo: `${process.env.GITHUB_USERNAME}/engine`,
602
+ workflowFile: `${confId}.cd.yml`,
603
+ ref: 'master',
604
+ inputs: { job },
605
+ });
482
606
  },
483
607
  /**
484
608
  * @method ide
@@ -488,15 +612,25 @@ class UnderpostRun {
488
612
  * @param {Object} options - The default underpost runner options for customizing workflow
489
613
  * @memberof UnderpostRun
490
614
  */
491
- ide: (path, options = DEFAULT_OPTION) => {
492
- const { underpostRoot } = options;
493
- if (path === 'install') {
494
- shellExec(`sudo curl -f https://zed.dev/install.sh | sh`);
495
- shellExec(
496
- `sudo dnf config-manager --add-repo https://download.sublimetext.com/rpm/stable/x86_64/sublime-text.repo`,
497
- );
498
- shellExec(`sudo dnf install -y sublime-text`);
499
- } else shellExec(`node ${underpostRoot}/bin/zed ${path}`);
615
+ ide: (path = '', options = DEFAULT_OPTION) => {
616
+ const underpostRoot = options.dev ? '.' : options.underpostRoot;
617
+ const [projectPath, customIde] = path.split(',');
618
+ if (projectPath === 'install') {
619
+ if (customIde === 'zed') shellExec(`sudo curl -f https://zed.dev/install.sh | sh`);
620
+ else if (customIde === 'subl') {
621
+ shellExec(
622
+ `sudo dnf config-manager --add-repo https://download.sublimetext.com/rpm/stable/x86_64/sublime-text.repo`,
623
+ );
624
+ shellExec(`sudo dnf install -y sublime-text`);
625
+ } else {
626
+ shellExec(`sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc &&
627
+ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\nautorefresh=1\ntype=rpm-md\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" | sudo tee /etc/yum.repos.d/vscode.repo > /dev/null`);
628
+ shellExec(`sudo dnf install -y code`);
629
+ }
630
+ return;
631
+ }
632
+ if (customIde === 'zed') shellExec(`node ${underpostRoot}/bin/zed ${projectPath}`);
633
+ else shellExec(`node ${underpostRoot}/bin/vs ${projectPath}`);
500
634
  },
501
635
  /**
502
636
  * @method crypto-policy
@@ -520,12 +654,13 @@ class UnderpostRun {
520
654
  const env = options.dev ? 'development' : 'production';
521
655
  const baseCommand = options.dev ? 'node bin' : 'underpost';
522
656
  const baseClusterCommand = options.dev ? ' --dev' : '';
657
+ const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
523
658
  const defaultPath = [
524
659
  'dd-default',
525
660
  options.replicas,
526
661
  ``,
527
662
  ``,
528
- options.dev || !isDeployRunnerContext(path, options) ? 'kind-control-plane' : os.hostname(),
663
+ !options.kubeadm && !options.k3s ? 'kind-control-plane' : os.hostname(),
529
664
  ];
530
665
  let [deployId, replicas, versions, image, node] = path ? path.split(',') : defaultPath;
531
666
  deployId = deployId ? deployId : defaultPath[0];
@@ -540,7 +675,8 @@ class UnderpostRun {
540
675
  if (!validVersion) throw new Error('Version mismatch');
541
676
  }
542
677
  if (options.timezone !== 'none') shellExec(`${baseCommand} run${baseClusterCommand} tz`);
543
- if (options.deployIdCronJobs !== 'none') shellExec(`node bin cron --dev --setup-start --apply`);
678
+ if (options.deployIdCronJobs !== 'none')
679
+ shellExec(`node bin cron${baseClusterCommand}${clusterFlag} --setup-start --git --apply`);
544
680
  }
545
681
 
546
682
  const currentTraffic = isDeployRunnerContext(path, options)
@@ -553,20 +689,25 @@ class UnderpostRun {
553
689
  const cmdString = options.cmd
554
690
  ? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
555
691
  : '';
692
+ const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
556
693
 
557
694
  shellExec(
558
- `${baseCommand} deploy --kubeadm --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
695
+ `${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
559
696
  image ? ` --image ${image}` : ''
560
697
  }${versions ? ` --versions ${versions}` : ''}${
561
698
  options.namespace ? ` --namespace ${options.namespace}` : ''
562
- }${timeoutFlags}${cmdString} dd ${env}`,
699
+ }${timeoutFlags}${cmdString}${gitCleanFlag} ${deployId} ${env}`,
563
700
  );
564
701
 
565
702
  if (isDeployRunnerContext(path, options)) {
703
+ // Backup app/services repositories with repo-backup configured
566
704
  shellExec(
567
- `${baseCommand} deploy --kubeadm${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
705
+ `${baseCommand} db ${deployId} ${clusterFlag}${baseClusterCommand} --repo-backup --primary-pod --git --force-clone --preserveUUID ${options.namespace ? ` --ns ${options.namespace}` : ''}`,
706
+ );
707
+ shellExec(
708
+ `${baseCommand} deploy${clusterFlag}${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
568
709
  options.namespace ? ` --namespace ${options.namespace}` : ''
569
- }${timeoutFlags}`,
710
+ }${timeoutFlags}${gitCleanFlag}`,
570
711
  );
571
712
  if (!targetTraffic)
572
713
  targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
@@ -778,6 +919,7 @@ class UnderpostRun {
778
919
  const confInstances = JSON.parse(
779
920
  fs.readFileSync(`./engine-private/conf/${deployId}/conf.instances.json`, 'utf8'),
780
921
  );
922
+ let promotedTraffic = '';
781
923
  for (const instance of confInstances) {
782
924
  let {
783
925
  id: _id,
@@ -797,6 +939,7 @@ class UnderpostRun {
797
939
  namespace: options.namespace,
798
940
  });
799
941
  const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
942
+ promotedTraffic = targetTraffic;
800
943
  let proxyYaml =
801
944
  Underpost.deploy.baseProxyYamlFactory({ host: _host, env: options.tls ? 'production' : env, options }) +
802
945
  Underpost.deploy.deploymentYamlServiceFactory({
@@ -822,6 +965,18 @@ EOF
822
965
  { disableLog: true },
823
966
  );
824
967
  }
968
+ // Refresh the gRPC service to ensure it points to the parent deploy's current traffic.
969
+ if (promotedTraffic) {
970
+ const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
971
+ const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
972
+ deployId,
973
+ env,
974
+ confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
975
+ namespace: options.namespace,
976
+ traffic: [parentTraffic],
977
+ });
978
+ if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
979
+ }
825
980
  },
826
981
 
827
982
  /**
@@ -861,12 +1016,12 @@ EOF
861
1016
  // `localhost/rockylinux9-underpost:${Underpost.version}`
862
1017
  if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
863
1018
 
864
- if (options.nodeName) {
865
- shellExec(`sudo crictl pull ${_image}`);
866
- } else {
867
- shellExec(`docker pull ${_image}`);
868
- shellExec(`sudo kind load docker-image ${_image}`);
869
- }
1019
+ Underpost.image.pullDockerHubImage({
1020
+ dockerhubImage: _image,
1021
+ kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
1022
+ kubeadm: options.nodeName || options.kubeadm,
1023
+ k3s: options.k3s,
1024
+ });
870
1025
 
871
1026
  const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
872
1027
  hostTest: _host,
@@ -875,7 +1030,7 @@ EOF
875
1030
 
876
1031
  const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
877
1032
  const podId = `${_deployId}-${env}-${targetTraffic}`;
878
- const ignorePods = Underpost.deploy.get(podId, 'pods', options.namespace).map((p) => p.NAME);
1033
+ const ignorePods = Underpost.kubectl.get(podId, 'pods', options.namespace).map((p) => p.NAME);
879
1034
  Underpost.deploy.configMap(env, options.namespace);
880
1035
  shellExec(`kubectl delete service ${podId}-service --namespace ${options.namespace} --ignore-not-found`);
881
1036
  shellExec(`kubectl delete deployment ${podId} --namespace ${options.namespace} --ignore-not-found`);
@@ -887,7 +1042,30 @@ EOF
887
1042
  env,
888
1043
  version: targetTraffic,
889
1044
  nodeName: options.nodeName,
1045
+ clusterContext: options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind',
1046
+ gitClean: options.gitClean || false,
890
1047
  });
1048
+ // Regenerate the parent deploy's gRPC ClusterIP service pointing to the
1049
+ // parent's current traffic colour and apply it before the instance pod starts so
1050
+ // DNS is resolvable the moment the pod boots.
1051
+ const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
1052
+ const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
1053
+ deployId,
1054
+ env,
1055
+ confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
1056
+ namespace: options.namespace,
1057
+ traffic: [targetTraffic],
1058
+ host: _host,
1059
+ });
1060
+ if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
1061
+
1062
+ const resolvedCmd = _cmd[env].map((c) =>
1063
+ c.replaceAll(
1064
+ '{{grpc-service-dns}}',
1065
+ `${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
1066
+ ),
1067
+ );
1068
+
891
1069
  let deploymentYaml = `---
892
1070
  ${Underpost.deploy
893
1071
  .deploymentYamlPartsFactory({
@@ -899,7 +1077,7 @@ ${Underpost.deploy
899
1077
  image: _image,
900
1078
  namespace: options.namespace,
901
1079
  volumes: _volumes,
902
- cmd: _cmd[env],
1080
+ cmd: resolvedCmd,
903
1081
  })
904
1082
  .replace('{{ports}}', buildKindPorts(_fromPort, _toPort))}
905
1083
  `;
@@ -945,7 +1123,7 @@ EOF
945
1123
  * @memberof UnderpostRun
946
1124
  */
947
1125
  'ls-deployments': async (path, options = DEFAULT_OPTION) => {
948
- console.table(await Underpost.deploy.get(path, 'deployments', options.namespace));
1126
+ console.table(await Underpost.kubectl.get(path, 'deployments', options.namespace));
949
1127
  },
950
1128
 
951
1129
  /**
@@ -1008,9 +1186,7 @@ EOF
1008
1186
  volumeMountPath: volumeHostPath,
1009
1187
  ...(options.dev ? { volumeHostPath } : { claimName }),
1010
1188
  on: {
1011
- init: async () => {
1012
- // openTerminal(`kubectl logs -f ${podName}`);
1013
- },
1189
+ init: async () => {},
1014
1190
  },
1015
1191
  args: [daemonProcess(path ? path : `cd /home/dd/engine && npm install && npm run test`)],
1016
1192
  };
@@ -1041,15 +1217,13 @@ EOF
1041
1217
  'db-client': async (path, options = DEFAULT_OPTION) => {
1042
1218
  const { underpostRoot } = options;
1043
1219
 
1044
- const image = 'adminer:4.7.6-standalone';
1045
-
1046
- if (!options.kubeadm && !options.k3s) {
1047
- // Only load if not kubeadm/k3s (Kind needs it)
1048
- shellExec(`docker pull ${image}`);
1049
- shellExec(`sudo kind load docker-image ${image}`);
1050
- } else if (options.kubeadm || options.k3s)
1051
- // For kubeadm/k3s, ensure it's available for containerd
1052
- shellExec(`sudo crictl pull ${image}`);
1220
+ Underpost.image.pullDockerHubImage({
1221
+ dockerhubImage: 'adminer',
1222
+ version: '4.7.6-standalone',
1223
+ kind: options.kind,
1224
+ kubeadm: options.kubeadm,
1225
+ k3s: options.k3s,
1226
+ });
1053
1227
 
1054
1228
  shellExec(`kubectl delete deployment adminer -n ${options.namespace} --ignore-not-found`);
1055
1229
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/deployment/adminer/. -n ${options.namespace}`);
@@ -1145,7 +1319,7 @@ EOF
1145
1319
  const deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',');
1146
1320
  let hosts = [];
1147
1321
  for (const deployId of deployList) {
1148
- const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
1322
+ const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
1149
1323
  hosts = hosts.concat(Object.keys(confServer));
1150
1324
  }
1151
1325
  shellExec(`node bin cluster --prom ${hosts.join(',')}`);
@@ -1318,34 +1492,31 @@ EOF
1318
1492
  if (!subConf) subConf = 'local';
1319
1493
  if (options.reset && fs.existsSync(`./engine-private/conf/${deployId}`))
1320
1494
  fs.removeSync(`./engine-private/conf/${deployId}`);
1321
- if (!fs.existsSync(`./engine-private/conf/${deployId}`)) Config.deployIdFactory(deployId, { subConf });
1322
1495
  if (options.devProxyPortOffset) {
1323
1496
  const envPath = `./engine-private/conf/${deployId}/.env.development`;
1324
1497
  const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
1325
1498
  envObj.DEV_PROXY_PORT_OFFSET = options.devProxyPortOffset;
1326
1499
  writeEnv(envPath, envObj);
1327
1500
  }
1501
+ dotenv.config({ path: `./engine-private/conf/${deployId}/.env.development`, override: true });
1328
1502
  shellExec(`node bin run dev-cluster --expose --namespace ${options.namespace}`, { async: true });
1329
1503
  {
1330
- const cmd = `npm run dev-api ${deployId} ${subConf} ${host} ${_path} ${clientHostPort}${
1504
+ const cmd = `npm run dev:api ${deployId} ${subConf} ${host} ${_path} ${clientHostPort} proxy${
1331
1505
  options.tls ? ' tls' : ''
1332
1506
  }`;
1333
- options.terminal ? openTerminal(cmd) : shellExec(cmd, { async: true });
1507
+ shellExec(cmd, { async: true });
1334
1508
  }
1335
1509
  await awaitDeployMonitor(true);
1336
1510
  {
1337
- const cmd = `npm run dev-client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
1338
- options.terminal
1339
- ? openTerminal(cmd)
1340
- : shellExec(cmd, {
1341
- async: true,
1342
- });
1511
+ const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
1512
+
1513
+ shellExec(cmd, {
1514
+ async: true,
1515
+ });
1343
1516
  }
1344
1517
  await awaitDeployMonitor(true);
1345
1518
  shellExec(
1346
- `./node_modules/.bin/env-cmd -f .env.development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${
1347
- options.tls ? ' tls' : ''
1348
- }`,
1519
+ `NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
1349
1520
  );
1350
1521
  },
1351
1522
 
@@ -1364,7 +1535,7 @@ EOF
1364
1535
  let [deployId, serviceId, host, _path, replicas, image, node] = path.split(',');
1365
1536
  if (!replicas) replicas = options.replicas;
1366
1537
  // const confClient = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.client.json`, 'utf8'));
1367
- const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
1538
+ const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
1368
1539
  // const confSSR = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.ssr.json`, 'utf8'));
1369
1540
  // const packageData = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/package.json`, 'utf8'));
1370
1541
  const services = fs.existsSync(`./engine-private/deploy/${deployId}/conf.services.json`)
@@ -1455,9 +1626,7 @@ EOF
1455
1626
  'etc-hosts': async (path = '', options = DEFAULT_OPTION) => {
1456
1627
  const hosts = path ? path.split(',') : [];
1457
1628
  if (options.deployId) {
1458
- const confServer = JSON.parse(
1459
- fs.readFileSync(`./engine-private/conf/${options.deployId}/conf.server.json`, 'utf8'),
1460
- );
1629
+ const confServer = loadConfServerJson(`./engine-private/conf/${options.deployId}/conf.server.json`);
1461
1630
  hosts.push(...Object.keys(confServer));
1462
1631
  }
1463
1632
  const hostListenResult = Underpost.deploy.etcHostFactory(hosts);
@@ -1527,29 +1696,75 @@ EOF
1527
1696
  },
1528
1697
 
1529
1698
  /**
1530
- * @method ptls
1531
- * @description Set on ~/.bashrc alias: ports <port> Command to list listening ports that match the given keyword.
1532
- * @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter listening ports).
1699
+ * @method pid-info
1700
+ * @description Displays detailed information about a process by PID, including service details, command line, executable path, working directory, environment variables, and parent process tree.
1701
+ * @param {string} path - The PID of the process to inspect.
1533
1702
  * @param {Object} options - The default underpost runner options for customizing workflow
1534
1703
  * @memberof UnderpostRun
1535
1704
  */
1536
- ptls: async (path = '', options = DEFAULT_OPTION) => {
1537
- shellExec(`chmod +x ${options.underpostRoot}/scripts/ports-ls.sh`);
1538
- shellExec(`${options.underpostRoot}/scripts/ports-ls.sh`);
1705
+ 'pid-info': (path, options = DEFAULT_OPTION) => {
1706
+ const pid = path;
1707
+ if (!pid) {
1708
+ logger.error('PID is required. Usage: underpost run pid-info <pid>');
1709
+ return;
1710
+ }
1711
+
1712
+ // Services
1713
+ logger.info('Process info');
1714
+ shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd`);
1715
+ logger.info('Command line');
1716
+ shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo`);
1717
+ logger.info('Executable path');
1718
+ shellExec(`sudo readlink -f /proc/${pid}/exe`);
1719
+ logger.info('Working directory');
1720
+ shellExec(`sudo readlink -f /proc/${pid}/cwd`);
1721
+ logger.info('Environment variables (first 200)');
1722
+ shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200`);
1723
+
1724
+ // Parent
1725
+ logger.info('Parent process');
1726
+ const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, { stdout: true, silent: true });
1727
+ console.log(parentInfo);
1728
+ const ppidMatch = parentInfo.split('\n').find((l) => l.trim().startsWith(pid));
1729
+ if (ppidMatch) {
1730
+ const ppid = ppidMatch.trim().split(/\s+/)[1];
1731
+ logger.info(`Parent PID: ${ppid}`);
1732
+ shellExec(`ps -fp ${ppid}`);
1733
+ }
1734
+ logger.info('Process tree');
1735
+ shellExec(`pstree -s ${pid}`);
1736
+ },
1737
+
1738
+ /**
1739
+ * @method background
1740
+ * @description Runs a custom command in the background using nohup, logging output to `/var/log/<id>.log` and saving the PID to `/var/run/<id>.pid`.
1741
+ * @param {string} path - The command to run in the background (e.g. 'npm run prod:container dd-cyberia-r3').
1742
+ * @param {Object} options - The default underpost runner options for customizing workflow
1743
+ * @memberof UnderpostRun
1744
+ */
1745
+ background: (path, options = DEFAULT_OPTION) => {
1746
+ if (!path) {
1747
+ logger.error('Command is required. Usage: underpost run background <command>');
1748
+ return;
1749
+ }
1750
+ const id = path.split(/\s+/).pop();
1751
+ const logFile = `/var/log/${id}.log`;
1752
+ const pidFile = `/var/run/${id}.pid`;
1753
+ logger.info(`Starting background process`, { id, logFile, pidFile });
1754
+ shellExec(`nohup ${path} > ${logFile} 2>&1 & pid=$!; echo $pid > ${pidFile}; disown`);
1755
+ logger.info(`Background process started for '${id}'`);
1539
1756
  },
1757
+
1540
1758
  /**
1541
- * @method release-cmt
1542
- * @description Commits and pushes a new release for the `engine` repository with a message indicating the new version.
1543
- * @param {string} path - The input value, identifier, or path for the operation.
1759
+ * @method ports
1760
+ * @description Set on ~/.bashrc alias: ports <port> Command to list listening ports that match the given keyword.
1761
+ * @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter listening ports).
1544
1762
  * @param {Object} options - The default underpost runner options for customizing workflow
1545
1763
  * @memberof UnderpostRun
1546
1764
  */
1547
- 'release-cmt': async (path, options = DEFAULT_OPTION) => {
1548
- shellExec(`underpost run pull`);
1549
- shellExec(`underpost run secret`);
1550
- shellCd(`/home/dd/engine`);
1551
- shellExec(`underpost cmt --empty . ci engine ' New engine release $(underpost --version)'`);
1552
- shellExec(`underpost push . ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
1765
+ ports: async (path = '', options = DEFAULT_OPTION) => {
1766
+ shellExec(`chmod +x ${options.underpostRoot}/scripts/ports-ls.sh`);
1767
+ shellExec(`${options.underpostRoot}/scripts/ports-ls.sh`);
1553
1768
  },
1554
1769
 
1555
1770
  /**
@@ -1571,43 +1786,12 @@ EOF
1571
1786
  : [
1572
1787
  `npm install -g npm@11.2.0`,
1573
1788
  `npm install -g underpost`,
1574
- `${baseCommand} secret underpost --create-from-file /etc/config/.env.${env}`,
1575
- `${baseCommand} start --build --run ${deployId} ${env} --underpost-quickly-install`,
1789
+ `${baseCommand} secret underpost --create-from-env`,
1790
+ `${baseCommand} start --build --run ${deployId} ${env}`,
1576
1791
  ];
1577
1792
  shellExec(`node bin run sync${baseClusterCommand} --deploy-id-cron-jobs none dd-test --cmd "${cmd}"`);
1578
1793
  },
1579
1794
 
1580
- /**
1581
- * @method sync-replica
1582
- * @description Syncs a replica for the dd.router
1583
- * @param {string} path - The input value, identifier, or path for the operation.
1584
- * @param {Object} options - The default underpost runner options for customizing workflow
1585
- * @memberof UnderpostRun
1586
- */
1587
- 'sync-replica': async (path, options = DEFAULT_OPTION) => {
1588
- const env = options.dev ? 'development' : 'production';
1589
- const baseCommand = options.dev ? 'node bin' : 'underpost';
1590
-
1591
- for (let deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
1592
- deployId = deployId.trim();
1593
- const _path = '/single-replica';
1594
- const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
1595
- shellExec(`${baseCommand} env ${deployId} ${env}`);
1596
- for (const host of Object.keys(confServer))
1597
- if (_path in confServer[host]) shellExec(`node bin/deploy build-single-replica ${deployId} ${host} ${_path}`);
1598
- const node = options.nodeName
1599
- ? options.nodeName
1600
- : options.dev || !isDeployRunnerContext(path, options)
1601
- ? 'kind-control-plane'
1602
- : os.hostname();
1603
- // deployId, replicas, versions, image, node
1604
- let defaultPath = [deployId, 1, ``, ``, node];
1605
- shellExec(`${baseCommand} run${options.dev === true ? ' --dev' : ''} --build sync ${defaultPath}`);
1606
- shellExec(`node bin/deploy build-full-client ${deployId}`);
1607
- }
1608
- if (isDeployRunnerContext(path, options)) shellExec(`${baseCommand} run promote ${path} production`);
1609
- },
1610
-
1611
1795
  /**
1612
1796
  * @method tf-vae-test
1613
1797
  * @description Creates and runs a job pod (`tf-vae-test`) that installs TensorFlow dependencies, clones the TensorFlow docs, and runs the CVAE tutorial script, with a terminal monitor attached.
@@ -1633,7 +1817,7 @@ EOF
1633
1817
 
1634
1818
  const { close } = await (async () => {
1635
1819
  const checkAwaitPath = '/await';
1636
- while (!Underpost.deploy.existsContainerFile({ podName, path: checkAwaitPath })) {
1820
+ while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
1637
1821
  logger.info('monitor', checkAwaitPath);
1638
1822
  await timer(1000);
1639
1823
  }
@@ -1660,7 +1844,7 @@ EOF
1660
1844
  logger.info('monitor', checkPath);
1661
1845
  {
1662
1846
  const checkAwaitPath = `/home/dd/docs${checkPath}`;
1663
- while (!Underpost.deploy.existsContainerFile({ podName, path: checkAwaitPath })) {
1847
+ while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
1664
1848
  logger.info('waiting for', checkAwaitPath);
1665
1849
  await timer(1000);
1666
1850
  }
@@ -1680,7 +1864,7 @@ EOF
1680
1864
  shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${fromPath} ${toPath}`);
1681
1865
  }
1682
1866
 
1683
- openTerminal(`firefox ${outsPaths.join(' ')}`, { single: true });
1867
+ shellExec(`firefox ${outsPaths.join(' ')}`);
1684
1868
  process.exit(0);
1685
1869
  }
1686
1870
  })();
@@ -1726,7 +1910,8 @@ EOF
1726
1910
 
1727
1911
  shellCd(dir);
1728
1912
 
1729
- shellExec(`git init && git add . && git commit -m "Base implementation"`);
1913
+ Underpost.repo.initLocalRepo({ path: dir });
1914
+ shellExec(`git add . && git commit -m "Base implementation"`);
1730
1915
  shellExec(`chmod +x ./replace_params.sh`);
1731
1916
  shellExec(`chmod +x ./build.sh`);
1732
1917
 
@@ -1771,11 +1956,46 @@ EOF
1771
1956
  * @param {Object} options - The default underpost runner options for customizing workflow
1772
1957
  * @memberof UnderpostRun
1773
1958
  */
1959
+ /**
1960
+ * @method generate-pass
1961
+ * @description Generates a cryptographically secure random password that satisfies all validatePassword
1962
+ * constraints (lowercase, uppercase, digit, special char, min 8 chars). Logs the plain password
1963
+ * to the console or, when `--copy` is set, copies it to the clipboard via pbcopy.
1964
+ * @param {string} path - Optional password length (default: 16).
1965
+ * @param {Object} options - The default underpost runner options for customizing workflow.
1966
+ * @param {boolean} options.copy - When true, copies to clipboard instead of logging.
1967
+ * @memberof UnderpostRun
1968
+ */
1969
+ 'generate-pass': (path, options = DEFAULT_OPTION) => {
1970
+ const length = path && parseInt(path) > 0 ? parseInt(path) : 16;
1971
+ const lower = 'abcdefghijklmnopqrstuvwxyz';
1972
+ const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
1973
+ const digits = '0123456789';
1974
+ const special = '@#$%^&*()_+';
1975
+ const all = lower + upper + digits + special;
1976
+ const buf = crypto.randomBytes(length + 4);
1977
+ // Guarantee at least one character from each required class
1978
+ const chars = [
1979
+ lower[buf[0] % lower.length],
1980
+ upper[buf[1] % upper.length],
1981
+ digits[buf[2] % digits.length],
1982
+ special[buf[3] % special.length],
1983
+ ];
1984
+ for (let i = 4; i < length; i++) chars.push(all[buf[i] % all.length]);
1985
+ // Fisher-Yates shuffle using an independent random buffer
1986
+ const shuf = crypto.randomBytes(length);
1987
+ for (let i = chars.length - 1; i > 0; i--) {
1988
+ const j = shuf[i % shuf.length] % (i + 1);
1989
+ [chars[i], chars[j]] = [chars[j], chars[i]];
1990
+ }
1991
+ const password = chars.join('');
1992
+ if (options.copy) pbcopy(password);
1993
+ else console.log(password);
1994
+ },
1995
+
1774
1996
  secret: (path, options = DEFAULT_OPTION) => {
1775
1997
  const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
1776
- const command = options.dev
1777
- ? `node bin secret underpost --create-from-file ${secretPath}`
1778
- : `underpost secret underpost --create-from-file ${secretPath}`;
1998
+ const command = `node bin secret underpost --create-from-file ${secretPath}`;
1779
1999
  shellExec(command);
1780
2000
  },
1781
2001
  /**