@aidalinfo/aegis-agent 0.1.1 → 0.2.1

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/bin.js CHANGED
@@ -7,14 +7,25 @@ import { execSync as execSync2 } from "child_process";
7
7
  // src/config.ts
8
8
  import { z } from "zod";
9
9
  import { readFileSync } from "fs";
10
+ import { join } from "path";
10
11
  import { hostname } from "os";
11
12
  var ConfigSchema = z.object({
12
13
  interval: z.coerce.number().int().min(5).default(30),
13
14
  endpoint: z.string().url(),
14
15
  apiKey: z.string().min(1),
15
16
  mode: z.enum(["core", "full"]).default("core"),
16
- hostname: z.string().default(hostname())
17
+ hostname: z.string().min(1)
17
18
  });
19
+ function resolveHostname() {
20
+ const hostRoot = process.env.AEGIS_HOST_ROOT;
21
+ if (hostRoot) {
22
+ try {
23
+ return readFileSync(join(hostRoot, "/etc/hostname"), "utf8").trim();
24
+ } catch {
25
+ }
26
+ }
27
+ return hostname();
28
+ }
18
29
  function loadConfig(configPath = "/etc/aegis-agent/config.json") {
19
30
  let fileConfig = {};
20
31
  try {
@@ -27,85 +38,251 @@ function loadConfig(configPath = "/etc/aegis-agent/config.json") {
27
38
  ...process.env.AEGIS_ENDPOINT !== void 0 && { endpoint: process.env.AEGIS_ENDPOINT },
28
39
  ...process.env.AEGIS_API_KEY !== void 0 && { apiKey: process.env.AEGIS_API_KEY },
29
40
  ...process.env.AEGIS_MODE !== void 0 && { mode: process.env.AEGIS_MODE },
30
- ...process.env.AEGIS_HOSTNAME !== void 0 && { hostname: process.env.AEGIS_HOSTNAME }
41
+ hostname: process.env.AEGIS_HOSTNAME ?? fileConfig.hostname ?? resolveHostname()
31
42
  });
32
43
  }
33
44
 
