@aidalinfo/aegis-agent 0.1.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/bin.js ADDED
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin.ts
4
+ import { defineCommand, runMain } from "citty";
5
+ import { execSync as execSync2 } from "child_process";
6
+
7
+ // src/config.ts
8
+ import { z } from "zod";
9
+ import { readFileSync } from "fs";
10
+ import { hostname } from "os";
11
+ var ConfigSchema = z.object({
12
+ interval: z.coerce.number().int().min(5).default(30),
13
+ endpoint: z.string().url(),
14
+ apiKey: z.string().min(1),
15
+ mode: z.enum(["core", "full"]).default("core"),
16
+ hostname: z.string().default(hostname())
17
+ });
18
+ function loadConfig(configPath = "/etc/aegis-agent/config.json") {
19
+ let fileConfig = {};
20
+ try {
21
+ fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
22
+ } catch {
23
+ }
24
+ return ConfigSchema.parse({
25
+ ...fileConfig,
26
+ ...process.env.AEGIS_INTERVAL !== void 0 && { interval: process.env.AEGIS_INTERVAL },
27
+ ...process.env.AEGIS_ENDPOINT !== void 0 && { endpoint: process.env.AEGIS_ENDPOINT },
28
+ ...process.env.AEGIS_API_KEY !== void 0 && { apiKey: process.env.AEGIS_API_KEY },
29
+ ...process.env.AEGIS_MODE !== void 0 && { mode: process.env.AEGIS_MODE },
30
+ ...process.env.AEGIS_HOSTNAME !== void 0 && { hostname: process.env.AEGIS_HOSTNAME }
31
+ });
32
+ }
33
+
34
+ // src/metrics/cpu.ts
35
+ import si from "systeminformation";
36
+ async function collectCpu() {
37
+ const load = await si.currentLoad();
38
+ return { usagePercent: Math.round(load.currentLoad * 10) / 10 };
39
+ }
40
+
41
+ // src/metrics/memory.ts
42
+ import si2 from "systeminformation";
43
+ 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
+ };
52
+ }
53
+
54
+ // src/metrics/disk.ts
55
+ import si3 from "systeminformation";
56
+ 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
+ }));
68
+ }
69
+
70
+ // src/metrics/network.ts
71
+ import si4 from "systeminformation";
72
+ 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
+ }));
79
+ }
80
+
81
+ // 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
+ };
90
+ }
91
+
92
+ // src/metrics/swap.ts
93
+ import si5 from "systeminformation";
94
+ 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
+ };
103
+ }
104
+
105
+ // src/metrics/processes.ts
106
+ import si6 from "systeminformation";
107
+ async function collectProcesses() {
108
+ const { list } = await si6.processes();
109
+ return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
110
+ pid: p.pid,
111
+ name: p.name,
112
+ cpuPercent: Math.round(p.cpu * 10) / 10,
113
+ memPercent: Math.round(p.mem * 10) / 10
114
+ }));
115
+ }
116
+
117
+ // src/metrics/temperature.ts
118
+ import si7 from "systeminformation";
119
+ async function collectTemperature() {
120
+ try {
121
+ const temp = await si7.cpuTemperature();
122
+ return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
123
+ } catch {
124
+ return { cpuCelsius: null };
125
+ }
126
+ }
127
+
128
+ // src/collector.ts
129
+ async function collectMetrics(config) {
130
+ const [cpu, memory, disks] = await Promise.all([
131
+ collectCpu(),
132
+ collectMemory(),
133
+ collectDisk()
134
+ ]);
135
+ const metrics = { cpu, memory, disks };
136
+ if (config.mode === "full") {
137
+ const [network, swap, topProcesses, temperature] = await Promise.all([
138
+ collectNetwork(),
139
+ collectSwap(),
140
+ collectProcesses(),
141
+ collectTemperature()
142
+ ]);
143
+ Object.assign(metrics, {
144
+ network,
145
+ loadAverage: collectLoad(),
146
+ swap,
147
+ topProcesses,
148
+ temperature
149
+ });
150
+ }
151
+ return {
152
+ hostname: config.hostname,
153
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
154
+ mode: config.mode,
155
+ metrics
156
+ };
157
+ }
158
+
159
+ // src/pusher.ts
160
+ async function sleep(ms) {
161
+ return new Promise((resolve) => setTimeout(resolve, ms));
162
+ }
163
+ async function pushMetrics(payload, config) {
164
+ const url = `${config.endpoint.replace(/\/$/, "")}/api/agent/metrics`;
165
+ const body = JSON.stringify(payload);
166
+ const headers = {
167
+ "Content-Type": "application/json",
168
+ Authorization: `Bearer ${config.apiKey}`
169
+ };
170
+ for (let attempt = 0; attempt < 3; attempt++) {
171
+ try {
172
+ const res = await fetch(url, { method: "POST", headers, body });
173
+ if (res.ok) return;
174
+ if (res.status === 401 || res.status === 403) {
175
+ console.error(`[aegis-agent] Auth error (${res.status}) \u2014 check your API key`);
176
+ return;
177
+ }
178
+ console.warn(`[aegis-agent] Push failed (${res.status}), attempt ${attempt + 1}/3`);
179
+ } catch (err) {
180
+ console.warn(`[aegis-agent] Network error, attempt ${attempt + 1}/3:`, err.message);
181
+ }
182
+ if (attempt < 2) await sleep(1e3 * 2 ** attempt);
183
+ }
184
+ console.error("[aegis-agent] All 3 push attempts failed \u2014 will retry next interval");
185
+ }
186
+
187
+ // src/systemd.ts
188
+ import { writeFileSync, mkdirSync } from "fs";
189
+ import { execSync } from "child_process";
190
+ function generateUnitFile(binaryPath) {
191
+ return `[Unit]
192
+ Description=Aegis Monitoring Agent
193
+ After=network.target
194
+ StartLimitIntervalSec=0
195
+
196
+ [Service]
197
+ Type=simple
198
+ Restart=always
199
+ RestartSec=5
200
+ ExecStart=${binaryPath} start
201
+ WorkingDirectory=/etc/aegis-agent
202
+ StandardOutput=journal
203
+ StandardError=journal
204
+ SyslogIdentifier=aegis-agent
205
+
206
+ [Install]
207
+ WantedBy=multi-user.target
208
+ `;
209
+ }
210
+ function installService(config, binaryPath) {
211
+ mkdirSync("/etc/aegis-agent", { recursive: true });
212
+ writeFileSync("/etc/aegis-agent/config.json", JSON.stringify(config, null, 2), { mode: 384 });
213
+ writeFileSync("/etc/systemd/system/aegis-agent.service", generateUnitFile(binaryPath));
214
+ execSync("systemctl daemon-reload");
215
+ execSync("systemctl enable --now aegis-agent");
216
+ console.log("[aegis-agent] Service installed and started \u2014 check status with: systemctl status aegis-agent");
217
+ }
218
+ function uninstallService() {
219
+ try {
220
+ execSync("systemctl disable --now aegis-agent", { stdio: "pipe" });
221
+ } catch {
222
+ }
223
+ try {
224
+ execSync("rm -f /etc/systemd/system/aegis-agent.service", { stdio: "pipe" });
225
+ } catch {
226
+ }
227
+ execSync("systemctl daemon-reload");
228
+ try {
229
+ execSync("rm -rf /etc/aegis-agent", { stdio: "pipe" });
230
+ } catch {
231
+ }
232
+ console.log("[aegis-agent] Service removed");
233
+ }
234
+
235
+ // src/bin.ts
236
+ var start = defineCommand({
237
+ meta: { description: "Run the metrics collection loop (foreground)" },
238
+ async run() {
239
+ const config = loadConfig();
240
+ console.log(
241
+ `[aegis-agent] Starting \u2014 mode=${config.mode} interval=${config.interval}s endpoint=${config.endpoint}`
242
+ );
243
+ async function tick() {
244
+ try {
245
+ const payload = await collectMetrics(config);
246
+ await pushMetrics(payload, config);
247
+ } catch (err) {
248
+ console.error("[aegis-agent] Unexpected error during tick:", err);
249
+ }
250
+ }
251
+ await tick();
252
+ setInterval(tick, config.interval * 1e3);
253
+ }
254
+ });
255
+ var install = defineCommand({
256
+ meta: { description: "Install as a systemd service (requires root)" },
257
+ args: {
258
+ endpoint: { type: "string", required: true, description: "Aegis API base URL" },
259
+ "api-key": { type: "string", required: true, description: "Bearer token" },
260
+ interval: { type: "string", default: "30", description: "Push interval in seconds" },
261
+ mode: { type: "string", default: "core", description: "core or full" },
262
+ hostname: { type: "string", description: "Override hostname (auto-detected if omitted)" }
263
+ },
264
+ async run({ args }) {
265
+ let binaryPath;
266
+ try {
267
+ binaryPath = execSync2("which aegis-agent").toString().trim();
268
+ } catch {
269
+ binaryPath = process.argv[1];
270
+ }
271
+ const config = ConfigSchema.parse({
272
+ endpoint: args.endpoint,
273
+ apiKey: args["api-key"],
274
+ interval: Number(args.interval),
275
+ mode: args.mode,
276
+ ...args.hostname ? { hostname: args.hostname } : {}
277
+ });
278
+ installService(config, binaryPath);
279
+ }
280
+ });
281
+ var uninstall = defineCommand({
282
+ meta: { description: "Stop and remove the systemd service (requires root)" },
283
+ async run() {
284
+ uninstallService();
285
+ }
286
+ });
287
+ var main = defineCommand({
288
+ meta: { name: "aegis-agent", description: "Aegis monitoring agent" },
289
+ subCommands: { start, install, uninstall }
290
+ });
291
+ runMain(main);
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const ConfigSchema: z.ZodObject<{
4
+ interval: z.ZodDefault<z.ZodNumber>;
5
+ endpoint: z.ZodString;
6
+ apiKey: z.ZodString;
7
+ mode: z.ZodDefault<z.ZodEnum<["core", "full"]>>;
8
+ hostname: z.ZodDefault<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ interval: number;
11
+ endpoint: string;
12
+ apiKey: string;
13
+ mode: "core" | "full";
14
+ hostname: string;
15
+ }, {
16
+ endpoint: string;
17
+ apiKey: string;
18
+ interval?: number | undefined;
19
+ mode?: "core" | "full" | undefined;
20
+ hostname?: string | undefined;
21
+ }>;
22
+ type Config = z.infer<typeof ConfigSchema>;
23
+
24
+ interface MetricsPayload {
25
+ hostname: string;
26
+ timestamp: string;
27
+ mode: 'core' | 'full';
28
+ metrics: Record<string, unknown>;
29
+ }
30
+
31
+ export type { Config, MetricsPayload };
package/dist/index.js ADDED
File without changes
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@aidalinfo/aegis-agent",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight monitoring agent for Linux machines — pushes system metrics to Aegis",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.0.0"
9
+ },
10
+ "bin": {
11
+ "aegis-agent": "./dist/bin.js"
12
+ },
13
+ "exports": {
14
+ ".": "./dist/index.js"
15
+ },
16
+ "files": ["dist"],
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest watch",
27
+ "release": "pnpm build && changelogen && npm publish && git push --follow-tags"
28
+ },
29
+ "dependencies": {
30
+ "citty": "^0.1.6",
31
+ "systeminformation": "^5.23.0",
32
+ "zod": "^3.23.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.10.0",
36
+ "changelogen": "^0.6.2",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.7.0",
39
+ "vitest": "^3.2.0"
40
+ }
41
+ }