@gazzehamine/armada-watch-agent 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collector.js +93 -2
- package/dist/index.js +26 -0
- package/package.json +2 -2
package/dist/collector.js
CHANGED
|
@@ -7,8 +7,13 @@ exports.getSystemInfo = getSystemInfo;
|
|
|
7
7
|
exports.collectMetrics = collectMetrics;
|
|
8
8
|
exports.collectProcesses = collectProcesses;
|
|
9
9
|
exports.collectDockerContainers = collectDockerContainers;
|
|
10
|
+
exports.collectSSLCertificates = collectSSLCertificates;
|
|
10
11
|
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
11
12
|
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const util_1 = require("util");
|
|
16
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
12
17
|
let lastNetworkStats = null;
|
|
13
18
|
let lastDiskStats = null;
|
|
14
19
|
async function getSystemInfo() {
|
|
@@ -136,9 +141,16 @@ async function collectMetrics() {
|
|
|
136
141
|
}
|
|
137
142
|
async function collectProcesses() {
|
|
138
143
|
const processes = await systeminformation_1.default.processes();
|
|
144
|
+
// Sort by CPU first, then by memory as tiebreaker
|
|
145
|
+
// Don't filter by cpu > 0 since CPU usage fluctuates rapidly
|
|
139
146
|
return processes.list
|
|
140
|
-
.
|
|
141
|
-
|
|
147
|
+
.sort((a, b) => {
|
|
148
|
+
// Sort by CPU descending, then by memory descending
|
|
149
|
+
if (b.cpu !== a.cpu) {
|
|
150
|
+
return b.cpu - a.cpu;
|
|
151
|
+
}
|
|
152
|
+
return b.mem - a.mem;
|
|
153
|
+
})
|
|
142
154
|
.slice(0, 20)
|
|
143
155
|
.map((p) => ({
|
|
144
156
|
pid: p.pid,
|
|
@@ -207,3 +219,82 @@ async function collectDockerContainers() {
|
|
|
207
219
|
return [];
|
|
208
220
|
}
|
|
209
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Get SSL certificate information from a PEM file
|
|
224
|
+
*/
|
|
225
|
+
async function getCertificateInfo(certPath, domain) {
|
|
226
|
+
try {
|
|
227
|
+
// Read the certificate file
|
|
228
|
+
const certPem = await fs_1.default.promises.readFile(certPath, 'utf8');
|
|
229
|
+
// Use openssl command to parse the certificate
|
|
230
|
+
const { stdout } = await execAsync(`openssl x509 -in "${certPath}" -noout -dates -issuer -serial -text`);
|
|
231
|
+
// Parse the output
|
|
232
|
+
const notBeforeMatch = stdout.match(/notBefore=(.+)/);
|
|
233
|
+
const notAfterMatch = stdout.match(/notAfter=(.+)/);
|
|
234
|
+
const issuerMatch = stdout.match(/issuer=(.+)/);
|
|
235
|
+
const serialMatch = stdout.match(/serial=(.+)/);
|
|
236
|
+
const algMatch = stdout.match(/Signature Algorithm: (.+)/);
|
|
237
|
+
if (!notBeforeMatch || !notAfterMatch) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const validFrom = new Date(notBeforeMatch[1]);
|
|
241
|
+
const validTo = new Date(notAfterMatch[1]);
|
|
242
|
+
const now = new Date();
|
|
243
|
+
const daysUntilExpiry = Math.floor((validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
244
|
+
const isExpired = daysUntilExpiry < 0;
|
|
245
|
+
const isExpiringSoon = daysUntilExpiry <= 30 && daysUntilExpiry >= 0;
|
|
246
|
+
const isValid = !isExpired && validFrom <= now;
|
|
247
|
+
return {
|
|
248
|
+
domain,
|
|
249
|
+
issuer: issuerMatch ? issuerMatch[1].trim() : 'Unknown',
|
|
250
|
+
validFrom,
|
|
251
|
+
validTo,
|
|
252
|
+
daysUntilExpiry,
|
|
253
|
+
isValid,
|
|
254
|
+
isExpired,
|
|
255
|
+
isExpiringSoon,
|
|
256
|
+
serialNumber: serialMatch ? serialMatch[1].trim() : 'Unknown',
|
|
257
|
+
algorithm: algMatch ? algMatch[1].trim() : 'Unknown',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error(`Error reading certificate for ${domain}:`, error);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Collect SSL certificate information from Certbot certificates
|
|
267
|
+
*/
|
|
268
|
+
async function collectSSLCertificates() {
|
|
269
|
+
const certificates = [];
|
|
270
|
+
const certbotLivePath = '/etc/letsencrypt/live';
|
|
271
|
+
try {
|
|
272
|
+
// Check if certbot directory exists
|
|
273
|
+
if (!fs_1.default.existsSync(certbotLivePath)) {
|
|
274
|
+
return certificates;
|
|
275
|
+
}
|
|
276
|
+
// Read all domain directories
|
|
277
|
+
const domains = await fs_1.default.promises.readdir(certbotLivePath);
|
|
278
|
+
for (const domain of domains) {
|
|
279
|
+
// Skip README file
|
|
280
|
+
if (domain === 'README')
|
|
281
|
+
continue;
|
|
282
|
+
const certPath = `${certbotLivePath}/${domain}/fullchain.pem`;
|
|
283
|
+
// Check if certificate file exists
|
|
284
|
+
if (!fs_1.default.existsSync(certPath)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const certInfo = await getCertificateInfo(certPath, domain);
|
|
288
|
+
if (certInfo) {
|
|
289
|
+
certificates.push(certInfo);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Sort by days until expiry (most urgent first)
|
|
293
|
+
return certificates.sort((a, b) => a.daysUntilExpiry - b.daysUntilExpiry);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
// Permission error or certbot not installed
|
|
297
|
+
console.error('Error collecting SSL certificates:', error);
|
|
298
|
+
return certificates;
|
|
299
|
+
}
|
|
300
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -8,14 +8,27 @@ const dotenv_1 = __importDefault(require("dotenv"));
|
|
|
8
8
|
const axios_1 = __importDefault(require("axios"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const collector_1 = require("./collector");
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const path_1 = require("path");
|
|
11
13
|
// Load environment variables
|
|
12
14
|
dotenv_1.default.config();
|
|
15
|
+
// Get agent version from package.json
|
|
16
|
+
let AGENT_VERSION = "unknown";
|
|
17
|
+
try {
|
|
18
|
+
const packageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
19
|
+
AGENT_VERSION = packageJson.version;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.warn("Could not read agent version from package.json");
|
|
23
|
+
}
|
|
13
24
|
const SERVER_URL = process.env.SERVER_URL || "http://localhost:4000";
|
|
14
25
|
const INSTANCE_NAME = process.env.INSTANCE_NAME || os_1.default.hostname();
|
|
15
26
|
const REGION = process.env.REGION || "unknown";
|
|
16
27
|
const INSTANCE_ID = process.env.INSTANCE_ID || os_1.default.hostname();
|
|
17
28
|
const COLLECTION_INTERVAL = parseInt(process.env.COLLECTION_INTERVAL || "10") * 1000;
|
|
18
29
|
let instanceInfo = null;
|
|
30
|
+
let sslCheckCounter = 0;
|
|
31
|
+
const SSL_CHECK_FREQUENCY = 30; // Check SSL every 30 metric collections (e.g., every 5 minutes if collection is 10s)
|
|
19
32
|
async function initializeAgent() {
|
|
20
33
|
try {
|
|
21
34
|
console.log("🔄 Initializing Armada Watch Agent...");
|
|
@@ -35,8 +48,10 @@ async function initializeAgent() {
|
|
|
35
48
|
hostname: sysInfo.hostname,
|
|
36
49
|
cpuModel: sysInfo.cpuModel,
|
|
37
50
|
cpuCores: sysInfo.cpuCores,
|
|
51
|
+
agentVersion: AGENT_VERSION,
|
|
38
52
|
};
|
|
39
53
|
console.log("✅ Agent initialized successfully");
|
|
54
|
+
console.log(`📦 Agent version: ${AGENT_VERSION}`);
|
|
40
55
|
console.log(`🔁 Collection interval: ${COLLECTION_INTERVAL / 1000}s`);
|
|
41
56
|
}
|
|
42
57
|
catch (error) {
|
|
@@ -49,6 +64,16 @@ async function sendMetrics() {
|
|
|
49
64
|
const metrics = await (0, collector_1.collectMetrics)();
|
|
50
65
|
const processes = await (0, collector_1.collectProcesses)();
|
|
51
66
|
const dockerContainers = await (0, collector_1.collectDockerContainers)();
|
|
67
|
+
// Check SSL certificates less frequently (every 5 minutes by default)
|
|
68
|
+
let sslCertificates = [];
|
|
69
|
+
sslCheckCounter++;
|
|
70
|
+
if (sslCheckCounter >= SSL_CHECK_FREQUENCY) {
|
|
71
|
+
sslCertificates = await (0, collector_1.collectSSLCertificates)();
|
|
72
|
+
sslCheckCounter = 0;
|
|
73
|
+
if (sslCertificates.length > 0) {
|
|
74
|
+
console.log(`🔐 SSL Certificates checked: ${sslCertificates.length} domain(s)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
52
77
|
const payload = {
|
|
53
78
|
instanceInfo,
|
|
54
79
|
metrics: {
|
|
@@ -57,6 +82,7 @@ async function sendMetrics() {
|
|
|
57
82
|
},
|
|
58
83
|
processes,
|
|
59
84
|
dockerContainers,
|
|
85
|
+
sslCertificates: sslCertificates.length > 0 ? sslCertificates : undefined,
|
|
60
86
|
};
|
|
61
87
|
await axios_1.default.post(`${SERVER_URL}/api/metrics`, payload, {
|
|
62
88
|
timeout: 5000,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gazzehamine/armada-watch-agent",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Monitoring agent for Armada Watch - EC2 instance monitoring",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Monitoring agent for Armada Watch - EC2 instance monitoring with SSL certificate tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"armada-watch-agent": "dist/index.js"
|