cyberia 2.99.8 → 3.0.2

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 (81) hide show
  1. package/.env.production +1 -0
  2. package/.github/workflows/engine-cyberia.cd.yml +1 -0
  3. package/.github/workflows/gitlab.ci.yml +20 -0
  4. package/.github/workflows/publish.ci.yml +18 -38
  5. package/.github/workflows/publish.cyberia.ci.yml +18 -38
  6. package/.vscode/extensions.json +8 -50
  7. package/.vscode/settings.json +0 -77
  8. package/CHANGELOG.md +171 -1
  9. package/{cli.md → CLI-HELP.md} +49 -44
  10. package/README.md +139 -0
  11. package/bin/build.js +7 -15
  12. package/bin/cyberia.js +385 -71
  13. package/bin/deploy.js +14 -151
  14. package/bin/file.js +13 -8
  15. package/bin/index.js +385 -71
  16. package/bin/zed.js +63 -2
  17. package/conf.js +32 -3
  18. package/deployment.yaml +2 -2
  19. package/jsdoc.json +1 -2
  20. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  21. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  22. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  23. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  24. package/manifests/deployment/fastapi/initial_data.sh +4 -52
  25. package/manifests/ipfs/configmap.yaml +64 -0
  26. package/manifests/ipfs/headless-service.yaml +35 -0
  27. package/manifests/ipfs/kustomization.yaml +8 -0
  28. package/manifests/ipfs/statefulset.yaml +149 -0
  29. package/manifests/ipfs/storage-class.yaml +9 -0
  30. package/package.json +15 -11
  31. package/scripts/k3s-node-setup.sh +89 -0
  32. package/scripts/lxd-vm-setup.sh +23 -0
  33. package/scripts/rocky-setup.sh +1 -13
  34. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +2 -0
  35. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -0
  36. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +93 -2
  37. package/src/api/file/file.controller.js +3 -13
  38. package/src/api/file/file.ref.json +0 -21
  39. package/src/api/ipfs/ipfs.controller.js +104 -0
  40. package/src/api/ipfs/ipfs.model.js +71 -0
  41. package/src/api/ipfs/ipfs.router.js +31 -0
  42. package/src/api/ipfs/ipfs.service.js +193 -0
  43. package/src/api/object-layer/README.md +139 -0
  44. package/src/api/object-layer/object-layer.controller.js +3 -0
  45. package/src/api/object-layer/object-layer.model.js +15 -1
  46. package/src/api/object-layer/object-layer.router.js +6 -10
  47. package/src/api/object-layer/object-layer.service.js +311 -182
  48. package/src/api/user/user.router.js +0 -47
  49. package/src/cli/baremetal.js +7 -9
  50. package/src/cli/cluster.js +95 -152
  51. package/src/cli/deploy.js +8 -5
  52. package/src/cli/index.js +31 -31
  53. package/src/cli/ipfs.js +184 -0
  54. package/src/cli/lxd.js +192 -237
  55. package/src/cli/repository.js +4 -1
  56. package/src/cli/run.js +17 -2
  57. package/src/client/components/core/Docs.js +92 -6
  58. package/src/client/components/core/LoadingAnimation.js +2 -3
  59. package/src/client/components/core/Modal.js +1 -1
  60. package/src/client/components/core/VanillaJs.js +36 -25
  61. package/src/client/components/cyberia/ObjectLayerEngineModal.js +4 -5
  62. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +280 -29
  63. package/src/client/services/ipfs/ipfs.service.js +144 -0
  64. package/src/client/services/object-layer/object-layer.management.js +161 -8
  65. package/src/client/services/user/user.management.js +0 -5
  66. package/src/client/services/user/user.service.js +1 -1
  67. package/src/index.js +12 -1
  68. package/src/runtime/express/Express.js +4 -3
  69. package/src/server/auth.js +18 -18
  70. package/src/server/client-build-docs.js +178 -41
  71. package/src/server/conf.js +1 -1
  72. package/src/server/ipfs-client.js +433 -0
  73. package/src/server/logger.js +22 -10
  74. package/src/server/object-layer.js +649 -18
  75. package/src/server/semantic-layer-generator.js +1083 -0
  76. package/src/server/shape-generator.js +952 -0
  77. package/test/shape-generator.test.js +457 -0
  78. package/.vscode/zed.keymap.json +0 -39
  79. package/.vscode/zed.settings.json +0 -20
  80. package/bin/ssl.js +0 -63
  81. package/manifests/lxd/underpost-setup.sh +0 -163