45
+ // src/metrics/hostpaths.ts
46
+ import { readFile, statfs } from "fs/promises";
47
+ import { join as join2 } from "path";
48
+ var HOST_PROC = process.env.AEGIS_HOST_PROC || "/proc";
49
+ var HOST_SYS = process.env.AEGIS_HOST_SYS || "/sys";
50
+ var HOST_ROOT = process.env.AEGIS_HOST_ROOT || "";
51
+ function readProc(rel) {
52
+ return readFile(join2(HOST_PROC, rel), "utf8");
53
+ }
54
+ function readHostFile(absPath) {
55
+ return readFile(HOST_ROOT ? join2(HOST_ROOT, absPath) : absPath, "utf8");
56
+ }
57
+ function statHostFs(mount) {
58
+ return statfs(HOST_ROOT ? join2(HOST_ROOT, mount) : mount);
59
+ }
60
+
61
+ // src/metrics/proc-parse.ts
62
+ function parseProcStat(content) {
63
+ const line = content.split("\n").find((l) => l.startsWith("cpu "));
64
+ if (!line) return { idle: 0, total: 0 };
65
+ const nums = line.trim().split(/\s+/).slice(1).map(Number);
66
+ const idle = (nums[3] ?? 0) + (nums[4] ?? 0);
67
+ const total = nums.reduce((a, b) => a + (b || 0), 0);
68
+ return { idle, total };
69
+ }
70
+ function cpuPercent(prev3, cur) {
71
+ const dTotal = cur.total - prev3.total;
72
+ const dIdle = cur.idle - prev3.idle;
73
+ if (dTotal <= 0) return 0;
74
+ return Math.round((dTotal - dIdle) / dTotal * 1e3) / 10;
75
+ }
76
+ function parseMemInfo(content) {
77
+ const get = (key) => {
78
+ const m = content.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
79
+ return m ? Number(m[1]) : 0;
80
+ };
81
+ return {
82
+ memTotalKb: get("MemTotal"),
83
+ memAvailableKb: get("MemAvailable"),
84
+ swapTotalKb: get("SwapTotal"),
85
+ swapFreeKb: get("SwapFree")
86
+ };
87
+ }
88
+ function memUsage(m) {
89
+ const totalMb = Math.round(m.memTotalKb / 1024);
90
+ const usedMb = Math.round((m.memTotalKb - m.memAvailableKb) / 1024);
91
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
92
+ }
93
+ function swapUsage(m) {
94
+ const totalMb = Math.round(m.swapTotalKb / 1024);
95
+ const usedMb = Math.round((m.swapTotalKb - m.swapFreeKb) / 1024);
96
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
97
+ }
98
+ function parseLoadAvg(content) {
99
+ const [a, b, c] = content.trim().split(/\s+/).map(Number);
100
+ return { "1m": a ?? 0, "5m": b ?? 0, "15m": c ?? 0 };
101
+ }
102
+ function parseUptime(content) {
103
+ return Math.round(Number(content.trim().split(/\s+/)[0] ?? 0));
104
+ }
105
+ function parseOsRelease(content) {
106
+ const m = content.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
107
+ return m ? m[1] : null;
108
+ }
109
+ function parseNetDev(content) {
110
+ const out = [];
111
+ for (const line of content.split("\n")) {
112
+ const m = line.match(/^\s*([^:]+):\s*(.+)$/);
113
+ if (!m) continue;
114
+ const iface = m[1].trim();
115
+ if (iface === "lo") continue;
116
+ const f = m[2].trim().split(/\s+/).map(Number);
117
+ out.push({ iface, rxBytes: f[0] ?? 0, txBytes: f[8] ?? 0 });
118
+ }
119
+ return out;
120
+ }
121
+ function netRates(prev3, cur, dtSec) {
122
+ if (dtSec <= 0) return [];
123
+ const prevMap = new Map(prev3.map((p) => [p.iface, p]));
124
+ return cur.map((c) => {
125
+ const p = prevMap.get(c.iface);
126
+ const rx = p ? Math.max(0, c.rxBytes - p.rxBytes) : 0;
127
+ const tx = p ? Math.max(0, c.txBytes - p.txBytes) : 0;
128
+ return {
129
+ interface: c.iface,
130
+ rxBytesPerSec: Math.round(rx / dtSec),
131
+ txBytesPerSec: Math.round(tx / dtSec)
132
+ };
133
+ });
134
+ }
135
+ var PSEUDO_FS = /* @__PURE__ */ new Set([
136
+ "sysfs",
137
+ "proc",
138
+ "tmpfs",
139
+ "devtmpfs",
140
+ "devpts",
141
+ "cgroup",
142
+ "cgroup2",
143
+ "pstore",
144
+ "bpf",
145
+ "mqueue",
146
+ "debugfs",
147
+ "tracefs",
148
+ "securityfs",
149
+ "fusectl",
150
+ "configfs",
151
+ "overlay",
152
+ "autofs",
153
+ "hugetlbfs",
154
+ "squashfs",
155
+ "ramfs",
156
+ "nsfs",
157
+ "binfmt_misc",
158
+ "efivarfs"
159
+ ]);
160
+ var DOCKER_FILE_MOUNTS = /* @__PURE__ */ new Set(["/etc/hostname", "/etc/resolv.conf", "/etc/hosts"]);
161
+ function parseMounts(content) {
162
+ const seen = /* @__PURE__ */ new Set();
163
+ const out = [];
164
+ for (const line of content.split("\n")) {
165
+ const parts = line.split(/\s+/);
166
+ if (parts.length < 3) continue;
167
+ const [, mount, fstype] = parts;
168
+ if (PSEUDO_FS.has(fstype)) continue;
169
+ if (DOCKER_FILE_MOUNTS.has(mount)) continue;
170
+ if (seen.has(mount)) continue;
171
+ seen.add(mount);
172
+ out.push(mount);
173
+ }
174
+ return out;
175
+ }
176
+ function parseDiskStats(content) {
177
+ let readSectors = 0;
178
+ let writeSectors = 0;
179
+ for (const line of content.split("\n")) {
180
+ const f = line.trim().split(/\s+/);
181
+ if (f.length < 11) continue;
182
+ const name = f[2];
183
+ if (/\d$/.test(name) || name.startsWith("loop") || name.startsWith("ram") || name.startsWith("dm-")) continue;
184
+ readSectors += Number(f[5]) || 0;
185
+ writeSectors += Number(f[9]) || 0;
186
+ }
187
+ return { readBytes: readSectors * 512, writeBytes: writeSectors * 512 };
188
+ }
189
+ function diskIoRates(prev3, cur, dtSec) {
190
+ if (dtSec <= 0) return { readBytesPerSec: 0, writeBytesPerSec: 0 };
191
+ return {
192
+ readBytesPerSec: Math.round(Math.max(0, cur.readBytes - prev3.readBytes) / dtSec),
193
+ writeBytesPerSec: Math.round(Math.max(0, cur.writeBytes - prev3.writeBytes) / dtSec)
194
+ };
195
+ }
196
+
34
197
  // src/metrics/cpu.ts
