underpost 3.2.8 → 3.2.10

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 (92) hide show
  1. package/.github/workflows/npmpkg.ci.yml +1 -0
  2. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  3. package/.github/workflows/release.cd.yml +1 -0
  4. package/.vscode/settings.json +10 -5
  5. package/CHANGELOG.md +223 -2
  6. package/CLI-HELP.md +36 -7
  7. package/README.md +38 -9
  8. package/bin/build.js +27 -11
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +32 -13
  11. package/bin/index.js +2 -1
  12. package/bin/vs.js +1 -1
  13. package/bump.config.js +26 -0
  14. package/conf.js +20 -4
  15. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  19. package/manifests/kind-config-dev.yaml +8 -0
  20. package/manifests/mongodb/pv-pvc.yaml +44 -8
  21. package/manifests/mongodb/statefulset.yaml +55 -68
  22. package/package.json +40 -25
  23. package/scripts/k3s-node-setup.sh +30 -11
  24. package/scripts/nat-iptables.sh +103 -18
  25. package/src/api/core/core.router.js +19 -14
  26. package/src/api/core/core.service.js +5 -5
  27. package/src/api/default/default.router.js +22 -18
  28. package/src/api/default/default.service.js +5 -5
  29. package/src/api/document/document.router.js +28 -23
  30. package/src/api/document/document.service.js +100 -23
  31. package/src/api/file/file.router.js +19 -13
  32. package/src/api/file/file.service.js +9 -7
  33. package/src/api/test/test.router.js +17 -12
  34. package/src/api/types.js +24 -0
  35. package/src/api/user/guest.service.js +5 -4
  36. package/src/api/user/user.router.js +297 -288
  37. package/src/api/user/user.service.js +100 -35
  38. package/src/cli/baremetal.js +20 -11
  39. package/src/cli/cluster.js +243 -55
  40. package/src/cli/db.js +106 -62
  41. package/src/cli/deploy.js +297 -154
  42. package/src/cli/fs.js +19 -3
  43. package/src/cli/index.js +37 -9
  44. package/src/cli/ipfs.js +4 -6
  45. package/src/cli/kubectl.js +4 -1
  46. package/src/cli/lxd.js +217 -135
  47. package/src/cli/release.js +289 -131
  48. package/src/cli/repository.js +91 -34
  49. package/src/cli/run.js +297 -56
  50. package/src/cli/test.js +9 -3
  51. package/src/client/Default.index.js +9 -3
  52. package/src/client/components/core/Auth.js +19 -5
  53. package/src/client/components/core/Docs.js +6 -34
  54. package/src/client/components/core/FileExplorer.js +6 -6
  55. package/src/client/components/core/Modal.js +65 -2
  56. package/src/client/components/core/PanelForm.js +56 -52
  57. package/src/client/components/core/Recover.js +4 -4
  58. package/src/client/components/core/Worker.js +170 -350
  59. package/src/client/services/default/default.management.js +20 -25
  60. package/src/client/services/user/guest.service.js +10 -3
  61. package/src/client/sw/core.sw.js +174 -112
  62. package/src/db/DataBaseProvider.js +120 -20
  63. package/src/db/mongo/MongoBootstrap.js +587 -0
  64. package/src/db/mongo/MongooseDB.js +126 -22
  65. package/src/index.js +1 -1
  66. package/src/runtime/express/Express.js +2 -2
  67. package/src/runtime/wp/Wp.js +8 -5
  68. package/src/server/auth.js +2 -2
  69. package/src/server/client-build-docs.js +1 -1
  70. package/src/server/client-build.js +94 -129
  71. package/src/server/conf.js +20 -65
  72. package/src/server/data-query.js +32 -20
  73. package/src/server/dns.js +22 -0
  74. package/src/server/process.js +180 -19
  75. package/src/server/runtime.js +1 -1
  76. package/src/server/start.js +26 -7
  77. package/src/server/valkey.js +9 -2
  78. package/src/ws/IoInterface.js +16 -16
  79. package/src/ws/core/channels/core.ws.chat.js +11 -11
  80. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  81. package/src/ws/core/channels/core.ws.stream.js +19 -19
  82. package/src/ws/core/core.ws.connection.js +8 -8
  83. package/src/ws/core/core.ws.server.js +6 -5
  84. package/src/ws/default/channels/default.ws.main.js +10 -10
  85. package/src/ws/default/default.ws.connection.js +4 -4
  86. package/src/ws/default/default.ws.server.js +4 -3
  87. package/typedoc.json +10 -1
  88. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  89. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  90. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  91. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  92. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -7,6 +7,8 @@