@@ -99,22 +99,6 @@ const UserRouter = (options) => {
99
99
  #swagger.description = 'This endpoint get a JWT for authenticated user'
100
100
  #swagger.path = '/user/auth'
101
101
  #swagger.method = 'post'
102
- #swagger.produces = ['application/json']
103
- #swagger.consumes = ['application/json']
104
-
105
- #swagger.requestBody = {
106
- in: 'body',
107
- description: 'User data',
108
- required: true,
109
- content: {
110
- 'application/json': {
111
- schema: {
112
- $ref: '#/components/schemas/userLogInRequest'
113
- }
114
- }
115
- }
116
- }
117
-
118
102
  #swagger.responses[200] = {
119
103
  description: 'User created successfully',
120
104
  content: {
@@ -148,22 +132,6 @@ const UserRouter = (options) => {
148
132
  #swagger.description = 'This endpoint will create a new user account'
149
133
  #swagger.path = '/user'
150
134
  #swagger.method = 'post'
151
- #swagger.produces = ['application/json']
152
- #swagger.consumes = ['application/json']
153
-
154
- #swagger.requestBody = {
155
- in: 'body',
156
- description: 'User data',
157
- required: true,
158
- content: {
159
- 'application/json': {
160
- schema: {
161
- $ref: '#/components/schemas/userRequest'
162
- }
163
- }
164
- }
165
- }
166
-
167
135
  #swagger.responses[200] = {
168
136
  description: 'User created successfully',
169
137
  content: {
@@ -274,8 +242,6 @@ const UserRouter = (options) => {
274
242
  #swagger.description = 'This endpoint will update user data by ID'
275
243
  #swagger.path = '/user/{id}'
276
244
  #swagger.method = 'put'
277
- #swagger.produces = ['application/json']
278
- #swagger.consumes = ['application/json']
279
245
  #swagger.security = [{
280
246
  'bearerAuth': []
281
247
  }]
@@ -287,19 +253,6 @@ const UserRouter = (options) => {
287
253
  type: 'string'
288
254
  }
289
255
 
290
- #swagger.requestBody = {
291
- in: 'body',
292
- description: 'User data',
293
- required: true,
294
- content: {
295
- 'application/json': {
296
- schema: {
297
- $ref: '#/components/schemas/userRequest'
298
- }
299
- }
300
- }
301
- }
302
-
303
256
  #swagger.responses[200] = {
304
257
  description: 'User updated successfully',
305
258
  content: {
@@ -534,9 +534,6 @@ rm -rf ${artifacts.join(' ')}`);
534
534
  if (options.controlServerDbInstall === true) {
535
535
  // Deploy the database provider and manage MAAS database.
536
536
  shellExec(`node ${underpostRoot}/bin/deploy ${dbProviderId} install`);
537
- shellExec(
538
- `node ${underpostRoot}/bin/deploy pg-drop-db ${process.env.DB_PG_MAAS_NAME} ${process.env.DB_PG_MAAS_USER}`,
539
- );
540
537
  shellExec(`node ${underpostRoot}/bin/deploy maas-db`);
541
538
  return;
542
539
  }
@@ -1150,8 +1147,9 @@ rm -rf ${artifacts.join(' ')}`);
1150
1147
  machine: machine ? machine.system_id : null,
1151
1148
  });
1152
1149
 
1153
- const { discovery, machine: discoveredMachine } =
1154
- await Underpost.baremetal.commissionMonitor(commissionMonitorPayload);
1150
+ const { discovery, machine: discoveredMachine } = await Underpost.baremetal.commissionMonitor(
1151
+ commissionMonitorPayload,
1152
+ );
1155
1153
  if (discoveredMachine) machine = discoveredMachine;
1156
1154
  }
1157
1155
  },
@@ -2496,10 +2494,10 @@ fi
2496
2494
  const discoverHostname = discovery.hostname
2497
2495
  ? discovery.hostname
2498
2496
  : discovery.mac_organization