35
- import si from "systeminformation";
198
+ var prev = null;
36
199
  async function collectCpu() {
37
- const load = await si.currentLoad();
38
- return { usagePercent: Math.round(load.currentLoad * 10) / 10 };
200
+ const cur = parseProcStat(await readProc("stat"));
201
+ const usagePercent = prev ? cpuPercent(prev, cur) : 0;
202
+ prev = cur;
203
+ return { usagePercent };
39
204
  }
40
205
 
41
206
  // src/metrics/memory.ts
42
- import si2 from "systeminformation";
43
207
  async function collectMemory() {
44
- const mem = await si2.mem();
45
- const totalMb = Math.round(mem.total / 1048576);
46
- const usedMb = Math.round(mem.used / 1048576);
47
- return {
48
- totalMb,
49
- usedMb,
50
- usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10
51
- };
208
+ return memUsage(parseMemInfo(await readProc("meminfo")));
52
209
  }
53
210
 
54
211
  // src/metrics/disk.ts
55
- import si3 from "systeminformation";
212
+ var prevIo = null;
213
+ function normaliseMountForLabel(rawMount) {
214
+ if (!HOST_ROOT) return rawMount;
215
+ if (rawMount === HOST_ROOT) return "/";
216
+ const prefix = HOST_ROOT.endsWith("/") ? HOST_ROOT : HOST_ROOT + "/";
217
+ if (rawMount.startsWith(prefix)) return "/" + rawMount.slice(prefix.length);
218
+ return rawMount;
219
+ }
56
220
  async function collectDisk() {
57
- const [fsSizes, fsStats] = await Promise.all([si3.fsSize(), si3.fsStats()]);
58
- const readBytesPerSec = Math.round(fsStats.rx_sec ?? 0);
59
- const writeBytesPerSec = Math.round(fsStats.wx_sec ?? 0);
60
- return fsSizes.map((fs) => ({
61
- mount: fs.mount,
62
- totalGb: Math.round(fs.size / 1073741824 * 10) / 10,
63
- usedGb: Math.round(fs.used / 1073741824 * 10) / 10,
64
- usagePercent: Math.round(fs.use * 10) / 10,
65
- readBytesPerSec,
66
- writeBytesPerSec
67
- }));
221
+ const rawMounts = parseMounts(await readProc("mounts"));
222
+ const stats = parseDiskStats(await readProc("diskstats"));
223
+ const now = Date.now();
224
+ let io = { readBytesPerSec: 0, writeBytesPerSec: 0 };
225
+ if (prevIo) io = diskIoRates(prevIo.stats, stats, (now - prevIo.t) / 1e3);
226
+ prevIo = { stats, t: now };
227
+ const out = [];
228
+ const seenLabels = /* @__PURE__ */ new Set();
229
+ for (const rawMount of rawMounts) {
230
+ try {
231
+ const label = normaliseMountForLabel(rawMount);
232
+ if (seenLabels.has(label)) continue;
233
+ seenLabels.add(label);
234
+ const fs = await statHostFs(label);
235
+ const bsize = Number(fs.bsize);
236
+ const blocks = Number(fs.blocks);
237
+ const bfree = Number(fs.bfree);
238
+ const bavail = Number(fs.bavail);
239
+ if (!blocks) continue;
240
+ const totalBytes = blocks * bsize;
241
+ const usedBytes = (blocks - bfree) * bsize;
242
+ const denom = blocks - bfree + bavail;
243
+ out.push({
244
+ mount: label,
245
+ totalGb: Math.round(totalBytes / 1073741824 * 10) / 10,
246
+ usedGb: Math.round(usedBytes / 1073741824 * 10) / 10,
247
+ usagePercent: denom > 0 ? Math.round((blocks - bfree) / denom * 1e3) / 10 : 0,
248
+ readBytesPerSec: io.readBytesPerSec,
249
+ writeBytesPerSec: io.writeBytesPerSec
250
+ });
251
+ } catch {
252
+ }
253
+ }
254
+ return out;
68
255
  }