7
7
  import { getNpmRootPath } from '../server/conf.js';
8
8
  import { loggerFactory } from '../server/logger.js';
9
9
  import { shellExec } from '../server/process.js';
10
+ import { MONGODB_DEFAULT_REPLICA_COUNT } from '../db/mongo/MongooseDB.js';
11
+ import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
10
12
  import os from 'os';
11
13
  import fs from 'fs-extra';
12
14
  import Underpost from '../index.js';
@@ -22,6 +24,7 @@ const logger = loggerFactory(import.meta);
22
24
  */
23
25
  class UnderpostCluster {
24
26
  static API = {
27
+
25
28
  /**
26
29
  * @method init
27
30
  * @description Initializes and configures the Kubernetes cluster based on provided options.
@@ -41,6 +44,7 @@ class UnderpostCluster {
41
44
  * @param {boolean} [options.certManager=false] - Deploy Cert-Manager for certificate management.
42
45
  * @param {boolean} [options.listPods=false] - List Kubernetes pods.
43
46
  * @param {boolean} [options.reset=false] - Perform a comprehensive reset of Kubernetes and container environments.
47
+ * @param {boolean} [options.resetMongodb=false] - Perform a targeted reset of MongoDB components without restarting the entire cluster.
44
48
  * @param {boolean} [options.dev=false] - Run in development mode (adjusts paths).
45
49
  * @param {string} [options.nsUse=''] - Set the current kubectl namespace (creates namespace if it doesn't exist).
46
50
  * @param {string} [options.namespace='default'] - Kubernetes namespace for cluster operations.
@@ -78,6 +82,7 @@ class UnderpostCluster {
78
82
  certManager: false,
79
83
  listPods: false,
80
84
  reset: false,
85
+ resetMongodb: false,
81
86
  dev: false,
82
87
  nsUse: '',
83
88
  namespace: 'default',
@@ -120,6 +125,7 @@ class UnderpostCluster {
120
125
  const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
121
126
  stdout: true,
122
127
  silent: true,
128
+ silentOnError: true,
123
129
  }).trim();
124
130
 
125
131
  if (!namespaceExists) {
@@ -145,6 +151,16 @@ class UnderpostCluster {
145
151
  });
146
152
  }
147
153
 
154
+ // Targeted MongoDB-only reset (does not restart the whole node)
155
+ if (options.resetMongodb) {
156
+ const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
157
+ return await MongoBootstrap.reset({
158
+ namespace: options.namespace,
159
+ clusterType,
160
+ underpostRoot,
161
+ });
162
+ }
163
+
148
164
  // Check if a cluster (Kind, Kubeadm, or K3s) is already initialized
149
165
  const alreadyKubeadmCluster = Underpost.kubectl.get('calico-kube-controllers')[0];
150
166
  const alreadyKindCluster = Underpost.kubectl.get('kube-apiserver-kind-control-plane')[0];
@@ -172,9 +188,19 @@ class UnderpostCluster {
172
188
  const podNetworkCidr = options.podNetworkCidr || '192.168.0.0/16';
173
189
  const controlPlaneEndpoint = options.controlPlaneEndpoint || `${os.hostname()}:6443`;
174
190
 
175
- // Initialize kubeadm control plane
191
+ // Initialize kubeadm control plane.
192
+ // Use CRI-O socket when available, otherwise fall back to containerd.
193
+ const crioSocket = 'unix:///var/run/crio/crio.sock';
194
+ const containerdSocket = 'unix:///run/containerd/containerd.sock';
195
+ const criSocket =
196
+ shellExec(`test -S /var/run/crio/crio.sock && echo crio || echo containerd`, {
197
+ stdout: true,
198
+ silent: true,
199
+ }).trim() === 'crio'
200
+ ? crioSocket
201
+ : containerdSocket;
176
202
  shellExec(
177
- `sudo kubeadm init --pod-network-cidr=${podNetworkCidr} --control-plane-endpoint="${controlPlaneEndpoint}"`,
203
+ `sudo kubeadm init --pod-network-cidr=${podNetworkCidr} --control-plane-endpoint="${controlPlaneEndpoint}" --cri-socket=${criSocket}`,
178
204
  );
179
205
  // Configure kubectl for the current user
180
206
  Underpost.cluster.chown('kubeadm'); // Pass 'kubeadm' to chown
@@ -198,15 +224,40 @@ class UnderpostCluster {
198
224
  `kubectl apply -f https://cdn.jsdelivr.net/gh/rancher/local-path-provisioner@master/deploy/local-path-storage.yaml`,
199
225
  );
200
226
  } else {
201
- // Kind cluster initialization (if not using kubeadm or k3s)
227
+ // Kind cluster initialization (default for development)
202
228
  logger.info('Initializing Kind cluster...');
203
- shellExec(
204
- `cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
205
- options.dev ? '-dev' : ''
206
- }.yaml`,
207
- );
229
+ const devReplicaCount = Math.max(Number(options.replicas) || MONGODB_DEFAULT_REPLICA_COUNT, 3);
230
+ shellExec(`sudo mkdir -p /data/mongodb`);
231
+ for (let index = 0; index < devReplicaCount; index++) {
232
+ shellExec(`sudo mkdir -p /data/mongodb/v${index}`);
233
+ }
234
+ const kindCreateCmd = `cd ${underpostRoot}/manifests && kind create cluster --config kind-config-dev.yaml`;
235
+ try {
236
+ shellExec(kindCreateCmd);
237
+ } catch (error) {
238
+ const kindCreateErrText = `${error?.message || ''}\n${error?.stderr || ''}`;
239
+ if (kindCreateErrText.includes('all predefined address pools have been fully subnetted')) {
240
+ logger.warn('Docker address pool exhausted while creating Kind cluster. Running cleanup and retrying once...');
241
+ Underpost.cluster.recoverKindDockerNetworks();
242
+ try {
243
+ shellExec(kindCreateCmd);
244
+ } catch (retryError) {
245
+ const retryErrText = `${retryError?.message || ''}\n${retryError?.stderr || ''}`;
246
+ if (retryErrText.includes('all predefined address pools have been fully subnetted')) {
247
+ logger.warn('Kind retry still failed from pool exhaustion. Applying Docker daemon address-pool config and retrying once more...');
248
+ Underpost.cluster.ensureDockerDefaultAddressPools();
249
+ shellExec(kindCreateCmd);
250
+ } else {
251
+ throw retryError;
252
+ }
253
+ }
254
+ } else {
255
+ throw error;
256
+ }
257
+ }
208
258
  Underpost.cluster.chown('kind'); // Pass 'kind' to chown
209
259
  }
260
+ Underpost.cluster.natSetup({ underpostRoot });
210
261
  }
211
262
 
212
263
  // --- Optional Component Deployments (Databases, Ingress, Cert-Manager) ---
@@ -307,33 +358,16 @@ EOF
307
358
  );
308
359
  }
309
360
  } else if (options.mongodb) {
310
- if (options.pullImage) Underpost.cluster.pullImage('mongo:latest', options);
311
- shellExec(
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}`,
313
- );
314
- shellExec(
315
- `sudo kubectl create secret generic mongodb-secret --from-file=username=/home/dd/engine/engine-private/mongodb-username --from-file=password=/home/dd/engine/engine-private/mongodb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
316
- );
317
- shellExec(`kubectl delete statefulset mongodb -n ${options.namespace} --ignore-not-found`);
318
- shellExec(`kubectl apply -f ${underpostRoot}/manifests/mongodb/storage-class.yaml -n ${options.namespace}`);
319
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb -n ${options.namespace}`);
320
-
321
- const successInstance = await Underpost.test.statusMonitor('mongodb-0', 'Running', 'pods', 1000, 60 * 10);
322
-
323
- if (successInstance) {
324
- if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-0.mongodb-service';
325
- const mongoConfig = {
326
- _id: 'rs0',
327
- members: options.mongoDbHost.split(',').map((host, index) => ({ _id: index, host: `${host}:27017` })),
328
- };
329
-
330
- shellExec(
331
- `sudo kubectl exec -i mongodb-0 -- mongosh --quiet --json=relaxed \
332
- --eval 'use admin' \
333
- --eval 'rs.initiate(${JSON.stringify(mongoConfig)})' \
334
- --eval 'rs.status()'`,
335
- );
336
- }
361
+ const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
362
+ await MongoBootstrap.initReplicaSet({
363
+ namespace: options.namespace,
364
+ replicaCount: Number(options.replicas) || MONGODB_DEFAULT_REPLICA_COUNT,
365
+ mongoDbHost: options.mongoDbHost || '',
366
+ pullImage: options.pullImage,
367
+ reset: options.reset,
368
+ clusterType,
369
+ underpostRoot,
370
+ });
337
371
  }
338
372
 
339
373
  if (options.contour) {
@@ -389,8 +423,17 @@ EOF
389
423
  );
390
424
  shellExec(`rm -f ${tarPath}`);
391
425
  } else if (options.kubeadm || options.k3s) {
392
- // Kubeadm / K3s: use crictl to pull directly into containerd
393
- shellExec(`sudo crictl pull ${image}`);
426
+ // Kubeadm / K3s: use crictl to pull directly into the active CRI runtime.
427
+ // crictl is not in sudo's secure_path; pass full PATH through env.
428
+ // Point crictl at CRI-O when the socket exists, otherwise fall back to containerd.
429
+ const criSock =
430
+ shellExec(`test -S /var/run/crio/crio.sock && echo crio || echo containerd`, {
431
+ stdout: true,
432
+ silent: true,
433
+ }).trim() === 'crio'
434
+ ? 'unix:///var/run/crio/crio.sock'
435
+ : 'unix:///run/containerd/containerd.sock';
436
+ shellExec(`sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl --runtime-endpoint ${criSock} pull ${image}`);
394
437
  }
395
438
  },
396
439
 
@@ -400,7 +443,8 @@ EOF
400
443
  * This method ensures proper SELinux, Docker, Containerd, and Sysctl settings
401
444
  * are applied for a healthy Kubernetes environment. It explicitly avoids
402
445
  * iptables flushing commands to prevent conflicts with Kubernetes' own network management.
403
- * @param {string} underpostRoot - The root directory of the underpost project.
446
+ * @param {object} [options] - Configuration options for host setup.
447
+ * @param {string} [options.underpostRoot] - The root path of the underpost project, used for locating scripts if needed.
404
448
  * @memberof UnderpostCluster
405
449
  */
406
450
  config(options = { underpostRoot: '.' }) {
@@ -432,6 +476,27 @@ EOF
432
476
  // Reload systemd daemon to pick up new unit files/changes
433
477
  shellExec(`sudo systemctl daemon-reload`);
434
478
 
479
+ // Increase inotify limits
480
+ shellExec(`sudo sysctl -w fs.inotify.max_user_watches=2099999999`);
481
+ shellExec(`sudo sysctl -w fs.inotify.max_user_instances=2099999999`);
482
+ shellExec(`sudo sysctl -w fs.inotify.max_queued_events=2099999999`);
483
+
484
+ },
485
+
486
+ /**
487
+ * @method natSetup
488
+ * @description Configures NAT and iptables settings for Kubernetes networking.
489
+ * This method enables necessary sysctl settings for bridge networking and applies iptables rules
490
+ * required for Kubernetes cluster communication. It is designed to work with kubeadm and k3s clusters, ensuring that
491
+ * traffic through Linux bridges is processed by iptables, which is crucial for CNI plugins to function correctly.
492
+ * The method also applies NAT iptables rules and configures firewalld for Kubernetes, which is required for multi-machine kubeadm inter-node communication.
493
+ * Note: This method should be called after the cluster is initialized and before deploying any workloads that require network communication.
494
+ * @param {object} [options] - Configuration options for NAT setup.
495
+ * @param {string} [options.underpostRoot] - The root path of the underpost project, used to locate the nat-iptables.sh script.
496
+ * @memberof UnderpostCluster
497
+ */
498
+ natSetup(options = { underpostRoot: '.' }) {
499
+ const { underpostRoot } = options;
435
500
  // Enable bridge-nf-call-iptables for Kubernetes networking
436
501
  // This ensures traffic through Linux bridges is processed by iptables (crucial for CNI)
437
502
  for (const iptableConfPath of [
@@ -446,19 +511,12 @@ net.bridge.bridge-nf-call-arptables = 1
446
511
  net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
447
512
  { silent: true },
448
513
  );
449
-
450
- // Increase inotify limits
451
- shellExec(`sudo sysctl -w fs.inotify.max_user_watches=2099999999`);
452
- shellExec(`sudo sysctl -w fs.inotify.max_user_instances=2099999999`);
453
- shellExec(`sudo sysctl -w fs.inotify.max_queued_events=2099999999`);
454
-
455
514
  // shellExec(`sudo sysctl --system`); // Apply sysctl changes immediately
456
- // Apply NAT iptables rules.
515
+ // Apply NAT iptables rules and configure firewalld for Kubernetes.
516
+ // nat-iptables.sh enables firewalld and opens all required ports; do NOT stop it
517
+ // afterwards — keeping firewalld running with these rules is required for
518
+ // multi-machine kubeadm inter-node communication.
457
519
  shellExec(`${underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
458
-
459
- // Disable firewalld (common cause of network issues in Kubernetes)
460
- shellExec(`sudo systemctl stop firewalld`); // Stop if running
461
- shellExec(`sudo systemctl disable firewalld`); // Disable from starting on boot
462
520
  },
463
521
 
464
522
  /**
@@ -535,6 +593,10 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
535
593
  // Phase 1: Clean up Persistent Volumes with hostPath
536
594
  // This targets data created by Kubernetes Persistent Volumes that use hostPath.
537
595
  logger.info('Phase 1/7: Cleaning Kubernetes hostPath volumes...');
596
+ if ((options.clusterType || 'kind') === 'kind') {
597
+ logger.info(' -> Kind detected: cleaning node-local MongoDB hostPath directories...');
598
+ Underpost.cluster.cleanKindMongoHostPaths({ basePath: '/data/mongodb', replicaCount: 3 });
599
+ }
538
600
  if (options.removeVolumeHostPaths)
539
601
  try {
540
602
  const pvListJson = shellExec(`kubectl get pv -o json || echo '{"items":[]}'`, {
@@ -575,8 +637,11 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
575
637
  shellExec('sudo systemctl stop kubelet');
576
638
  shellExec('sudo systemctl stop docker');
577
639
  shellExec('sudo systemctl stop podman');
578
- // Safely unmount pod filesystems to avoid errors.
579
- shellExec('sudo umount -f /var/lib/kubelet/pods/*/*');
640
+ // Lazy-unmount all kubelet pod mounts to avoid 'Device or resource busy' on rm.
641
+ shellExec(
642
+ `sudo sh -c 'findmnt --raw --noheadings -o TARGET | grep /var/lib/kubelet | sort -r | xargs -r umount -l'`,
643
+ { silentOnError: true },
644
+ );
580
645
 
581
646
  // Phase 3: Execute official uninstallation commands (type-specific)
582
647
  const clusterType = options.clusterType || 'kind';
@@ -584,6 +649,14 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
584
649
  `Phase 3/7: Executing official reset/uninstallation commands for cluster type: '${clusterType}'...`,
585
650
  );
586
651
  if (clusterType === 'kubeadm') {
652
+ // Kill control plane processes that hold ports (6443, 10257, 10259, 2379, 2380)
653
+ // so the next `kubeadm init` does not fail with [ERROR Port-xxxx].
654
+ logger.info(' -> Stopping and killing control plane containers and processes...');
655
+ shellExec('sudo crictl rm -a -f', { silentOnError: true });
656
+ shellExec('sudo crictl rmp -a -f', { silentOnError: true });
657
+ shellExec('sudo systemctl stop etcd', { silentOnError: true });
658
+ for (const port of [6443, 10259, 10257, 2379, 2380])
659
+ shellExec(`sudo fuser -k ${port}/tcp`, { silentOnError: true });
587
660
  logger.info(' -> Executing kubeadm reset...');
588
661
  shellExec('sudo kubeadm reset --force');
589
662
  } else if (clusterType === 'k3s') {
@@ -592,7 +665,13 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
592
665
  } else {
593
666
  // Default: kind
594
667
  logger.info(' -> Deleting Kind clusters...');
595
- shellExec('kind get clusters | xargs -r -t -n1 kind delete cluster');
668
+ shellExec(`clusters=$(kind get clusters)
669
+ if [ -n "$clusters" ]; then
670
+ for c in $clusters; do
671
+ echo "Deleting cluster: $c"
672
+ kind delete cluster --name "$c"
673
+ done
674
+ fi`);
596
675
  }
597
676
 
598
677
  // Phase 4: File system cleanup
@@ -600,7 +679,13 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
600
679
  // Remove any leftover configurations and data.
601
680
  shellExec('sudo rm -rf /etc/kubernetes/*');
602
681
  shellExec('sudo rm -rf /etc/cni/net.d/*');
682
+ // Second-pass lazy umount before rm to clear any remaining busy mounts.
683
+ shellExec(
684
+ `sudo sh -c 'findmnt --raw --noheadings -o TARGET | grep /var/lib/kubelet | sort -r | xargs -r umount -l'`,
685
+ { silentOnError: true },
686
+ );
603
687
  shellExec('sudo rm -rf /var/lib/kubelet/*');
688
+ shellExec('sudo rm -rf /var/lib/etcd');
604
689
  shellExec('sudo rm -rf /var/lib/cni/*');
605
690
  shellExec('sudo rm -rf /var/lib/docker/*');
606
691
  shellExec('sudo rm -rf /var/lib/containerd/*');
@@ -613,11 +698,14 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
613
698
  // Remove iptables rules and CNI network interfaces.
614
699
  shellExec('sudo iptables -F');
615
700
  shellExec('sudo iptables -t nat -F');
616
- shellExec('sudo ip link del cni0');
617
- shellExec('sudo ip link del flannel.1');
701
+ shellExec('sudo ip link del cni0', { silentOnError: true });
702
+ shellExec('sudo ip link del flannel.1', { silentOnError: true });
703
+ shellExec('sudo ip link del vxlan.calico', { silentOnError: true });
704
+ shellExec('sudo ip link del tunl0', { silentOnError: true });
618
705
 
619
706
  logger.info('Phase 6/7: Clean up images');
620
- shellExec(`podman rmi $(podman images -qa) --force`);
707
+ shellExec('sudo podman rmi --all --force', { silentOnError: true });
708
+ shellExec('sudo crictl rmi --prune', { silentOnError: true });
621
709
 
622
710
  // Phase 6: Reload daemon and finalize
623
711
  logger.info('Phase 7/7: Reloading the system daemon and finalizing...');
@@ -687,6 +775,9 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
687
775
  // Install Podman
688
776
  shellExec(`sudo dnf -y install podman`);
689
777
 
778
+ // Install CRI-O (required for kubeadm with CRI-O socket)
779
+ shellExec(`node bin run install-crio`);
780
+
690
781
  // Install Kind (Kubernetes in Docker)
691
782
  shellExec(`[ $(uname -m) = ${archData.name} ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.29.0/kind-linux-${archData.alias}
692
783
  chmod +x ./kind
@@ -744,6 +835,14 @@ EOF`);
744
835
  console.log('Removing Podman...');
745
836
  shellExec(`sudo dnf -y remove podman`);
746
837
 
838
+ // Remove CRI-O
839
+ console.log('Removing CRI-O...');
840
+ shellExec('sudo systemctl stop crio', { silentOnError: true });
841
+ shellExec('sudo systemctl disable crio', { silentOnError: true });
842
+ shellExec(`sudo dnf -y remove cri-o`);
843
+ shellExec(`sudo rm -f /etc/yum.repos.d/cri-o.repo`);
844
+ shellExec(`sudo rm -f /etc/crictl.yaml`);
845
+
747
846
  // Remove Kubeadm, Kubelet, and Kubectl
748
847
  console.log('Removing Kubernetes tools...');
749
848
  shellExec(`sudo yum remove -y kubelet kubeadm kubectl`);
@@ -780,6 +879,95 @@ EOF`);
780
879
 
781
880
  console.log('Uninstall process completed.');
782
881
  },
882
+
883
+ /**
884
+ * @method cleanKindMongoHostPaths
885
+ * @description Best-effort cleanup of MongoDB hostPath directories inside Kind node containers.
886
+ * This prevents stale replica/auth state when hostPath data lives in node-local container filesystems.
887
+ * @param {object} [options]
888
+ * @param {string} [options.basePath='/data/mongodb'] - Node-internal base path for MongoDB data.
889
+ * @param {number} [options.replicaCount=3] - Number of replica ordinal directories (v0..vN-1).
890
+ * @memberof UnderpostCluster
891
+ */
892
+ cleanKindMongoHostPaths(options = { basePath: '/data/mongodb', replicaCount: 3 }) {
893
+ const basePath = options.basePath || '/data/mongodb';
894
+ const replicaCount = Math.max(Number(options.replicaCount) || 3, 1);
895
+ const nodesRaw = shellExec('kind get nodes', {
896
+ stdout: true,
897
+ silent: true,
898
+ silentOnError: true,
899
+ });
900
+ const nodes = nodesRaw
901
+ .split('\n')
902
+ .map((node) => node.trim())
903
+ .filter((node) => !!node);
904
+
905
+ if (nodes.length === 0) {
906
+ logger.info('No Kind nodes detected for node-local MongoDB hostPath cleanup.');
907
+ return;
908
+ }
909
+
910
+ for (const node of nodes) {
911
+ logger.info(
912
+ `Cleaning Kind node-local MongoDB paths '${basePath}/v0..v${replicaCount - 1}' on node '${node}'...`,
913
+ );
914
+ const prepareReplicaDirsCmd = Array.from(
915
+ { length: replicaCount },
916
+ (_, index) => `mkdir -p ${basePath}/v${index}; rm -rf ${basePath}/v${index}/*;`,
917
+ ).join(' ');
918
+ const verifyReplicaDirsCmd = Array.from(
919
+ { length: replicaCount },
920
+ (_, index) => `test -d ${basePath}/v${index};`,
921
+ ).join(' ');
922
+ shellExec(
923
+ `sudo docker exec ${node} sh -lc 'mkdir -p ${basePath}; ${prepareReplicaDirsCmd}'`,
924
+ { silentOnError: true },
925
+ );
926
+ shellExec(`sudo docker exec ${node} sh -lc '${verifyReplicaDirsCmd}'`);
927
+ }
928
+ },
929
+
930
+ /**
931
+ * @method recoverKindDockerNetworks
932
+ * @description Best-effort cleanup of stale Kind Docker resources when Docker bridge
933
+ * address pools are exhausted and new networks cannot be allocated.
934
+ * @memberof UnderpostCluster
935
+ */
936
+ recoverKindDockerNetworks() {
937
+ logger.warn('Attempting Docker network recovery for Kind (address pool exhaustion detected)...');
938
+ shellExec(`sudo docker ps -aq --filter label=io.x-k8s.kind.cluster | xargs -r sudo docker rm -f`, {
939
+ silentOnError: true,
940
+ });
941
+ shellExec(`sudo docker network ls -q --filter label=io.x-k8s.kind.cluster | xargs -r sudo docker network rm`, {
942
+ silentOnError: true,
943
+ });
944
+ shellExec(`sudo docker network rm kind`, { silentOnError: true });
945
+ shellExec(`sudo docker network prune -f`, { silentOnError: true });
946
+ },
947
+
948
+ /**
949
+ * @method ensureDockerDefaultAddressPools
950
+ * @description Writes a sane Docker default-address-pools config to reduce
951
+ * Kind network allocation failures on hosts with exhausted predefined pools.
952
+ * @memberof UnderpostCluster
953
+ */
954
+ ensureDockerDefaultAddressPools() {
955
+ logger.warn('Applying Docker default-address-pools workaround for Kind network creation...');
956
+ shellExec(`cat <<'EOF' | sudo tee /etc/docker/daemon.json
957
+ {
958
+ "default-address-pools": [
959
+ {"base": "172.17.0.0/16", "size": 24},
960
+ {"base": "172.18.0.0/16", "size": 24},
961
+ {"base": "172.19.0.0/16", "size": 24},
962
+ {"base": "172.20.0.0/14", "size": 24},
963
+ {"base": "172.24.0.0/14", "size": 24}
964
+ ]
965
+ }
966
+ EOF`);
967
+ shellExec('sudo systemctl restart docker');
968
+ shellExec('sudo docker network prune -f', { silentOnError: true });
969
+ },
970
+
783
971
  };
784
972
  }
785
973
  export default UnderpostCluster;