2499
- ? discovery.mac_organization
2500
- : discovery.domain
2501
- ? discovery.domain
2502
- : `generic-host-${s4()}${s4()}`;
2497
+ ? discovery.mac_organization
2498
+ : discovery.domain
2499
+ ? discovery.domain
2500
+ : `generic-host-${s4()}${s4()}`;
2503
2501
 
2504
2502
  console.log(discoverHostname.bgBlue.bold.white);
2505
2503
  console.log('ip target:'.green + ipAddress, 'ip discovered:'.green + discovery.ip);
@@ -36,7 +36,7 @@ class UnderpostCluster {
36
36
  * @param {boolean} [options.mysql=false] - Deploy MySQL.
37
37
  * @param {boolean} [options.postgresql=false] - Deploy PostgreSQL.
38
38
  * @param {boolean} [options.valkey=false] - Deploy Valkey.
39
- * @param {boolean} [options.full=false] - Deploy a full set of common components.
39
+ * @param {boolean} [options.ipfs=false] - Deploy ipfs-cluster statefulset.
40
40
  * @param {boolean} [options.info=false] - Display extensive Kubernetes cluster information.
41
41
  * @param {boolean} [options.certManager=false] - Deploy Cert-Manager for certificate management.
42
42
  * @param {boolean} [options.listPods=false] - List Kubernetes pods.
@@ -57,10 +57,10 @@ class UnderpostCluster {
57
57
  * @param {string} [options.prom=''] - Initialize the cluster with a Prometheus Operator deployment and monitor scrap for specified hosts.
58
58
  * @param {boolean} [options.uninstallHost=false] - Uninstall all host components.
59
59
  * @param {boolean} [options.config=false] - Apply general host configuration (SELinux, containerd, sysctl, firewalld).
60
- * @param {boolean} [options.worker=false] - Configure as a worker node (for Kubeadm or K3s join).
61
60
  * @param {boolean} [options.chown=false] - Set up kubectl configuration for the current user.
62
61
  * @param {boolean} [options.removeVolumeHostPaths=false] - Remove data from host paths used by Persistent Volumes.
63
62
  * @param {string} [options.hosts] - Set custom hosts entries.
63
+ * @param {string} [options.replicas] - Set the number of replicas for certain deployments.
64
64
  * @memberof UnderpostCluster
65
65
  */
66
66
  async init(
@@ -73,7 +73,7 @@ class UnderpostCluster {
73
73
  mysql: false,
74
74
  postgresql: false,
75
75
  valkey: false,
76
- full: false,
76
+ ipfs: false,
77
77
  info: false,
78
78
  certManager: false,
79
79
  listPods: false,
@@ -94,32 +94,28 @@ class UnderpostCluster {
94
94
  prom: '',
95
95
  uninstallHost: false,
96
96
  config: false,
97
- worker: false,
98
97
  chown: false,
99
98
  removeVolumeHostPaths: false,
100
99
  hosts: '',
100
+ replicas: '',
101
101
  },
102
102
  ) {
103
- // Handles initial host setup (installing docker, podman, kind, kubeadm, helm)
104
- if (options.initHost === true) return Underpost.cluster.initHost();
103
+ if (options.initHost) return Underpost.cluster.initHost();
105
104
 
106
- // Handles initial host setup (installing docker, podman, kind, kubeadm, helm)
107
- if (options.uninstallHost === true) return Underpost.cluster.uninstallHost();
105
+ if (options.uninstallHost) return Underpost.cluster.uninstallHost();
108
106
 
109
- // Applies general host configuration (SELinux, containerd, sysctl)
110
- if (options.config === true) return Underpost.cluster.config();
107
+ if (options.config) return Underpost.cluster.config();
111
108
 
112
- // Sets up kubectl configuration for the current user
113
- if (options.chown === true) return Underpost.cluster.chown();
109
+ if (options.chown) return Underpost.cluster.chown();
114
110
 
115
111
  const npmRoot = getNpmRootPath();
116
- const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
112
+ const underpostRoot = options.dev ? '.' : `${npmRoot}/underpost`;
117
113
 
118
- if (options.listPods === true) return console.table(Underpost.deploy.get(podName ?? undefined));
114
+ if (options.listPods) return console.table(Underpost.deploy.get(podName ?? undefined));
119
115
  // Set default namespace if not specified
120
116
  if (!options.namespace) options.namespace = 'default';
121
117
 
122
- if (options.nsUse && typeof options.nsUse === 'string') {
118
+ if (options.nsUse) {
123
119
  // Verify if namespace exists, create if not
124
120
  const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
125
121
  stdout: true,
@@ -140,11 +136,14 @@ class UnderpostCluster {
140
136
  }
141
137
 
142
138
  // Reset Kubernetes cluster components (Kind/Kubeadm/K3s) and container runtimes
143
- if (options.reset === true)
139
+ if (options.reset) {
140
+ const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
144
141
  return await Underpost.cluster.safeReset({
145
142
  underpostRoot,
146
143
  removeVolumeHostPaths: options.removeVolumeHostPaths,
144
+ clusterType,
147
145
  });
146
+ }
148
147
 
149
148
  // Check if a cluster (Kind, Kubeadm, or K3s) is already initialized
150
149
  const alreadyKubeadmCluster = Underpost.deploy.get('calico-kube-controllers')[0];
@@ -153,67 +152,21 @@ class UnderpostCluster {
153
152
  const alreadyK3sCluster = Underpost.deploy.get('svclb-traefik')[0];
154
153
 
155
154
  // --- Kubeadm/Kind/K3s Cluster Initialization ---
156
- // This block handles the initial setup of the Kubernetes cluster (control plane or worker).
157
- // It prevents re-initialization if a cluster is already detected.
158
- if (!options.worker && !alreadyKubeadmCluster && !alreadyKindCluster && !alreadyK3sCluster) {
155
+ if (!alreadyKubeadmCluster && !alreadyKindCluster && !alreadyK3sCluster) {
159
156
  Underpost.cluster.config();
160
- if (options.k3s === true) {
157
+ if (options.k3s) {
161
158
  logger.info('Initializing K3s control plane...');
162
159
  // Install K3s
163
- console.log('Installing K3s...');
160
+ logger.info('Installing K3s...');
164
161
  shellExec(`curl -sfL https://get.k3s.io | sh -`);
165
- console.log('K3s installation completed.');
166
-
167
- // Move k3s binary to /bin/k3s and make it executable
168
- shellExec(`sudo mv /usr/local/bin/k3s /bin/k3s`);
169
- shellExec(`sudo chmod +x /bin/k3s`);
170
- console.log('K3s binary moved to /bin/k3s and made executable.');
162
+ logger.info('K3s installation completed.');
171
163
 
172
- // Configure kubectl for the current user for K3s *before* checking readiness
173
- // This ensures kubectl can find the K3s kubeconfig immediately after K3s installation.
174
164
  Underpost.cluster.chown('k3s');
175
165
 
176
- // Wait for K3s to be ready
177
166
  logger.info('Waiting for K3s to be ready...');
178
- let k3sReady = false;
179
- let retries = 0;
180
- const maxRetries = 20; // Increased retries for K3s startup
181
- const delayMs = 5000; // 5 seconds
182
-
183
- while (!k3sReady && retries < maxRetries) {
184
- try {
185
- // Explicitly use KUBECONFIG for kubectl commands to ensure it points to K3s config
186
- const nodes = shellExec(`KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get nodes -o json`, {
187
- stdout: true,
188
- silent: true,
189
- });
190
- const parsedNodes = JSON.parse(nodes);
191
- if (
192
- parsedNodes.items.some((node) =>
193
- node.status.conditions.some((cond) => cond.type === 'Ready' && cond.status === 'True'),
194
- )
195
- ) {
196
- k3sReady = true;
197
- logger.info('K3s cluster is ready.');
198
- } else {
199
- logger.info(`K3s not yet ready. Retrying in ${delayMs / 1000} seconds...`);
200
- await new Promise((resolve) => setTimeout(resolve, delayMs));
201
- }
202
- } catch (error) {
203
- logger.info(`Error checking K3s status: ${error.message}. Retrying in ${delayMs / 1000} seconds...`);
204
- await new Promise((resolve) => setTimeout(resolve, delayMs));
205
- }
206
- retries++;
207
- }
208
-
209
- if (!k3sReady) {
210
- logger.error('K3s cluster did not become ready in time. Please check the K3s logs.');
211
- return;
212
- }
213
-
214
- // K3s includes local-path-provisioner by default, so no need to install explicitly.
215
- logger.info('K3s comes with local-path-provisioner by default. Skipping explicit installation.');
216
- } else if (options.kubeadm === true) {
167
+ shellExec(`sudo systemctl is-active --wait k3s || sudo systemctl wait --for=active k3s.service`);
168
+ logger.info('K3s service is active.');
169
+ } else if (options.kubeadm) {
217
170
  logger.info('Initializing Kubeadm control plane...');
218
171
  // Set default values if not provided
219
172
  const podNetworkCidr = options.podNetworkCidr || '192.168.0.0/16';
@@ -249,32 +202,22 @@ class UnderpostCluster {
249
202
  logger.info('Initializing Kind cluster...');
250
203
  shellExec(
251
204
  `cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
252
- options?.dev === true ? '-dev' : ''
205
+ options.dev ? '-dev' : ''
253
206
  }.yaml`,
254
207
  );
255
208
  Underpost.cluster.chown('kind'); // Pass 'kind' to chown
256
209
  }
257
- } else if (options.worker === true) {
258
- // Worker node specific configuration (kubeadm join command needs to be executed separately)
259
- logger.info('Worker node configuration applied. Awaiting join command...');
260
- // No direct cluster initialization here for workers. The `kubeadm join` or `k3s agent` command
261
- // needs to be run on the worker after the control plane is up and a token is created.
262
- // This part of the script is for general worker setup, not the join itself.
263
- } else {
264
- logger.warn('Cluster already initialized or worker flag not set for worker node.');
265
210
  }
266
211
 
267
212
  // --- Optional Component Deployments (Databases, Ingress, Cert-Manager) ---
268
213
  // These deployments happen after the base cluster is up.
269
214
 
270
- if (options.full === true || options.dedicatedGpu === true) {
215
+ if (options.dedicatedGpu) {
271
216
  shellExec(`node ${underpostRoot}/bin/deploy nvidia-gpu-operator`);
272
- shellExec(
273
- `node ${underpostRoot}/bin/deploy kubeflow-spark-operator${options.kubeadm === true ? ' kubeadm' : ''}`,
274
- );
217
+ shellExec(`node ${underpostRoot}/bin/deploy kubeflow-spark-operator${options.kubeadm ? ' kubeadm' : ''}`);
275
218
  }
276
219
 
277
- if (options.grafana === true) {
220
+ if (options.grafana) {
278
221
  shellExec(`kubectl delete deployment grafana -n ${options.namespace} --ignore-not-found`);
279
222
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana -n ${options.namespace}`);
280
223
  const yaml = `${fs
@@ -287,7 +230,7 @@ EOF
287
230
  `);
288
231
  }
289
232
 
290
- if (options.prom && typeof options.prom === 'string') {
233
+ if (options.prom) {
291
234
  shellExec(`kubectl delete deployment prometheus --ignore-not-found`);
292
235
  shellExec(`kubectl delete configmap prometheus-config --ignore-not-found`);
293
236
  shellExec(`kubectl delete service prometheus --ignore-not-found`);
@@ -306,41 +249,26 @@ EOF
306
249
  `);
307
250
  }
308
251
 
309
- if (options.full === true || options.valkey === true) {
310
- if (options.pullImage === true) {
311
- // shellExec(`sudo podman pull valkey/valkey:latest`);
312
- if (!options.kubeadm && !options.k3s) {
313
- // Only load if not kubeadm/k3s (Kind needs it)
314
- shellExec(`docker pull valkey/valkey:latest`);
315
- shellExec(`sudo kind load docker-image valkey/valkey:latest`);
316
- } else if (options.kubeadm || options.k3s)
317
- // For kubeadm/k3s, ensure it's available for containerd
318
- shellExec(`sudo crictl pull valkey/valkey:latest`);
319
- }
252
+ if (options.valkey) {
253
+ if (options.pullImage) Underpost.cluster.pullImage('valkey/valkey:latest', options);
320
254
  shellExec(`kubectl delete statefulset valkey-service -n ${options.namespace} --ignore-not-found`);
321
255
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey -n ${options.namespace}`);
322
256
  await Underpost.test.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60 * 10);
323
257
  }
324
- if (options.full === true || options.mariadb === true) {
258
+ if (options.ipfs) {
259
+ await Underpost.ipfs.deploy(options, underpostRoot);
260
+ }
261
+ if (options.mariadb) {
325
262
  shellExec(
326
263
  `sudo kubectl create secret generic mariadb-secret --from-file=username=/home/dd/engine/engine-private/mariadb-username --from-file=password=/home/dd/engine/engine-private/mariadb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
327
264
  );
328
265
  shellExec(`kubectl delete statefulset mariadb-statefulset -n ${options.namespace} --ignore-not-found`);
329
266
 
330
- if (options.pullImage === true) {
331
- // shellExec(`sudo podman pull mariadb:latest`);
332
- if (!options.kubeadm && !options.k3s) {
333
- // Only load if not kubeadm/k3s (Kind needs it)
334
- shellExec(`docker pull mariadb:latest`);
335
- shellExec(`sudo kind load docker-image mariadb:latest`);
336
- } else if (options.kubeadm || options.k3s)
337
- // For kubeadm/k3s, ensure it's available for containerd
338
- shellExec(`sudo crictl pull mariadb:latest`);
339
- }
267
+ if (options.pullImage) Underpost.cluster.pullImage('mariadb:latest', options);
340
268
  shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml -n ${options.namespace}`);
341
269
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb -n ${options.namespace}`);
342
270
  }
343
- if (options.full === true || options.mysql === true) {
271
+ if (options.mysql) {
344
272
  shellExec(
345
273
  `sudo kubectl create secret generic mysql-secret --from-file=username=/home/dd/engine/engine-private/mysql-username --from-file=password=/home/dd/engine/engine-private/mysql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
346
274
  );
@@ -349,31 +277,15 @@ EOF
349
277
  shellExec(`sudo chown -R $(whoami):$(whoami) /mnt/data`);
350
278
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql -n ${options.namespace}`);
351
279
  }
352
- if (options.full === true || options.postgresql === true) {
353
- if (options.pullImage === true) {
354
- if (!options.kubeadm && !options.k3s) {
355
- // Only load if not kubeadm/k3s (Kind needs it)
356
- shellExec(`docker pull postgres:latest`);
357
- shellExec(`sudo kind load docker-image postgres:latest`);
358
- } else if (options.kubeadm || options.k3s)
359
- // For kubeadm/k3s, ensure it's available for containerd
360
- shellExec(`sudo crictl pull postgres:latest`);
361
- }
280
+ if (options.postgresql) {
281
+ if (options.pullImage) Underpost.cluster.pullImage('postgres:latest', options);
362
282
  shellExec(
363
283
  `sudo kubectl create secret generic postgres-secret --from-file=password=/home/dd/engine/engine-private/postgresql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
364
284
  );
365
285
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql -n ${options.namespace}`);
366
286
  }
367
- if (options.mongodb4 === true) {
368
- if (options.pullImage === true) {
369
- if (!options.kubeadm && !options.k3s) {
370
- // Only load if not kubeadm/k3s (Kind needs it)
371
- shellExec(`docker pull mongo:4.4`);
372
- shellExec(`sudo kind load docker-image mongo:4.4`);
373
- } else if (options.kubeadm || options.k3s)
374
- // For kubeadm/k3s, ensure it's available for containerd
375
- shellExec(`sudo crictl pull mongo:4.4`);
376
- }
287
+ if (options.mongodb4) {
288
+ if (options.pullImage) Underpost.cluster.pullImage('mongo:4.4', options);
377
289
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4 -n ${options.namespace}`);
378
290
 
379
291
  const deploymentName = 'mongodb-deployment';
@@ -394,16 +306,8 @@ EOF
394
306
  --eval 'rs.initiate(${JSON.stringify(mongoConfig)})'`,
395
307
  );
396
308
  }
397
- } else if (options.full === true || options.mongodb === true) {
398
- if (options.pullImage === true) {
399
- if (!options.kubeadm && !options.k3s) {
400
- // Only load if not kubeadm/k3s (Kind needs it)
401
- shellExec(`docker pull mongo:latest`);
402
- shellExec(`sudo kind load docker-image mongo:latest`);
403
- } else if (options.kubeadm || options.k3s)
404
- // For kubeadm/k3s, ensure it's available for containerd
405
- shellExec(`sudo crictl pull mongo:latest`);
406
- }
309
+ } else if (options.mongodb) {
310
+ if (options.pullImage) Underpost.cluster.pullImage('mongo:latest', options);
407
311
  shellExec(
408
312
  `sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
409
313
  );
@@ -432,11 +336,11 @@ EOF
432
336
  }
433
337
  }
434
338
 
435
- if (options.full === true || options.contour === true) {
339
+ if (options.contour) {
436
340
  shellExec(
437
341
  `kubectl apply -f https://cdn.jsdelivr.net/gh/projectcontour/contour@release-1.33/examples/render/contour.yaml`,
438
342
  );
439
- if (options.kubeadm === true) {
343
+ if (options.kubeadm) {
440
344
  // Envoy service might need NodePort for kubeadm
441
345
  shellExec(
442
346
  `sudo kubectl apply -f ${underpostRoot}/manifests/envoy-service-nodeport.yaml -n ${options.namespace}`,
@@ -446,7 +350,7 @@ EOF
446
350
  // so a specific NodePort service might not be needed or can be configured differently.
447
351
  }
448
352
 
449
- if (options.full === true || options.certManager === true) {
353
+ if (options.certManager) {
450
354
  if (!Underpost.deploy.get('cert-manager').find((p) => p.STATUS === 'Running')) {
451
355
  shellExec(`helm repo add jetstack https://charts.jetstack.io --force-update`);
452
356
  shellExec(
@@ -464,6 +368,32 @@ EOF
464
368
  }
465
369
  },
466
370
 
371
+ /**
372
+ * @method pullImage
373
+ * @description Pulls a container image using the appropriate runtime based on the cluster type.
374
+ * - For Kind clusters: pulls via Docker and loads the image into the Kind cluster.
375
+ * - For Kubeadm/K3s clusters: pulls via crictl (containerd).
376
+ * @param {string} image - The fully-qualified container image reference (e.g. 'mongo:latest').
377
+ * @param {object} options - The cluster options object from `init`.
378
+ * @param {boolean} [options.kubeadm=false] - Whether the cluster is Kubeadm-based.
379
+ * @param {boolean} [options.k3s=false] - Whether the cluster is K3s-based.
380
+ * @memberof UnderpostCluster
381
+ */
382
+ pullImage(image, options = { kubeadm: false, k3s: false }) {
383
+ if (!options.kubeadm && !options.k3s) {
384
+ const tarPath = `/tmp/kind-image-${image.replace(/[\/:]/g, '-')}.tar`;
385
+ shellExec(`docker pull ${image}`);
386
+ shellExec(`docker save ${image} -o ${tarPath}`);
387
+ shellExec(
388
+ `for node in $(kind get nodes); do cat ${tarPath} | docker exec -i $node ctr --namespace=k8s.io images import -; done`,
389
+ );
390
+ shellExec(`rm -f ${tarPath}`);
391
+ } else if (options.kubeadm || options.k3s) {
392
+ // Kubeadm / K3s: use crictl to pull directly into containerd
393
+ shellExec(`sudo crictl pull ${image}`);
394
+ }
395
+ },
396
+
467
397
  /**
468
398
  * @method config
469
399
  * @description Configures host-level settings required for Kubernetes.
@@ -570,12 +500,15 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
570
500
  * @description Performs a complete reset of the Kubernetes cluster and its container environments.
571
501
  * This version focuses on correcting persistent permission errors (such as 'permission denied'
572
502
  * in coredns) by restoring SELinux security contexts and safely cleaning up cluster artifacts.
503
+ * Only the uninstall/delete commands specific to the given clusterType are executed; all other
504
+ * cleanup steps (log truncation, filesystem, network) are always run as generic k8s resets.
573
505
  * @param {object} [options] - Configuration options for the reset.
574
506
  * @param {string} [options.underpostRoot] - The root path of the underpost project.
575
507
  * @param {boolean} [options.removeVolumeHostPaths=false] - Whether to remove data from host paths used by Persistent Volumes.
508
+ * @param {string} [options.clusterType='kind'] - The type of cluster to reset: 'kind', 'kubeadm', or 'k3s'.
576
509
  * @memberof UnderpostCluster
577
510
  */
578
- async safeReset(options = { underpostRoot: '.', removeVolumeHostPaths: false }) {
511
+ async safeReset(options = { underpostRoot: '.', removeVolumeHostPaths: false, clusterType: 'kind' }) {
579
512
  logger.info('Starting a safe and comprehensive reset of Kubernetes and container environments...');
580
513
 
581
514
  try {
@@ -645,14 +578,22 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
645
578
  // Safely unmount pod filesystems to avoid errors.
646
579
  shellExec('sudo umount -f /var/lib/kubelet/pods/*/*');
647
580
 
648
- // Phase 3: Execute official uninstallation commands
649
- logger.info('Phase 3/7: Executing official reset and uninstallation commands...');
650
- logger.info(' -> Executing kubeadm reset...');
651
- shellExec('sudo kubeadm reset --force');
652
- logger.info(' -> Executing K3s uninstallation script if it exists...');
653
- shellExec('sudo /usr/local/bin/k3s-uninstall.sh');
654
- logger.info(' -> Deleting Kind clusters...');
655
- shellExec('kind get clusters | xargs -r -t -n1 kind delete cluster');
581
+ // Phase 3: Execute official uninstallation commands (type-specific)
582
+ const clusterType = options.clusterType || 'kind';
583
+ logger.info(
584
+ `Phase 3/7: Executing official reset/uninstallation commands for cluster type: '${clusterType}'...`,
585
+ );
586
+ if (clusterType === 'kubeadm') {
587
+ logger.info(' -> Executing kubeadm reset...');
588
+ shellExec('sudo kubeadm reset --force');
589
+ } else if (clusterType === 'k3s') {
590
+ logger.info(' -> Executing K3s uninstallation script if it exists...');
591
+ shellExec('sudo /usr/local/bin/k3s-uninstall.sh');
592
+ } else {
593
+ // Default: kind
594
+ logger.info(' -> Deleting Kind clusters...');
595
+ shellExec('kind get clusters | xargs -r -t -n1 kind delete cluster');
596
+ }
656
597
 
657
598
  // Phase 4: File system cleanup
658
599
  logger.info('Phase 4/7: Cleaning up remaining file system artifacts...');
@@ -672,9 +613,6 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
672
613
  // Remove iptables rules and CNI network interfaces.
673
614
  shellExec('sudo iptables -F');
674
615
  shellExec('sudo iptables -t nat -F');
675
- // Restore iptables rules
676
- shellExec(`chmod +x ${options.underpostRoot}/scripts/nat-iptables.sh`);
677
- shellExec(`${options.underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
678
616
  shellExec('sudo ip link del cni0');
679
617
  shellExec('sudo ip link del flannel.1');
680
618
 
@@ -772,6 +710,11 @@ EOF`);
772
710
  shellExec(`chmod +x /usr/local/bin/helm`);
773
711
  shellExec(`sudo mv /usr/local/bin/helm /bin/helm`);
774
712
  shellExec(`sudo rm -rf get_helm.sh`);
713
+
714
+ // Install snap
715
+ shellExec(`sudo yum install -y snapd`);
716
+ shellExec(`sudo systemctl enable --now snapd.socket`);
717
+
775
718
  console.log('Host prerequisites installed successfully.');
776
719
  },
777
720
 
package/src/cli/deploy.js CHANGED
@@ -491,6 +491,7 @@ spec:
491
491
  retryPerTryTimeout: '',
492
492
  kindType: '',
493
493
  port: 0,
494
+ exposePort: 0,
494
495
  cmd: '',
495
496
  },
496
497
  ) {
@@ -573,11 +574,13 @@ EOF`);
573
574
  if (options.expose === true) {
574
575
  const kindType = options.kindType ? options.kindType : 'svc';
575
576
  const svc = Underpost.deploy.get(deployId, kindType)[0];
576
- const port = options.port
577
- ? options.port
578
- : kindType !== 'svc'
579
- ? 80
580
- : parseInt(svc[`PORT(S)`].split('/TCP')[0]);
577
+ const port = options.exposePort
578
+ ? parseInt(options.exposePort)
579
+ : options.port
580
+ ? parseInt(options.port)
581
+ : kindType !== 'svc'
582
+ ? 80
583
+ : parseInt(svc[`PORT(S)`].split('/TCP')[0]);
581
584
  logger.info(deployId, {
582
585
  svc,
583
586
  port,