69
256
 
70
257
  // src/metrics/network.ts
71
- import si4 from "systeminformation";
258
+ var prev2 = null;
72
259
  async function collectNetwork() {
73
- const stats = await si4.networkStats();
74
- return stats.filter((s) => s.iface && s.rx_sec !== null).map((s) => ({
75
- interface: s.iface,
76
- rxBytesPerSec: Math.round(s.rx_sec ?? 0),
77
- txBytesPerSec: Math.round(s.tx_sec ?? 0)
78
- }));
260
+ const counters = parseNetDev(await readProc("net/dev"));
261
+ const now = Date.now();
262
+ if (!prev2) {
263
+ prev2 = { counters, t: now };
264
+ return counters.map((c) => ({ interface: c.iface, rxBytesPerSec: 0, txBytesPerSec: 0 }));
265
+ }
266
+ const dtSec = (now - prev2.t) / 1e3;
267
+ const rates = netRates(prev2.counters, counters, dtSec);
268
+ prev2 = { counters, t: now };
269
+ return rates;
79
270
  }
80
271
 
81
272
  // src/metrics/load.ts
82
- import { loadavg } from "os";
83
- function collectLoad() {
84
- const [l1, l5, l15] = loadavg();
85
- return {
86
- "1m": Math.round(l1 * 100) / 100,
87
- "5m": Math.round(l5 * 100) / 100,
88
- "15m": Math.round(l15 * 100) / 100
89
- };
273
+ async function collectLoad() {
274
+ return parseLoadAvg(await readProc("loadavg"));
90
275
  }
91
276
 
92
277
  // src/metrics/swap.ts
93
- import si5 from "systeminformation";
94
278
  async function collectSwap() {
95
- const mem = await si5.mem();
96
- const totalMb = Math.round(mem.swaptotal / 1048576);
97
- const usedMb = Math.round(mem.swapused / 1048576);
98
- return {
99
- totalMb,
100
- usedMb,
101
- usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10
102
- };
279
+ return swapUsage(parseMemInfo(await readProc("meminfo")));
103
280
  }
104
281
 
105
282
  // src/metrics/processes.ts
106
- import si6 from "systeminformation";
283
+ import si from "systeminformation";
107
284
  async function collectProcesses() {
108
- const { list } = await si6.processes();
285
+ const { list } = await si.processes();
109
286
  return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
110
287
  pid: p.pid,
111
288
  name: p.name,
@@ -115,45 +292,51 @@ async function collectProcesses() {
115
292
  }
116
293
 
117
294
  // src/metrics/temperature.ts
118
- import si7 from "systeminformation";
295
+ import si2 from "systeminformation";
119
296
  async function collectTemperature() {
120
297
  try {
121
- const temp = await si7.cpuTemperature();
298
+ const temp = await si2.cpuTemperature();
122
299
  return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
123
300
  } catch {
124
301
  return { cpuCelsius: null };
125
302
  }
126
303
  }
127
304
 
305
+ // src/metrics/system.ts
306
+ async function collectUptime() {
307
+ try {
308
+ return parseUptime(await readProc("uptime"));
309
+ } catch {
310
+ return 0;
311
+ }
312
+ }
313
+ async function collectOs() {
314
+ try {
315
+ return parseOsRelease(await readHostFile("/etc/os-release"));
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+
128
321
  // src/collector.ts
129
- import { type as osType, release as osRelease, uptime as osUptime } from "os";
130
322
  async function collectMetrics(config) {
131
- const [cpu, memory, disks] = await Promise.all([
323
+ const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
132
324
  collectCpu(),
133
325
  collectMemory(),
134
- collectDisk()
326
+ collectDisk(),
327
+ collectUptime(),
328
+ collectOs()
135
329
  ]);
136
- const metrics = {
137
- cpu,
138
- memory,
139
- disks,
140
- os: `${osType()} ${osRelease()}`,
141
- uptimeSeconds: Math.round(osUptime())
142
- };
330
+ const metrics = { cpu, memory, disks, os, uptimeSeconds };
143
331
  if (config.mode === "full") {
144
- const [network, swap, topProcesses, temperature] = await Promise.all([
332
+ const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
145
333
  collectNetwork(),
146
334
  collectSwap(),
147
335
  collectProcesses(),
148
- collectTemperature()
336
+ collectTemperature(),
337
+ collectLoad()
149
338
  ]);
150
- Object.assign(metrics, {
151
- network,
152
- loadAverage: collectLoad(),
153
- swap,
154
- topProcesses,
155
- temperature
156
- });
339
+ Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
157
340
  }
158
341
  return {
159
342
  hostname: config.hostname,
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ declare const ConfigSchema: z.ZodObject<{
5
5
  endpoint: z.ZodString;
6
6
  apiKey: z.ZodString;
7
7
  mode: z.ZodDefault<z.ZodEnum<["core", "full"]>>;
8
- hostname: z.ZodDefault<z.ZodString>;
8
+ hostname: z.ZodString;
9
9
  }, "strip", z.ZodTypeAny, {
10
10
  interval: number;
11
11
  endpoint: string;
@@ -15,11 +15,12 @@ declare const ConfigSchema: z.ZodObject<{
15
15
  }, {
16
16
  endpoint: string;
17
17
  apiKey: string;
18
+ hostname: string;
18
19
  interval?: number | undefined;
19
20
  mode?: "core" | "full" | undefined;
20
- hostname?: string | undefined;
21
21
  }>;
22
22
  type Config = z.infer<typeof ConfigSchema>;
23
+ declare function loadConfig(configPath?: string): Config;
23
24
 
24
25
  interface MetricsPayload {
25
26
  hostname: string;
@@ -27,5 +28,6 @@ interface MetricsPayload {
27
28
  mode: 'core' | 'full';
28
29
  metrics: Record<string, unknown>;
29
30
  }
31
+ declare function collectMetrics(config: Config): Promise<MetricsPayload>;
30
32
 
31
- export type { Config, MetricsPayload };
33
+ export { type Config, ConfigSchema, type MetricsPayload, collectMetrics, loadConfig };
package/dist/index.js CHANGED
@@ -0,0 +1,346 @@
1
+ // src/metrics/hostpaths.ts
2
+ import { readFile, statfs } from "fs/promises";
3
+ import { join } from "path";
4
+ var HOST_PROC = process.env.AEGIS_HOST_PROC || "/proc";
5
+ var HOST_SYS = process.env.AEGIS_HOST_SYS || "/sys";
6
+ var HOST_ROOT = process.env.AEGIS_HOST_ROOT || "";
7
+ function readProc(rel) {
8
+ return readFile(join(HOST_PROC, rel), "utf8");
9
+ }
10
+ function readHostFile(absPath) {
11
+ return readFile(HOST_ROOT ? join(HOST_ROOT, absPath) : absPath, "utf8");
12
+ }
13
+ function statHostFs(mount) {
14
+ return statfs(HOST_ROOT ? join(HOST_ROOT, mount) : mount);
15
+ }
16
+
17
+ // src/metrics/proc-parse.ts
18
+ function parseProcStat(content) {
19
+ const line = content.split("\n").find((l) => l.startsWith("cpu "));
20
+ if (!line) return { idle: 0, total: 0 };
21
+ const nums = line.trim().split(/\s+/).slice(1).map(Number);
22
+ const idle = (nums[3] ?? 0) + (nums[4] ?? 0);
23
+ const total = nums.reduce((a, b) => a + (b || 0), 0);
24
+ return { idle, total };
25
+ }
26
+ function cpuPercent(prev3, cur) {
27
+ const dTotal = cur.total - prev3.total;
28
+ const dIdle = cur.idle - prev3.idle;
29
+ if (dTotal <= 0) return 0;
30
+ return Math.round((dTotal - dIdle) / dTotal * 1e3) / 10;
31
+ }
32
+ function parseMemInfo(content) {
33
+ const get = (key) => {
34
+ const m = content.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
35
+ return m ? Number(m[1]) : 0;
36
+ };
37
+ return {
38
+ memTotalKb: get("MemTotal"),
39
+ memAvailableKb: get("MemAvailable"),
40
+ swapTotalKb: get("SwapTotal"),
41
+ swapFreeKb: get("SwapFree")
42
+ };
43
+ }
44
+ function memUsage(m) {
45
+ const totalMb = Math.round(m.memTotalKb / 1024);
46
+ const usedMb = Math.round((m.memTotalKb - m.memAvailableKb) / 1024);
47
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
48
+ }
49
+ function swapUsage(m) {
50
+ const totalMb = Math.round(m.swapTotalKb / 1024);
51
+ const usedMb = Math.round((m.swapTotalKb - m.swapFreeKb) / 1024);
52
+ return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
53
+ }
54
+ function parseLoadAvg(content) {
55
+ const [a, b, c] = content.trim().split(/\s+/).map(Number);
56
+ return { "1m": a ?? 0, "5m": b ?? 0, "15m": c ?? 0 };
57
+ }
58
+ function parseUptime(content) {
59
+ return Math.round(Number(content.trim().split(/\s+/)[0] ?? 0));
60
+ }
61
+ function parseOsRelease(content) {
62
+ const m = content.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
63
+ return m ? m[1] : null;
64
+ }
65
+ function parseNetDev(content) {
66
+ const out = [];
67
+ for (const line of content.split("\n")) {
68
+ const m = line.match(/^\s*([^:]+):\s*(.+)$/);
69
+ if (!m) continue;
70
+ const iface = m[1].trim();
71
+ if (iface === "lo") continue;
72
+ const f = m[2].trim().split(/\s+/).map(Number);
73
+ out.push({ iface, rxBytes: f[0] ?? 0, txBytes: f[8] ?? 0 });
74
+ }
75
+ return out;
76
+ }
77
+ function netRates(prev3, cur, dtSec) {
78
+ if (dtSec <= 0) return [];
79
+ const prevMap = new Map(prev3.map((p) => [p.iface, p]));
80
+ return cur.map((c) => {
81
+ const p = prevMap.get(c.iface);
82
+ const rx = p ? Math.max(0, c.rxBytes - p.rxBytes) : 0;
83
+ const tx = p ? Math.max(0, c.txBytes - p.txBytes) : 0;
84
+ return {
85
+ interface: c.iface,
86
+ rxBytesPerSec: Math.round(rx / dtSec),
87
+ txBytesPerSec: Math.round(tx / dtSec)
88
+ };
89
+ });
90
+ }
91
+ var PSEUDO_FS = /* @__PURE__ */ new Set([
92
+ "sysfs",
93
+ "proc",
94
+ "tmpfs",
95
+ "devtmpfs",
96
+ "devpts",
97
+ "cgroup",
98
+ "cgroup2",
99
+ "pstore",
100
+ "bpf",
101
+ "mqueue",
102
+ "debugfs",
103
+ "tracefs",
104
+ "securityfs",
105
+ "fusectl",
106
+ "configfs",
107
+ "overlay",
108
+ "autofs",
109
+ "hugetlbfs",
110
+ "squashfs",
111
+ "ramfs",
112
+ "nsfs",
113
+ "binfmt_misc",
114
+ "efivarfs"
115
+ ]);
116
+ var DOCKER_FILE_MOUNTS = /* @__PURE__ */ new Set(["/etc/hostname", "/etc/resolv.conf", "/etc/hosts"]);
117
+ function parseMounts(content) {
118
+ const seen = /* @__PURE__ */ new Set();
119
+ const out = [];
120
+ for (const line of content.split("\n")) {
121
+ const parts = line.split(/\s+/);
122
+ if (parts.length < 3) continue;
123
+ const [, mount, fstype] = parts;
124
+ if (PSEUDO_FS.has(fstype)) continue;
125
+ if (DOCKER_FILE_MOUNTS.has(mount)) continue;
126
+ if (seen.has(mount)) continue;
127
+ seen.add(mount);
128
+ out.push(mount);
129
+ }
130
+ return out;
131
+ }
132
+ function parseDiskStats(content) {
133
+ let readSectors = 0;
134
+ let writeSectors = 0;
135
+ for (const line of content.split("\n")) {
136
+ const f = line.trim().split(/\s+/);
137
+ if (f.length < 11) continue;
138
+ const name = f[2];
139
+ if (/\d$/.test(name) || name.startsWith("loop") || name.startsWith("ram") || name.startsWith("dm-")) continue;
140
+ readSectors += Number(f[5]) || 0;
141
+ writeSectors += Number(f[9]) || 0;
142
+ }
143
+ return { readBytes: readSectors * 512, writeBytes: writeSectors * 512 };
144
+ }
145
+ function diskIoRates(prev3, cur, dtSec) {
146
+ if (dtSec <= 0) return { readBytesPerSec: 0, writeBytesPerSec: 0 };
147
+ return {
148
+ readBytesPerSec: Math.round(Math.max(0, cur.readBytes - prev3.readBytes) / dtSec),
149
+ writeBytesPerSec: Math.round(Math.max(0, cur.writeBytes - prev3.writeBytes) / dtSec)
150
+ };
151
+ }
152
+
153
+ // src/metrics/cpu.ts
154
+ var prev = null;
155
+ async function collectCpu() {
156
+ const cur = parseProcStat(await readProc("stat"));
157
+ const usagePercent = prev ? cpuPercent(prev, cur) : 0;
158
+ prev = cur;
159
+ return { usagePercent };
160
+ }
161
+
162
+ // src/metrics/memory.ts
163
+ async function collectMemory() {
164
+ return memUsage(parseMemInfo(await readProc("meminfo")));
165
+ }
166
+
167
+ // src/metrics/disk.ts
168
+ var prevIo = null;
169
+ function normaliseMountForLabel(rawMount) {
170
+ if (!HOST_ROOT) return rawMount;
171
+ if (rawMount === HOST_ROOT) return "/";
172
+ const prefix = HOST_ROOT.endsWith("/") ? HOST_ROOT : HOST_ROOT + "/";
173
+ if (rawMount.startsWith(prefix)) return "/" + rawMount.slice(prefix.length);
174
+ return rawMount;
175
+ }
176
+ async function collectDisk() {
177
+ const rawMounts = parseMounts(await readProc("mounts"));
178
+ const stats = parseDiskStats(await readProc("diskstats"));
179
+ const now = Date.now();
180
+ let io = { readBytesPerSec: 0, writeBytesPerSec: 0 };
181
+ if (prevIo) io = diskIoRates(prevIo.stats, stats, (now - prevIo.t) / 1e3);
182
+ prevIo = { stats, t: now };
183
+ const out = [];
184
+ const seenLabels = /* @__PURE__ */ new Set();
185
+ for (const rawMount of rawMounts) {
186
+ try {
187
+ const label = normaliseMountForLabel(rawMount);
188
+ if (seenLabels.has(label)) continue;
189
+ seenLabels.add(label);
190
+ const fs = await statHostFs(label);
191
+ const bsize = Number(fs.bsize);
192
+ const blocks = Number(fs.blocks);
193
+ const bfree = Number(fs.bfree);
194
+ const bavail = Number(fs.bavail);
195
+ if (!blocks) continue;
196
+ const totalBytes = blocks * bsize;
197
+ const usedBytes = (blocks - bfree) * bsize;
198
+ const denom = blocks - bfree + bavail;
199
+ out.push({
200
+ mount: label,
201
+ totalGb: Math.round(totalBytes / 1073741824 * 10) / 10,
202
+ usedGb: Math.round(usedBytes / 1073741824 * 10) / 10,
203
+ usagePercent: denom > 0 ? Math.round((blocks - bfree) / denom * 1e3) / 10 : 0,
204
+ readBytesPerSec: io.readBytesPerSec,
205
+ writeBytesPerSec: io.writeBytesPerSec
206
+ });
207
+ } catch {
208
+ }
209
+ }
210
+ return out;
211
+ }
212
+
213
+ // src/metrics/network.ts
214
+ var prev2 = null;
215
+ async function collectNetwork() {
216
+ const counters = parseNetDev(await readProc("net/dev"));
217
+ const now = Date.now();
218
+ if (!prev2) {
219
+ prev2 = { counters, t: now };
220
+ return counters.map((c) => ({ interface: c.iface, rxBytesPerSec: 0, txBytesPerSec: 0 }));
221
+ }
222
+ const dtSec = (now - prev2.t) / 1e3;
223
+ const rates = netRates(prev2.counters, counters, dtSec);
224
+ prev2 = { counters, t: now };
225
+ return rates;
226
+ }
227
+
228
+ // src/metrics/load.ts
229
+ async function collectLoad() {
230
+ return parseLoadAvg(await readProc("loadavg"));
231
+ }
232
+
233
+ // src/metrics/swap.ts
234
+ async function collectSwap() {
235
+ return swapUsage(parseMemInfo(await readProc("meminfo")));
236
+ }
237
+
238
+ // src/metrics/processes.ts
239
+ import si from "systeminformation";
240
+ async function collectProcesses() {
241
+ const { list } = await si.processes();
242
+ return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
243
+ pid: p.pid,
244
+ name: p.name,
245
+ cpuPercent: Math.round(p.cpu * 10) / 10,
246
+ memPercent: Math.round(p.mem * 10) / 10
247
+ }));
248
+ }
249
+
250
+ // src/metrics/temperature.ts
251
+ import si2 from "systeminformation";
252
+ async function collectTemperature() {
253
+ try {
254
+ const temp = await si2.cpuTemperature();
255
+ return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
256
+ } catch {
257
+ return { cpuCelsius: null };
258
+ }
259
+ }
260
+
261
+ // src/metrics/system.ts
262
+ async function collectUptime() {
263
+ try {
264
+ return parseUptime(await readProc("uptime"));
265
+ } catch {
266
+ return 0;
267
+ }
268
+ }
269
+ async function collectOs() {
270
+ try {
271
+ return parseOsRelease(await readHostFile("/etc/os-release"));
272
+ } catch {
273
+ return null;
274
+ }
275
+ }
276
+
277
+ // src/collector.ts
278
+ async function collectMetrics(config) {
279
+ const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
280
+ collectCpu(),
281
+ collectMemory(),
282
+ collectDisk(),
283
+ collectUptime(),
284
+ collectOs()
285
+ ]);
286
+ const metrics = { cpu, memory, disks, os, uptimeSeconds };
287
+ if (config.mode === "full") {
288
+ const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
289
+ collectNetwork(),
290
+ collectSwap(),
291
+ collectProcesses(),
292
+ collectTemperature(),
293
+ collectLoad()
294
+ ]);
295
+ Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
296
+ }
297
+ return {
298
+ hostname: config.hostname,
299
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
300
+ mode: config.mode,
301
+ metrics
302
+ };
303
+ }
304
+
305
+ // src/config.ts
306
+ import { z } from "zod";
307
+ import { readFileSync } from "fs";
308
+ import { join as join2 } from "path";
309
+ import { hostname } from "os";
310
+ var ConfigSchema = z.object({
311
+ interval: z.coerce.number().int().min(5).default(30),
312
+ endpoint: z.string().url(),
313
+ apiKey: z.string().min(1),
314
+ mode: z.enum(["core", "full"]).default("core"),
315
+ hostname: z.string().min(1)
316
+ });
317
+ function resolveHostname() {
318
+ const hostRoot = process.env.AEGIS_HOST_ROOT;
319
+ if (hostRoot) {
320
+ try {
321
+ return readFileSync(join2(hostRoot, "/etc/hostname"), "utf8").trim();
322
+ } catch {
323
+ }
324
+ }
325
+ return hostname();
326
+ }
327
+ function loadConfig(configPath = "/etc/aegis-agent/config.json") {
328
+ let fileConfig = {};
329
+ try {
330
+ fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
331
+ } catch {
332
+ }
333
+ return ConfigSchema.parse({
334
+ ...fileConfig,
335
+ ...process.env.AEGIS_INTERVAL !== void 0 && { interval: process.env.AEGIS_INTERVAL },
336
+ ...process.env.AEGIS_ENDPOINT !== void 0 && { endpoint: process.env.AEGIS_ENDPOINT },
337
+ ...process.env.AEGIS_API_KEY !== void 0 && { apiKey: process.env.AEGIS_API_KEY },
338
+ ...process.env.AEGIS_MODE !== void 0 && { mode: process.env.AEGIS_MODE },
339
+ hostname: process.env.AEGIS_HOSTNAME ?? fileConfig.hostname ?? resolveHostname()
340
+ });
341
+ }
342
+ export {
343
+ ConfigSchema,
344
+ collectMetrics,
345
+ loadConfig
346
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aidalinfo/aegis-agent",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Lightweight monitoring agent for Linux machines — pushes system metrics to Aegis",
5
5
  "license": "MIT",
6
6
  "type": "module",