cyberia 2.89.2 → 2.89.45
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.
- package/.env.development +2 -0
- package/.env.production +2 -0
- package/.env.test +2 -0
- package/.github/workflows/engine-cyberia.cd.yml +4 -0
- package/.github/workflows/release.cd.yml +2 -0
- package/bin/cyberia.js +10 -7
- package/bin/deploy.js +22 -15
- package/bin/index.js +10 -7
- package/cli.md +105 -54
- package/deployment.yaml +34 -6
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +18 -6
- package/manifests/deployment/dd-test-development/proxy.yaml +2 -0
- package/manifests/deployment/kafka/deployment.yaml +0 -2
- package/manifests/deployment/spark/spark-pi-py.yaml +0 -1
- package/manifests/deployment/tensorflow/tf-gpu-test.yaml +0 -2
- package/manifests/envoy-service-nodeport.yaml +0 -1
- package/manifests/kubeadm-calico-config.yaml +10 -115
- package/manifests/letsencrypt-prod.yaml +0 -1
- package/manifests/mariadb/statefulset.yaml +1 -1
- package/manifests/mongodb/statefulset.yaml +11 -11
- package/manifests/mongodb-4.4/service-deployment.yaml +1 -3
- package/manifests/mysql/pv-pvc.yaml +1 -1
- package/manifests/mysql/statefulset.yaml +1 -1
- package/manifests/pv-pvc-dd.yaml +34 -0
- package/manifests/valkey/service.yaml +0 -1
- package/manifests/valkey/statefulset.yaml +2 -3
- package/package.json +1 -1
- package/proxy.yaml +6 -0
- package/scripts/device-scan.sh +43 -21
- package/scripts/gen-fqdns.sh +14 -0
- package/scripts/ip-info.sh +118 -0
- package/scripts/rpmfusion-ffmpeg-setup.sh +1 -0
- package/src/api/object-layer/object-layer.controller.js +19 -0
- package/src/api/object-layer/object-layer.router.js +4 -0
- package/src/api/object-layer/object-layer.service.js +111 -0
- package/src/api/user/user.model.js +10 -1
- package/src/cli/cluster.js +88 -75
- package/src/cli/deploy.js +165 -85
- package/src/cli/index.js +44 -3
- package/src/cli/monitor.js +12 -6
- package/src/cli/repository.js +13 -1
- package/src/cli/run.js +127 -60
- package/src/client/components/core/Logger.js +1 -1
- package/src/client/components/core/Modal.js +5 -0
- package/src/client/components/core/ObjectLayerEngineModal.js +336 -72
- package/src/client/components/core/ObjectLayerEngineViewer.js +239 -420
- package/src/client/components/core/Router.js +10 -1
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/ObjectLayerCyberiaPortal.js +44 -4
- package/src/client/services/default/default.management.js +25 -5
- package/src/client/services/object-layer/object-layer.management.js +8 -8
- package/src/client/services/object-layer/object-layer.service.js +34 -0
- package/src/index.js +1 -1
- package/src/server/client-build.js +5 -4
- package/src/server/conf.js +1 -1
- package/src/server/start.js +3 -1
- package/manifests/kubelet-config.yaml +0 -65
- package/manifests/mongodb/backup-access.yaml +0 -16
- package/manifests/mongodb/backup-cronjob.yaml +0 -42
- package/manifests/mongodb/backup-pv-pvc.yaml +0 -22
- package/manifests/mongodb/configmap.yaml +0 -26
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ip-info.sh
|
|
3
|
+
# Retrieve information about an IP address or hostname
|
|
4
|
+
# Usage: ip-info.sh <IP-or-hostname>
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
IFS=$'
|
|
8
|
+
'
|
|
9
|
+
|
|
10
|
+
if [[ ${#} -lt 1 ]]; then
|
|
11
|
+
echo "Usage: $0 <IP-or-hostname>"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
TARGET="$1"
|
|
15
|
+
|
|
16
|
+
# Detect package manager
|
|
17
|
+
PKG_MANAGER=""
|
|
18
|
+
if command -v dnf >/dev/null 2>&1; then
|
|
19
|
+
PKG_MANAGER=dnf
|
|
20
|
+
elif command -v yum >/dev/null 2>&1; then
|
|
21
|
+
PKG_MANAGER=yum
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Commands we need (bind-utils provides dig)
|
|
25
|
+
REQUIRED_CMDS=(curl jq whois dig traceroute)
|
|
26
|
+
PKGS=(curl jq whois bind-utils traceroute)
|
|
27
|
+
|
|
28
|
+
missing=()
|
|
29
|
+
for i in "${!REQUIRED_CMDS[@]}"; do
|
|
30
|
+
cmd=${REQUIRED_CMDS[$i]}
|
|
31
|
+
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
32
|
+
missing+=("${PKGS[$i]}")
|
|
33
|
+
fi
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
37
|
+
if [[ -z "$PKG_MANAGER" ]]; then
|
|
38
|
+
echo "Missing packages: ${missing[*]}
|
|
39
|
+
No supported package manager found. Install: ${missing[*]} and re-run."
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
if [[ $EUID -ne 0 && -z "$(command -v sudo)" ]]; then
|
|
43
|
+
echo "Missing packages: ${missing[*]}
|
|
44
|
+
Run as root or install sudo."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
echo "Installing missing: ${missing[*]}"
|
|
48
|
+
sudo $PKG_MANAGER install -y ${missing[*]} >/dev/null
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Resolve to IP(s)
|
|
52
|
+
resolve_ips(){
|
|
53
|
+
if getent ahosts "$TARGET" >/dev/null 2>&1; then
|
|
54
|
+
getent ahosts "$TARGET" | awk '{print $1}' | sort -u
|
|
55
|
+
else
|
|
56
|
+
dig +short "$TARGET" | awk '/^[0-9]/ {print $1}' | sort -u
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
mapfile -t IPS < <(resolve_ips)
|
|
61
|
+
if [[ ${#IPS[@]} -eq 0 ]]; then
|
|
62
|
+
IPS=("$TARGET")
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
format(){
|
|
66
|
+
if command -v jq >/dev/null 2>&1; then
|
|
67
|
+
jq '.' 2>/dev/null || cat
|
|
68
|
+
else
|
|
69
|
+
cat
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for IP in "${IPS[@]}"; do
|
|
74
|
+
printf "
|
|
75
|
+
===== %s (resolved: %s) =====
|
|
76
|
+
|
|
77
|
+
" "$TARGET" "$IP"
|
|
78
|
+
|
|
79
|
+
echo "-- REVERSE DNS --"
|
|
80
|
+
dig -x "$IP" +short || true
|
|
81
|
+
|
|
82
|
+
echo "
|
|
83
|
+
-- WHOIS --"
|
|
84
|
+
whois "$IP" | sed -n '1,80p' || true
|
|
85
|
+
|
|
86
|
+
echo "
|
|
87
|
+
-- IPINFO (ipinfo.io) --"
|
|
88
|
+
curl -sS "https://ipinfo.io/$IP/json" | format || true
|
|
89
|
+
|
|
90
|
+
echo "
|
|
91
|
+
-- IP-API (ip-api.com) --"
|
|
92
|
+
curl -sS "http://ip-api.com/json/$IP?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query" | format || true
|
|
93
|
+
|
|
94
|
+
echo "
|
|
95
|
+
-- IPWHOIS (ipwhois.app) --"
|
|
96
|
+
curl -sS "https://ipwhois.app/json/$IP" | format || true
|
|
97
|
+
|
|
98
|
+
echo "
|
|
99
|
+
-- GEOPLUGIN --"
|
|
100
|
+
curl -sS "http://www.geoplugin.net/json.gp?ip=$IP" | format || true
|
|
101
|
+
|
|
102
|
+
echo "
|
|
103
|
+
-- TRACEROUTE --"
|
|
104
|
+
traceroute -m 20 "$IP" || true
|
|
105
|
+
|
|
106
|
+
if [[ "$TARGET" =~ [A-Za-z] ]]; then
|
|
107
|
+
echo "
|
|
108
|
+
-- DNS RECORDS for $TARGET --"
|
|
109
|
+
dig +short A "$TARGET" || true
|
|
110
|
+
dig +short AAAA "$TARGET" || true
|
|
111
|
+
dig +short MX "$TARGET" || true
|
|
112
|
+
dig +short TXT "$TARGET" || true
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
echo "
|
|
116
|
+
===== done: $IP =====
|
|
117
|
+
"
|
|
118
|
+
done
|
|
@@ -32,6 +32,7 @@ echo "6) Try to install audio helper packages that sometimes block ffmpeg (ladsp
|
|
|
32
32
|
# These may be provided by CRB/EPEL or other compatible repos
|
|
33
33
|
dnf -y install ladspa || echo "ladspa not available from enabled repos (will try later)"
|
|
34
34
|
dnf -y install rubberband || echo "rubberband not available from enabled repos (will try later)"
|
|
35
|
+
dnf -y install libwebp-tools || echo "libwebp-tools not available from enabled repos (will try later)"
|
|
35
36
|
|
|
36
37
|
echo "7) Try installing ffmpeg (several fallbacks tried)"
|
|
37
38
|
if dnf -y install ffmpeg ffmpeg-devel --allowerasing; then
|
|
@@ -34,6 +34,25 @@ const ObjectLayerController = {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
+
generateWebp: async (req, res, options) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = await ObjectLayerService.generateWebp(req, res, options);
|
|
40
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
41
|
+
res.setHeader('Content-Type', 'image/webp');
|
|
42
|
+
res.setHeader(
|
|
43
|
+
'Content-Disposition',
|
|
44
|
+
`attachment; filename="${req.params.itemType}_${req.params.itemId}_${req.params.directionCode}.webp"`,
|
|
45
|
+
);
|
|
46
|
+
res.setHeader('Content-Length', result.length);
|
|
47
|
+
return res.status(200).end(result);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logger.error(error, error.stack);
|
|
50
|
+
return res.status(400).json({
|
|
51
|
+
status: 'error',
|
|
52
|
+
message: error.message,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
37
56
|
put: async (req, res, options) => {
|
|
38
57
|
try {
|
|
39
58
|
const result = await ObjectLayerService.put(req, res, options);
|
|
@@ -16,6 +16,10 @@ const ObjectLayerRouter = (options) => {
|
|
|
16
16
|
|
|
17
17
|
router.post(`/:id`, authMiddleware, async (req, res) => await ObjectLayerController.post(req, res, options));
|
|
18
18
|
router.post(`/`, authMiddleware, async (req, res) => await ObjectLayerController.post(req, res, options));
|
|
19
|
+
router.get(
|
|
20
|
+
`/generate-webp/:itemType/:itemId/:directionCode`,
|
|
21
|
+
async (req, res) => await ObjectLayerController.generateWebp(req, res, options),
|
|
22
|
+
);
|
|
19
23
|
router.get(`/render/:id`, authMiddleware, async (req, res) => await ObjectLayerController.get(req, res, options));
|
|
20
24
|
router.get(`/metadata/:id`, authMiddleware, async (req, res) => await ObjectLayerController.get(req, res, options));
|
|
21
25
|
router.get(
|
|
@@ -5,6 +5,7 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import { ObjectLayerDto } from './object-layer.model.js';
|
|
7
7
|
import { ObjectLayerEngine } from '../../server/object-layer.js';
|
|
8
|
+
import { shellExec } from '../../server/process.js';
|
|
8
9
|
const logger = loggerFactory(import.meta);
|
|
9
10
|
|
|
10
11
|
const ObjectLayerService = {
|
|
@@ -250,6 +251,116 @@ const ObjectLayerService = {
|
|
|
250
251
|
]);
|
|
251
252
|
return { data, total, page, totalPages: Math.ceil(total / limit) };
|
|
252
253
|
},
|
|
254
|
+
generateWebp: async (req, res, options) => {
|
|
255
|
+
/** @type {import('./object-layer.model.js').ObjectLayerModel} */
|
|
256
|
+
const ObjectLayer = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.ObjectLayer;
|
|
257
|
+
|
|
258
|
+
// GET /generate-webp/:itemType/:itemId/:directionCode - Generate webp animation from PNG frames
|
|
259
|
+
const itemType = req.params.itemType;
|
|
260
|
+
const itemId = req.params.itemId;
|
|
261
|
+
const directionCode = req.params.directionCode;
|
|
262
|
+
|
|
263
|
+
if (!itemType || !itemId || !directionCode) {
|
|
264
|
+
throw new Error('Missing required parameters: itemType, itemId, directionCode');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Path to the PNG frames directory
|
|
268
|
+
const framesFolder = `./src/client/public/cyberia/assets/${itemType}/${itemId}/${directionCode}`;
|
|
269
|
+
|
|
270
|
+
// Check if the folder exists
|
|
271
|
+
if (!fs.existsSync(framesFolder)) {
|
|
272
|
+
throw new Error(`Frames directory not found: ${framesFolder}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check if there are PNG files in the directory
|
|
276
|
+
const files = await fs.readdir(framesFolder);
|
|
277
|
+
const pngFiles = files.filter((file) => file.endsWith('.png')).sort();
|
|
278
|
+
|
|
279
|
+
if (pngFiles.length === 0) {
|
|
280
|
+
throw new Error(`No PNG frames found in directory: ${framesFolder}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
logger.info(`Found ${pngFiles.length} PNG frames in ${framesFolder}`);
|
|
284
|
+
|
|
285
|
+
// Get frame duration from the object layer metadata
|
|
286
|
+
let frameDuration = 100; // Default to 100ms
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
// Find object layer by itemType and itemId
|
|
290
|
+
const objectLayer = await ObjectLayer.findOne({
|
|
291
|
+
'data.item.type': itemType,
|
|
292
|
+
'data.item.id': itemId,
|
|
293
|
+
}).select(ObjectLayerDto.select.getMetadata());
|
|
294
|
+
|
|
295
|
+
if (objectLayer && objectLayer.data.render.frame_duration) {
|
|
296
|
+
frameDuration = objectLayer.data.render.frame_duration;
|
|
297
|
+
logger.info(`Using frame duration from object layer: ${frameDuration}ms`);
|
|
298
|
+
} else {
|
|
299
|
+
logger.warn(`Object layer not found or no frame_duration set, using default: ${frameDuration}ms`);
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
logger.warn(
|
|
303
|
+
`Error fetching object layer metadata: ${error.message}. Using default frame duration: ${frameDuration}ms`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const tmpOutputFileName = `output_${Date.now()}_${Math.floor(Math.random() * 1000)}.webp`;
|
|
308
|
+
|
|
309
|
+
// Create temporary output file path
|
|
310
|
+
const tempOutputPath = `${framesFolder}/${tmpOutputFileName}`;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
// Change to the frames directory and execute img2webp command
|
|
314
|
+
const cmd = `cd "${framesFolder}" && img2webp -d ${frameDuration} -loop 0 *.png -o ${tmpOutputFileName}`;
|
|
315
|
+
|
|
316
|
+
logger.info(`Executing command: ${cmd}`);
|
|
317
|
+
|
|
318
|
+
const result = shellExec(cmd, { silent: false });
|
|
319
|
+
|
|
320
|
+
if (result.code !== 0) {
|
|
321
|
+
throw new Error(`img2webp command failed: ${result.stderr || result.stdout}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
logger.info(`Successfully generated webp: ${tempOutputPath}`);
|
|
325
|
+
|
|
326
|
+
// Check if the output file was created
|
|
327
|
+
if (!fs.existsSync(tempOutputPath)) {
|
|
328
|
+
throw new Error(`Output file was not created: ${tempOutputPath}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Read the webp file as a buffer
|
|
332
|
+
const webpBuffer = await fs.readFile(tempOutputPath);
|
|
333
|
+
|
|
334
|
+
logger.info(`WebP file size: ${webpBuffer.length} bytes`);
|
|
335
|
+
|
|
336
|
+
// Delete the temporary file after sending the response
|
|
337
|
+
// Use setImmediate to ensure the response is fully sent before cleanup
|
|
338
|
+
setImmediate(async () => {
|
|
339
|
+
try {
|
|
340
|
+
if (fs.existsSync(tempOutputPath)) {
|
|
341
|
+
await fs.remove(tempOutputPath);
|
|
342
|
+
logger.info(`Cleaned up temporary file: ${tempOutputPath}`);
|
|
343
|
+
}
|
|
344
|
+
} catch (cleanupError) {
|
|
345
|
+
logger.error(`Error cleaning up temporary file: ${cleanupError.message}`);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return webpBuffer;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
// Clean up on error
|
|
352
|
+
try {
|
|
353
|
+
if (fs.existsSync(tempOutputPath)) {
|
|
354
|
+
await fs.remove(tempOutputPath);
|
|
355
|
+
logger.info(`Cleaned up temporary file after error: ${tempOutputPath}`);
|
|
356
|
+
}
|
|
357
|
+
} catch (cleanupError) {
|
|
358
|
+
logger.error(`Error cleaning up temporary file after error: ${cleanupError.message}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
},
|
|
253
364
|
put: async (req, res, options) => {
|
|
254
365
|
/** @type {import('./object-layer.model.js').ObjectLayerModel} */
|
|
255
366
|
const ObjectLayer = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.ObjectLayer;
|
|
@@ -73,7 +73,16 @@ const ProviderSchema = UserSchema;
|
|
|
73
73
|
const UserDto = {
|
|
74
74
|
select: {
|
|
75
75
|
get: () => {
|
|
76
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
_id: 1,
|
|
78
|
+
username: 1,
|
|
79
|
+
email: 1,
|
|
80
|
+
role: 1,
|
|
81
|
+
emailConfirmed: 1,
|
|
82
|
+
profileImageId: 1,
|
|
83
|
+
createdAt: 1,
|
|
84
|
+
updatedAt: 1,
|
|
85
|
+
};
|
|
77
86
|
},
|
|
78
87
|
getAll: () => {
|
|
79
88
|
return { _id: 1 };
|
package/src/cli/cluster.js
CHANGED
|
@@ -44,12 +44,15 @@ class UnderpostCluster {
|
|
|
44
44
|
* @param {boolean} [options.listPods=false] - List Kubernetes pods.
|
|
45
45
|
* @param {boolean} [options.reset=false] - Perform a comprehensive reset of Kubernetes and container environments.
|
|
46
46
|
* @param {boolean} [options.dev=false] - Run in development mode (adjusts paths).
|
|
47
|
-
* @param {string} [options.nsUse=''] - Set the current kubectl namespace.
|
|
47
|
+
* @param {string} [options.nsUse=''] - Set the current kubectl namespace (creates namespace if it doesn't exist).
|
|
48
|
+
* @param {string} [options.namespace='default'] - Kubernetes namespace for cluster operations.
|
|
48
49
|
* @param {boolean} [options.infoCapacity=false] - Display resource capacity information for the cluster.
|
|
49
50
|
* @param {boolean} [options.infoCapacityPod=false] - Display resource capacity information for pods.
|
|
50
51
|
* @param {boolean} [options.pullImage=false] - Pull necessary Docker images before deployment.
|
|
51
52
|
* @param {boolean} [options.dedicatedGpu=false] - Configure for dedicated GPU usage (e.g., NVIDIA GPU Operator).
|
|
52
53
|
* @param {boolean} [options.kubeadm=false] - Initialize the cluster using Kubeadm.
|
|
54
|
+
* @param {string} [options.podNetworkCidr='192.168.0.0/16'] - Custom pod network CIDR for Kubeadm cluster initialization. Defaults to '192.168.0.0/16'.
|
|
55
|
+
* @param {string} [options.controlPlaneEndpoint=''] - Custom control plane endpoint for Kubeadm cluster initialization. Defaults to '${os.hostname()}:6443'.
|
|
53
56
|
* @param {boolean} [options.k3s=false] - Initialize the cluster using K3s.
|
|
54
57
|
* @param {boolean} [options.initHost=false] - Perform initial host setup (install Docker, Podman, Kind, Kubeadm, Helm).
|
|
55
58
|
* @param {boolean} [options.grafana=false] - Initialize the cluster with a Grafana deployment.
|
|
@@ -58,6 +61,7 @@ class UnderpostCluster {
|
|
|
58
61
|
* @param {boolean} [options.config=false] - Apply general host configuration (SELinux, containerd, sysctl, firewalld).
|
|
59
62
|
* @param {boolean} [options.worker=false] - Configure as a worker node (for Kubeadm or K3s join).
|
|
60
63
|
* @param {boolean} [options.chown=false] - Set up kubectl configuration for the current user.
|
|
64
|
+
* @param {boolean} [options.removeVolumeHostPaths=false] - Remove data from host paths used by Persistent Volumes.
|
|
61
65
|
* @param {string} [options.hosts] - Set custom hosts entries.
|
|
62
66
|
* @memberof UnderpostCluster
|
|
63
67
|
*/
|
|
@@ -78,11 +82,14 @@ class UnderpostCluster {
|
|
|
78
82
|
reset: false,
|
|
79
83
|
dev: false,
|
|
80
84
|
nsUse: '',
|
|
85
|
+
namespace: 'default',
|
|
81
86
|
infoCapacity: false,
|
|
82
87
|
infoCapacityPod: false,
|
|
83
88
|
pullImage: false,
|
|
84
89
|
dedicatedGpu: false,
|
|
85
90
|
kubeadm: false,
|
|
91
|
+
podNetworkCidr: '192.168.0.0/16',
|
|
92
|
+
controlPlaneEndpoint: '',
|
|
86
93
|
k3s: false,
|
|
87
94
|
initHost: false,
|
|
88
95
|
grafana: false,
|
|
@@ -91,6 +98,7 @@ class UnderpostCluster {
|
|
|
91
98
|
config: false,
|
|
92
99
|
worker: false,
|
|
93
100
|
chown: false,
|
|
101
|
+
removeVolumeHostPaths: false,
|
|
94
102
|
hosts: '',
|
|
95
103
|
},
|
|
96
104
|
) {
|
|
@@ -114,46 +122,35 @@ class UnderpostCluster {
|
|
|
114
122
|
if (options.infoCapacity === true)
|
|
115
123
|
return logger.info('', UnderpostCluster.API.getResourcesCapacity(options.kubeadm || options.k3s)); // Adjust for k3s
|
|
116
124
|
if (options.listPods === true) return console.table(UnderpostDeploy.API.get(podName ?? undefined));
|
|
125
|
+
// Set default namespace if not specified
|
|
126
|
+
if (!options.namespace) options.namespace = 'default';
|
|
127
|
+
|
|
117
128
|
if (options.nsUse && typeof options.nsUse === 'string') {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
shellExec(`kubectl get svc --all-namespaces -o wide`);
|
|
131
|
-
shellExec(`kubectl get statefulsets --all-namespaces -o wide`);
|
|
132
|
-
shellExec(`kubectl get deployments --all-namespaces -o wide`);
|
|
133
|
-
shellExec(`kubectl get configmap --all-namespaces -o wide`);
|
|
134
|
-
shellExec(`kubectl get pods --all-namespaces -o wide`);
|
|
135
|
-
shellExec(
|
|
136
|
-
`kubectl get pod --all-namespaces -o="custom-columns=NAME:.metadata.name,INIT-CONTAINERS:.spec.initContainers[*].name,CONTAINERS:.spec.containers[*].name"`,
|
|
137
|
-
);
|
|
138
|
-
shellExec(
|
|
139
|
-
`kubectl get pods --all-namespaces -o=jsonpath='{range .items[*]}{"\\n"}{.metadata.name}{":\\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}'`,
|
|
140
|
-
);
|
|
141
|
-
shellExec(`sudo crictl images`);
|
|
142
|
-
console.log();
|
|
143
|
-
logger.info('contour -------------------------------------------------');
|
|
144
|
-
for (const _k of ['Cluster', 'HTTPProxy', 'ClusterIssuer', 'Certificate']) {
|
|
145
|
-
shellExec(`kubectl get ${_k} --all-namespaces -o wide`);
|
|
129
|
+
// Verify if namespace exists, create if not
|
|
130
|
+
const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
|
|
131
|
+
stdout: true,
|
|
132
|
+
silent: true,
|
|
133
|
+
}).trim();
|
|
134
|
+
|
|
135
|
+
if (!namespaceExists) {
|
|
136
|
+
logger.info(`Namespace '${options.nsUse}' does not exist. Creating it...`);
|
|
137
|
+
shellExec(`kubectl create namespace ${options.nsUse}`);
|
|
138
|
+
logger.info(`Namespace '${options.nsUse}' created successfully.`);
|
|
139
|
+
} else {
|
|
140
|
+
logger.info(`Namespace '${options.nsUse}' already exists.`);
|
|
146
141
|
}
|
|
147
|
-
|
|
148
|
-
shellExec(`kubectl
|
|
149
|
-
|
|
150
|
-
shellExec(`kubectl get crd --all-namespaces -o wide`);
|
|
151
|
-
shellExec(`sudo kubectl api-resources`);
|
|
142
|
+
|
|
143
|
+
shellExec(`kubectl config set-context --current --namespace=${options.nsUse}`);
|
|
144
|
+
logger.info(`Context switched to namespace: ${options.nsUse}`);
|
|
152
145
|
return;
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
// Reset Kubernetes cluster components (Kind/Kubeadm/K3s) and container runtimes
|
|
156
|
-
if (options.reset === true)
|
|
149
|
+
if (options.reset === true)
|
|
150
|
+
return await UnderpostCluster.API.safeReset({
|
|
151
|
+
underpostRoot,
|
|
152
|
+
removeVolumeHostPaths: options.removeVolumeHostPaths,
|
|
153
|
+
});
|
|
157
154
|
|
|
158
155
|
// Check if a cluster (Kind, Kubeadm, or K3s) is already initialized
|
|
159
156
|
const alreadyKubeadmCluster = UnderpostDeploy.API.get('calico-kube-controllers')[0];
|
|
@@ -224,9 +221,13 @@ class UnderpostCluster {
|
|
|
224
221
|
logger.info('K3s comes with local-path-provisioner by default. Skipping explicit installation.');
|
|
225
222
|
} else if (options.kubeadm === true) {
|
|
226
223
|
logger.info('Initializing Kubeadm control plane...');
|
|
224
|
+
// Set default values if not provided
|
|
225
|
+
const podNetworkCidr = options.podNetworkCidr || '192.168.0.0/16';
|
|
226
|
+
const controlPlaneEndpoint = options.controlPlaneEndpoint || `${os.hostname()}:6443`;
|
|
227
|
+
|
|
227
228
|
// Initialize kubeadm control plane
|
|
228
229
|
shellExec(
|
|
229
|
-
`sudo kubeadm init --pod-network-cidr
|
|
230
|
+
`sudo kubeadm init --pod-network-cidr=${podNetworkCidr} --control-plane-endpoint="${controlPlaneEndpoint}"`,
|
|
230
231
|
);
|
|
231
232
|
// Configure kubectl for the current user
|
|
232
233
|
UnderpostCluster.API.chown('kubeadm'); // Pass 'kubeadm' to chown
|
|
@@ -236,7 +237,11 @@ class UnderpostCluster {
|
|
|
236
237
|
shellExec(
|
|
237
238
|
`sudo kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/tigera-operator.yaml`,
|
|
238
239
|
);
|
|
240
|
+
shellExec(
|
|
241
|
+
`kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/custom-resources.yaml`,
|
|
242
|
+
);
|
|
239
243
|
shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/kubeadm-calico-config.yaml`);
|
|
244
|
+
|
|
240
245
|
// Untaint control plane node to allow scheduling pods
|
|
241
246
|
const nodeName = os.hostname();
|
|
242
247
|
shellExec(`kubectl taint nodes ${nodeName} node-role.kubernetes.io/control-plane:NoSchedule-`);
|
|
@@ -280,13 +285,13 @@ class UnderpostCluster {
|
|
|
280
285
|
}
|
|
281
286
|
|
|
282
287
|
if (options.grafana === true) {
|
|
283
|
-
shellExec(`kubectl delete deployment grafana --ignore-not-found`);
|
|
284
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana`);
|
|
288
|
+
shellExec(`kubectl delete deployment grafana -n ${options.namespace} --ignore-not-found`);
|
|
289
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana -n ${options.namespace}`);
|
|
285
290
|
const yaml = `${fs
|
|
286
291
|
.readFileSync(`${underpostRoot}/manifests/grafana/deployment.yaml`, 'utf8')
|
|
287
292
|
.replace('{{GF_SERVER_ROOT_URL}}', options.hosts.split(',')[0])}`;
|
|
288
293
|
console.log(yaml);
|
|
289
|
-
shellExec(`kubectl apply -f - <<EOF
|
|
294
|
+
shellExec(`kubectl apply -f - -n ${options.namespace} <<EOF
|
|
290
295
|
${yaml}
|
|
291
296
|
EOF
|
|
292
297
|
`);
|
|
@@ -305,7 +310,7 @@ EOF
|
|
|
305
310
|
.join(',')}]`,
|
|
306
311
|
)}`;
|
|
307
312
|
console.log(yaml);
|
|
308
|
-
shellExec(`kubectl apply -f - <<EOF
|
|
313
|
+
shellExec(`kubectl apply -f - -n ${options.namespace} <<EOF
|
|
309
314
|
${yaml}
|
|
310
315
|
EOF
|
|
311
316
|
`);
|
|
@@ -334,15 +339,15 @@ EOF
|
|
|
334
339
|
// For kubeadm/k3s, ensure it's available for containerd
|
|
335
340
|
shellExec(`sudo crictl pull valkey/valkey:latest`);
|
|
336
341
|
}
|
|
337
|
-
shellExec(`kubectl delete statefulset valkey-service --ignore-not-found`);
|
|
338
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey`);
|
|
342
|
+
shellExec(`kubectl delete statefulset valkey-service -n ${options.namespace} --ignore-not-found`);
|
|
343
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey -n ${options.namespace}`);
|
|
339
344
|
await UnderpostTest.API.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60);
|
|
340
345
|
}
|
|
341
346
|
if (options.full === true || options.mariadb === true) {
|
|
342
347
|
shellExec(
|
|
343
|
-
`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
|
|
348
|
+
`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}`,
|
|
344
349
|
);
|
|
345
|
-
shellExec(`kubectl delete statefulset mariadb-statefulset --ignore-not-found`);
|
|
350
|
+
shellExec(`kubectl delete statefulset mariadb-statefulset -n ${options.namespace} --ignore-not-found`);
|
|
346
351
|
|
|
347
352
|
if (options.pullImage === true) {
|
|
348
353
|
// shellExec(`sudo podman pull mariadb:latest`);
|
|
@@ -354,17 +359,17 @@ EOF
|
|
|
354
359
|
// For kubeadm/k3s, ensure it's available for containerd
|
|
355
360
|
shellExec(`sudo crictl pull mariadb:latest`);
|
|
356
361
|
}
|
|
357
|
-
shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml`);
|
|
358
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb`);
|
|
362
|
+
shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml -n ${options.namespace}`);
|
|
363
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb -n ${options.namespace}`);
|
|
359
364
|
}
|
|
360
365
|
if (options.full === true || options.mysql === true) {
|
|
361
366
|
shellExec(
|
|
362
|
-
`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
|
|
367
|
+
`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}`,
|
|
363
368
|
);
|
|
364
369
|
shellExec(`sudo mkdir -p /mnt/data`);
|
|
365
370
|
shellExec(`sudo chmod 777 /mnt/data`);
|
|
366
371
|
shellExec(`sudo chown -R root:root /mnt/data`);
|
|
367
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql`);
|
|
372
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql -n ${options.namespace}`);
|
|
368
373
|
}
|
|
369
374
|
if (options.full === true || options.postgresql === true) {
|
|
370
375
|
if (options.pullImage === true) {
|
|
@@ -377,9 +382,9 @@ EOF
|
|
|
377
382
|
shellExec(`sudo crictl pull postgres:latest`);
|
|
378
383
|
}
|
|
379
384
|
shellExec(
|
|
380
|
-
`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
|
|
385
|
+
`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}`,
|
|
381
386
|
);
|
|
382
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql`);
|
|
387
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql -n ${options.namespace}`);
|
|
383
388
|
}
|
|
384
389
|
if (options.mongodb4 === true) {
|
|
385
390
|
if (options.pullImage === true) {
|
|
@@ -391,7 +396,7 @@ EOF
|
|
|
391
396
|
// For kubeadm/k3s, ensure it's available for containerd
|
|
392
397
|
shellExec(`sudo crictl pull mongo:4.4`);
|
|
393
398
|
}
|
|
394
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4`);
|
|
399
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4 -n ${options.namespace}`);
|
|
395
400
|
|
|
396
401
|
const deploymentName = 'mongodb-deployment';
|
|
397
402
|
|
|
@@ -422,14 +427,14 @@ EOF
|
|
|
422
427
|
shellExec(`sudo crictl pull mongo:latest`);
|
|
423
428
|
}
|
|
424
429
|
shellExec(
|
|
425
|
-
`sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f
|
|
430
|
+
`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}`,
|
|
426
431
|
);
|
|
427
432
|
shellExec(
|
|
428
|
-
`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
|
|
433
|
+
`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}`,
|
|
429
434
|
);
|
|
430
|
-
shellExec(`kubectl delete statefulset mongodb --ignore-not-found`);
|
|
431
|
-
shellExec(`kubectl apply -f ${underpostRoot}/manifests/mongodb/storage-class.yaml`);
|
|
432
|
-
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb`);
|
|
435
|
+
shellExec(`kubectl delete statefulset mongodb -n ${options.namespace} --ignore-not-found`);
|
|
436
|
+
shellExec(`kubectl apply -f ${underpostRoot}/manifests/mongodb/storage-class.yaml -n ${options.namespace}`);
|
|
437
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb -n ${options.namespace}`);
|
|
433
438
|
|
|
434
439
|
const successInstance = await UnderpostTest.API.statusMonitor('mongodb-0', 'Running', 'pods', 1000, 60 * 10);
|
|
435
440
|
|
|
@@ -453,7 +458,9 @@ EOF
|
|
|
453
458
|
shellExec(`kubectl apply -f https://projectcontour.io/quickstart/contour.yaml`);
|
|
454
459
|
if (options.kubeadm === true) {
|
|
455
460
|
// Envoy service might need NodePort for kubeadm
|
|
456
|
-
shellExec(
|
|
461
|
+
shellExec(
|
|
462
|
+
`sudo kubectl apply -f ${underpostRoot}/manifests/envoy-service-nodeport.yaml -n ${options.namespace}`,
|
|
463
|
+
);
|
|
457
464
|
}
|
|
458
465
|
// K3s has a built-in LoadBalancer (Klipper-lb) that can expose services,
|
|
459
466
|
// so a specific NodePort service might not be needed or can be configured differently.
|
|
@@ -473,7 +480,7 @@ EOF
|
|
|
473
480
|
|
|
474
481
|
const letsEncName = 'letsencrypt-prod';
|
|
475
482
|
shellExec(`sudo kubectl delete ClusterIssuer ${letsEncName} --ignore-not-found`);
|
|
476
|
-
shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/${letsEncName}.yaml`);
|
|
483
|
+
shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/${letsEncName}.yaml -n ${options.namespace}`);
|
|
477
484
|
}
|
|
478
485
|
},
|
|
479
486
|
|
|
@@ -585,9 +592,10 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
|
|
|
585
592
|
* in coredns) by restoring SELinux security contexts and safely cleaning up cluster artifacts.
|
|
586
593
|
* @param {object} [options] - Configuration options for the reset.
|
|
587
594
|
* @param {string} [options.underpostRoot] - The root path of the underpost project.
|
|
595
|
+
* @param {boolean} [options.removeVolumeHostPaths=false] - Whether to remove data from host paths used by Persistent Volumes.
|
|
588
596
|
* @memberof UnderpostCluster
|
|
589
597
|
*/
|
|
590
|
-
async safeReset(options = { underpostRoot: '.' }) {
|
|
598
|
+
async safeReset(options = { underpostRoot: '.', removeVolumeHostPaths: false }) {
|
|
591
599
|
logger.info('Starting a safe and comprehensive reset of Kubernetes and container environments...');
|
|
592
600
|
|
|
593
601
|
try {
|
|
@@ -614,25 +622,30 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
|
|
|
614
622
|
// Phase 1: Clean up Persistent Volumes with hostPath
|
|
615
623
|
// This targets data created by Kubernetes Persistent Volumes that use hostPath.
|
|
616
624
|
logger.info('Phase 1/7: Cleaning Kubernetes hostPath volumes...');
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
625
|
+
if (options.removeVolumeHostPaths)
|
|
626
|
+
try {
|
|
627
|
+
const pvListJson = shellExec(`kubectl get pv -o json || echo '{"items":[]}'`, {
|
|
628
|
+
stdout: true,
|
|
629
|
+
silent: true,
|
|
630
|
+
});
|
|
631
|
+
const pvList = JSON.parse(pvListJson);
|
|
632
|
+
|
|
633
|
+
if (pvList.items && pvList.items.length > 0) {
|
|
634
|
+
for (const pv of pvList.items) {
|
|
635
|
+
// Check if the PV uses hostPath and delete its contents
|
|
636
|
+
if (pv.spec.hostPath && pv.spec.hostPath.path) {
|
|
637
|
+
const hostPath = pv.spec.hostPath.path;
|
|
638
|
+
logger.info(`Removing data from host path for PV '${pv.metadata.name}': ${hostPath}`);
|
|
639
|
+
shellExec(`sudo rm -rf ${hostPath}/* || true`);
|
|
640
|
+
}
|
|
628
641
|
}
|
|
642
|
+
} else {
|
|
643
|
+
logger.info('No Persistent Volumes found with hostPath to clean up.');
|
|
629
644
|
}
|
|
630
|
-
}
|
|
631
|
-
logger.
|
|
645
|
+
} catch (error) {
|
|
646
|
+
logger.error('Failed to clean up Persistent Volumes:', error);
|
|
632
647
|
}
|
|
633
|
-
|
|
634
|
-
logger.error('Failed to clean up Persistent Volumes:', error);
|
|
635
|
-
}
|
|
648
|
+
else logger.info(' -> Skipping hostPath volume cleanup as per configuration.');
|
|
636
649
|
// Phase 2: Restore SELinux and stop services
|
|
637
650
|
// This is critical for fixing the 'permission denied' error you experienced.
|
|
638
651
|
// Enable SELinux permissive mode and restore file contexts.
|