@agentsoc/beacon 0.0.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/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # AgentSOC Syslog Beacon CLI
2
+
3
+ Lightweight, background-running log forwarder (beacon) for AgentSOC.
4
+
5
+ ## Overview
6
+
7
+ The Syslog Beacon CLI allows you to configure, install, and run a lightweight log forwarding daemon that sends system logs to an AgentSOC ingest endpoint.
8
+
9
+ ## Requirements
10
+
11
+ - [Node.js](https://nodejs.org/) v16 or higher
12
+
13
+ ## Installation
14
+
15
+ ### 🚀 Quick Install (One-Line Commands)
16
+
17
+ #### macOS / Linux
18
+
19
+ ```bash
20
+ curl -fsSL https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.sh | bash
21
+ ```
22
+
23
+ #### Windows PowerShell
24
+
25
+ ```powershell
26
+ irm https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.ps1 | iex
27
+ ```
28
+
29
+ #### Standard npm
30
+
31
+ Published as [`@agentsoc/beacon`](https://www.npmjs.com/package/@agentsoc/beacon) (npm requires a lowercase scope; same AgentSOC package).
32
+
33
+ ```bash
34
+ npm install -g @agentsoc/beacon
35
+ ```
36
+
37
+ The `syslog-beacon` command is also installed as an alias for the same CLI.
38
+
39
+ ### Option 2: Run from source with Bun
40
+
41
+ If you have the repository cloned and [Bun](https://bun.sh/) installed:
42
+
43
+ ```bash
44
+ cd apps/connectors/syslog-beacon-cli
45
+ bun install
46
+ bun run src/cli.ts config --url <url> --key <key>
47
+ bun run src/cli.ts run
48
+ ```
49
+
50
+ ### Installation Scripts
51
+
52
+ Automated installation scripts are provided for easy setup:
53
+
54
+ - **`install.sh`** - macOS/Linux installer
55
+ - **`install.ps1`** - Windows PowerShell installer
56
+ - **`install.bat`** - Windows batch installer
57
+
58
+ These scripts automatically check for Node.js/npm, install the package, and verify the installation.
59
+
60
+ ## Usage
61
+
62
+ The CLI provides the following commands:
63
+
64
+ ### Configure
65
+
66
+ Configure the AgentSOC connection (saves to a local configuration file):
67
+
68
+ ```bash
69
+ beacon config --url <url> --key <key>
70
+ ```
71
+
72
+ **Required Parameters:**
73
+
74
+ - `--url, -u <url>`: AgentSOC ingest URL (e.g., `https://api.agentsoc.com/ingest`)
75
+ - `--key, -k <key>`: AgentSOC API Key (e.g., `sk_1234567890abcdef`)
76
+
77
+ **Example:**
78
+
79
+ ```bash
80
+ beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef
81
+ ```
82
+
83
+ **Note:** If you run `bun run src/cli.ts config` without the required parameters, you'll receive an error instructing you to provide the `-u` and `-k` options.
84
+
85
+ ### Run Foreground Daemon
86
+
87
+ Run the forwarder daemon in the foreground:
88
+
89
+ ```bash
90
+ beacon run [options]
91
+ ```
92
+
93
+ **Options:**
94
+
95
+ - `--batch-size <n>`: Set the batch size for log forwarding (default: 25)
96
+ - `--flush-ms <n>`: Set the flush interval in milliseconds (default: 2000)
97
+
98
+ **Example:**
99
+
100
+ ```bash
101
+ beacon run --batch-size 50 --flush-ms 5000
102
+ ```
103
+
104
+ _Note: You can also use environment variables `AGENTSOC_API_KEY` and `AGENTSOC_INGEST_URL` instead of running the `config` command._
105
+
106
+ ### Install Service
107
+
108
+ Install the background service daemon (systemd on Linux, launchd on macOS):
109
+
110
+ ```bash
111
+ beacon install
112
+ ```
113
+
114
+ ## Development
115
+
116
+ - Start the daemon using npm script: `npm start` or `bun run start`
117
+ - Install service using npm script: `npm run install-service` or `bun run install-service`
118
+ - Build the distributable: `npm run build:simple` (compiles TypeScript to dist/)
119
+
120
+ ## Building & Distribution
121
+
122
+ ### Build the Package
123
+
124
+ To build the TypeScript files into the `dist/` folder:
125
+
126
+ ```bash
127
+ npm run build:simple
128
+ ```
129
+
130
+ ### Share Installation Scripts
131
+
132
+ Users can install the package using one of the provided installation scripts:
133
+
134
+ - Copy the `install.sh` script to your repository (macOS/Linux users)
135
+ - Copy the `install.ps1` script to your repository (Windows PowerShell users)
136
+ - Copy the `install.bat` script to your repository (Windows batch users)
137
+
138
+ Users can then run:
139
+
140
+ ```bash
141
+ curl -fsSL https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.sh | bash
142
+ ```
143
+
144
+ Or use npm:
145
+
146
+ ```bash
147
+ npm install -g @agentsoc/beacon
148
+ ```
package/dist/cli.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { loadConfig, saveConfig } from "./config.js";
4
+ import { runBeacon } from "./run-beacon.js";
5
+ import { installService } from "./service-install.js";
6
+ const program = new Command();
7
+ program
8
+ .name("beacon")
9
+ .description("AgentSOC Syslog Beacon - Lightweight background log forwarder")
10
+ .version("1.0.0", "-v, --version");
11
+ program
12
+ .command("config")
13
+ .description("Configure the AgentSOC connection")
14
+ .option("-u, --url <url>", "AgentSOC ingest URL")
15
+ .option("-k, --key <key>", "AgentSOC API Key")
16
+ .action(async (options) => {
17
+ if (!options.url || !options.key) {
18
+ console.log("\n[beacon] Missing required options.\n");
19
+ console.log("Usage: beacon config --url <url> --key <key>\n");
20
+ console.log("Examples:");
21
+ console.log(" bun run src/cli.ts config -u https://api.agentsoc.com/ingest -k sk_1234567890abcdef");
22
+ console.log(" beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef\n");
23
+ console.log("Options:");
24
+ console.log(" -u, --url <url> AgentSOC ingest URL");
25
+ console.log(" -k, --key <key> AgentSOC API Key\n");
26
+ process.exit(1);
27
+ }
28
+ await saveConfig({
29
+ ingestUrl: options.url,
30
+ apiKey: options.key,
31
+ });
32
+ console.log("[beacon] Config saved.");
33
+ });
34
+ program
35
+ .command("install")
36
+ .description("Install the background service daemon")
37
+ .action(async () => {
38
+ try {
39
+ await installService();
40
+ }
41
+ catch (err) {
42
+ console.error("[beacon] Install failed:", err);
43
+ process.exit(1);
44
+ }
45
+ });
46
+ program
47
+ .command("run")
48
+ .description("Run the forwarder daemon (foreground)")
49
+ .option("--batch-size <n>", "Batch size", "25")
50
+ .option("--flush-ms <n>", "Flush interval in ms", "2000")
51
+ .action(async (options) => {
52
+ const config = await loadConfig();
53
+ const apiKey = process.env.AGENTSOC_API_KEY || config.apiKey;
54
+ const ingestUrl = process.env.AGENTSOC_INGEST_URL || config.ingestUrl;
55
+ if (!apiKey || !ingestUrl) {
56
+ console.error("[beacon] Missing apiKey or ingestUrl. Run `beacon config` or set ENV vars.");
57
+ process.exit(1);
58
+ }
59
+ console.log("[beacon] Starting in foreground...");
60
+ const beacon = await runBeacon({
61
+ apiKey,
62
+ ingestUrl,
63
+ batchSize: parseInt(options.batchSize, 10),
64
+ flushMs: parseInt(options.flushMs, 10),
65
+ });
66
+ const stop = async () => {
67
+ console.log("\n[beacon] Stopping...");
68
+ await beacon.stop();
69
+ process.exit(0);
70
+ };
71
+ process.on("SIGINT", stop);
72
+ process.on("SIGTERM", stop);
73
+ });
74
+ program.parse(process.argv);
package/dist/config.js ADDED
@@ -0,0 +1,37 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ export function getConfigDir() {
5
+ const home = os.homedir();
6
+ if (process.platform === "win32") {
7
+ return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "agentsoc-beacon");
8
+ }
9
+ if (process.platform === "darwin") {
10
+ return path.join(home, "Library", "Application Support", "agentsoc-beacon");
11
+ }
12
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
13
+ return path.join(xdg, "agentsoc-beacon");
14
+ }
15
+ export function getConfigFile() {
16
+ return path.join(getConfigDir(), "config.json");
17
+ }
18
+ export async function loadConfig() {
19
+ try {
20
+ const file = getConfigFile();
21
+ const data = await fs.readFile(file, "utf8");
22
+ return JSON.parse(data);
23
+ }
24
+ catch (err) {
25
+ return {};
26
+ }
27
+ }
28
+ export async function saveConfig(config) {
29
+ const dir = getConfigDir();
30
+ try {
31
+ await fs.mkdir(dir, { recursive: true });
32
+ await fs.writeFile(getConfigFile(), JSON.stringify(config, null, 2), "utf8");
33
+ }
34
+ catch (err) {
35
+ console.error(`[syslog-beacon] Failed to save config to ${getConfigFile()}:`, err);
36
+ }
37
+ }
package/dist/enrich.js ADDED
@@ -0,0 +1,32 @@
1
+ import os from "node:os";
2
+ import crypto from "node:crypto";
3
+ import { loadConfig, saveConfig } from "./config.js";
4
+ export async function getHostInfo() {
5
+ const config = await loadConfig();
6
+ let agentId = config.agentId;
7
+ if (!agentId) {
8
+ agentId = crypto.randomUUID();
9
+ config.agentId = agentId;
10
+ await saveConfig(config);
11
+ }
12
+ const hostname = os.hostname();
13
+ const osPlatform = os.platform();
14
+ let ip = "127.0.0.1";
15
+ let mac = "00:00:00:00:00:00";
16
+ const interfaces = os.networkInterfaces();
17
+ for (const name of Object.keys(interfaces)) {
18
+ const iface = interfaces[name];
19
+ if (!iface)
20
+ continue;
21
+ for (const info of iface) {
22
+ if (!info.internal && info.family === "IPv4") {
23
+ ip = info.address;
24
+ mac = info.mac;
25
+ break;
26
+ }
27
+ }
28
+ if (ip !== "127.0.0.1")
29
+ break;
30
+ }
31
+ return { agentId, hostname, ip, osPlatform, mac };
32
+ }
@@ -0,0 +1,132 @@
1
+ function parseSyslogPri(line) {
2
+ // Default mapping if line does not start with <PRI>
3
+ let severity = 5; // notice (maps to 'low' severity in our logic)
4
+ let facility = 1; // user-level messages
5
+ const match = line.match(/^<(\d+)>/);
6
+ if (match) {
7
+ const pri = parseInt(match[1], 10);
8
+ facility = Math.floor(pri / 8); // pri >> 3
9
+ severity = pri & 7; // pri % 8
10
+ }
11
+ else {
12
+ // If testing on macOS via log stream, the raw line might not contain <PRI>.
13
+ // Try to guess from keywords just for testing convenience:
14
+ const l = line.toLowerCase();
15
+ if (l.includes("critical") || l.includes("fatal") || l.includes("panic")) {
16
+ severity = 2; // crit
17
+ }
18
+ else if (l.includes("high") ||
19
+ l.includes("error") ||
20
+ l.includes("failed")) {
21
+ severity = 3; // err
22
+ }
23
+ else if (l.includes("medium") || l.includes("warn")) {
24
+ severity = 4; // warning
25
+ }
26
+ else if (l.includes("info")) {
27
+ severity = 6; // info
28
+ }
29
+ else if (l.includes("low") || l.includes("notice")) {
30
+ severity = 5; // notice
31
+ }
32
+ }
33
+ let severityStr = "info";
34
+ if (severity <= 2)
35
+ severityStr = "critical";
36
+ else if (severity === 3)
37
+ severityStr = "high";
38
+ else if (severity === 4)
39
+ severityStr = "medium";
40
+ else if (severity === 5)
41
+ severityStr = "low";
42
+ return { severityStr, facility };
43
+ }
44
+ function extractProcInfo(line) {
45
+ // Try to extract the program name from syslog format: hostname[pid]: message or hostname program: message
46
+ const match = line.match(/^\S+\s+(?:\S+\s+)?(\w+)(?:\[\d+\])?:\s*(.*)$/);
47
+ if (match) {
48
+ return { app: match[1], message: match[2] };
49
+ }
50
+ // Fall back to the whole line as message
51
+ return { message: line };
52
+ }
53
+ export class Forwarder {
54
+ opts;
55
+ buffer = [];
56
+ timer = null;
57
+ batchSize;
58
+ flushMs;
59
+ constructor(opts) {
60
+ this.opts = opts;
61
+ this.batchSize = opts.batchSize || 25;
62
+ this.flushMs = opts.flushMs || 2000;
63
+ }
64
+ pushLog(line) {
65
+ const { severityStr, facility } = parseSyslogPri(line);
66
+ const { app, message } = extractProcInfo(line);
67
+ // Build syslog event matching the ingest API schema
68
+ const event = {
69
+ full_log: message,
70
+ timestamp: new Date().toISOString(),
71
+ hostname: this.opts.host.hostname,
72
+ facility,
73
+ severity: severityStr,
74
+ id: `${this.opts.host.agentId}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
75
+ agent: {
76
+ id: this.opts.host.agentId,
77
+ name: this.opts.host.hostname,
78
+ ip: this.opts.host.ip,
79
+ mac: this.opts.host.mac,
80
+ },
81
+ };
82
+ if (app) {
83
+ event.app = app;
84
+ event.process = {
85
+ name: app,
86
+ };
87
+ }
88
+ this.buffer.push(event);
89
+ if (this.buffer.length >= this.batchSize) {
90
+ this.flush();
91
+ }
92
+ else if (!this.timer) {
93
+ this.timer = setTimeout(() => this.flush(), this.flushMs);
94
+ }
95
+ }
96
+ async flush() {
97
+ if (this.timer) {
98
+ clearTimeout(this.timer);
99
+ this.timer = null;
100
+ }
101
+ if (this.buffer.length === 0)
102
+ return;
103
+ const payload = this.buffer;
104
+ this.buffer = [];
105
+ console.log(`[syslog-beacon] Pushing ${payload.length} logs to API...`);
106
+ try {
107
+ const res = await fetch(this.opts.ingestUrl, {
108
+ method: "POST",
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ Authorization: `Bearer ${this.opts.apiKey}`,
112
+ },
113
+ body: JSON.stringify(payload),
114
+ });
115
+ if (!res.ok) {
116
+ const text = await res.text();
117
+ console.error(`[syslog-beacon] Failed to push logs: HTTP ${res.status} ${text}`);
118
+ }
119
+ else {
120
+ console.log(`[syslog-beacon] Successfully pushed ${payload.length} logs`);
121
+ }
122
+ }
123
+ catch (err) {
124
+ console.error(`[syslog-beacon] Ingest error:`, err);
125
+ }
126
+ }
127
+ async stop() {
128
+ if (this.buffer.length > 0) {
129
+ await this.flush();
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,17 @@
1
+ import { getHostInfo } from './enrich.js';
2
+ import { Forwarder } from './forwarder.js';
3
+ import { SyslogSource } from './tail.js';
4
+ export async function runBeacon(opts) {
5
+ const host = await getHostInfo();
6
+ const forwarder = new Forwarder({ ...opts, host });
7
+ const source = new SyslogSource((line) => {
8
+ forwarder.pushLog(line);
9
+ });
10
+ source.start();
11
+ return {
12
+ stop: async () => {
13
+ source.stop();
14
+ await forwarder.stop();
15
+ }
16
+ };
17
+ }
@@ -0,0 +1,67 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ import { loadConfig } from './config.js';
5
+ export async function installService() {
6
+ const config = await loadConfig();
7
+ if (!config.apiKey || !config.ingestUrl) {
8
+ throw new Error('Config missing apiKey or ingestUrl. Please configure first.');
9
+ }
10
+ const isBun = process.execPath.endsWith('bun') || process.execPath.endsWith('bun.exe');
11
+ const runner = isBun ? process.execPath : 'node';
12
+ const cliPath = path.resolve(process.argv[1]);
13
+ const command = `${runner} ${cliPath} run`;
14
+ if (process.platform === 'linux') {
15
+ const serviceUnit = `[Unit]
16
+ Description=AgentSOC Syslog Beacon
17
+ After=network.target
18
+
19
+ [Service]
20
+ Type=simple
21
+ ExecStart=${command}
22
+ Restart=always
23
+ RestartSec=10
24
+ Environment=NODE_ENV=production
25
+
26
+ [Install]
27
+ WantedBy=multi-user.target
28
+ `;
29
+ const dest = '/etc/systemd/system/syslog-beacon.service';
30
+ await fs.writeFile(dest, serviceUnit, 'utf8');
31
+ execSync('systemctl daemon-reload');
32
+ execSync('systemctl enable syslog-beacon');
33
+ execSync('systemctl start syslog-beacon');
34
+ console.log('[syslog-beacon] Installed systemd service');
35
+ }
36
+ else if (process.platform === 'darwin') {
37
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
38
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39
+ <plist version="1.0">
40
+ <dict>
41
+ <key>Label</key>
42
+ <string>com.agentsoc.syslog-beacon</string>
43
+ <key>ProgramArguments</key>
44
+ <array>
45
+ <string>${runner}</string>
46
+ <string>${cliPath}</string>
47
+ <string>run</string>
48
+ </array>
49
+ <key>RunAtLoad</key>
50
+ <true/>
51
+ <key>KeepAlive</key>
52
+ <true/>
53
+ </dict>
54
+ </plist>
55
+ `;
56
+ const dest = '/Library/LaunchDaemons/com.agentsoc.syslog-beacon.plist';
57
+ await fs.writeFile(dest, plist, 'utf8');
58
+ execSync(`launchctl load -w ${dest}`);
59
+ console.log('[syslog-beacon] Installed launchd service');
60
+ }
61
+ else if (process.platform === 'win32') {
62
+ console.log('[syslog-beacon] Windows service installation must be done via NSSM or WinSW. Skipping automatic install.');
63
+ }
64
+ else {
65
+ throw new Error(`Platform ${process.platform} is not supported for automatic service installation.`);
66
+ }
67
+ }
package/dist/tail.js ADDED
@@ -0,0 +1,71 @@
1
+ import { spawn } from "node:child_process";
2
+ import dgram from "node:dgram";
3
+ export class SyslogSource {
4
+ onLog;
5
+ tailProc;
6
+ udpServer;
7
+ constructor(onLog) {
8
+ this.onLog = onLog;
9
+ }
10
+ start() {
11
+ if (process.platform === "linux") {
12
+ this.tailProc = spawn("tail", [
13
+ "-F",
14
+ "/var/log/syslog",
15
+ "/var/log/auth.log",
16
+ ]);
17
+ this.tailProc.stdout?.on("data", (data) => {
18
+ const lines = data.toString().split("\n");
19
+ for (const line of lines) {
20
+ if (line.trim())
21
+ this.onLog(line);
22
+ }
23
+ });
24
+ this.tailProc.stderr?.on("data", (data) => {
25
+ console.error(`[syslog-tail] ${data.toString()}`);
26
+ });
27
+ }
28
+ else if (process.platform === "darwin") {
29
+ this.tailProc = spawn("log", [
30
+ "stream",
31
+ "--predicate",
32
+ 'process == "sshd" or process == "sudo" or process == "login" or process == "logger"',
33
+ "--style",
34
+ "syslog",
35
+ ]);
36
+ console.log("[syslog-tail] macOS log stream started");
37
+ this.tailProc.stdout?.on("data", (data) => {
38
+ const lines = data.toString().split("\n");
39
+ for (const line of lines) {
40
+ if (line.trim())
41
+ this.onLog(line);
42
+ }
43
+ });
44
+ this.tailProc.stderr?.on("data", (data) => {
45
+ console.error(`[syslog-tail] ${data.toString()}`);
46
+ });
47
+ }
48
+ else {
49
+ // Windows or other - start a UDP server
50
+ this.udpServer = dgram.createSocket("udp4");
51
+ this.udpServer.on("message", (msg) => {
52
+ this.onLog(msg.toString("utf8").trim());
53
+ });
54
+ this.udpServer.on("error", (err) => {
55
+ console.error(`[syslog-udp] error:\n${err.stack}`);
56
+ this.udpServer?.close();
57
+ });
58
+ this.udpServer.bind(5140, () => {
59
+ console.log("[syslog-udp] listening on UDP 5140");
60
+ });
61
+ }
62
+ }
63
+ stop() {
64
+ if (this.tailProc) {
65
+ this.tailProc.kill();
66
+ }
67
+ if (this.udpServer) {
68
+ this.udpServer.close();
69
+ }
70
+ }
71
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@agentsoc/beacon",
3
+ "version": "0.0.1",
4
+ "description": "Lightweight, background-running log forwarder (beacon) for AgentSOC",
5
+ "type": "module",
6
+ "bin": {
7
+ "beacon": "./dist/cli.js",
8
+ "syslog-beacon": "./dist/cli.js"
9
+ },
10
+ "main": "./dist/cli.js",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "bun run --cwd ../../.. bun build --target bun --entry-points ./apps/connectors/syslog-beacon-cli/src/cli.ts --outdir ./apps/connectors/syslog-beacon-cli/dist --minify",
16
+ "build:simple": "tsc",
17
+ "start": "bun run src/cli.ts run",
18
+ "install-service": "bun run src/cli.ts install"
19
+ },
20
+ "dependencies": {
21
+ "commander": "^11.1.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/bun": "latest",
25
+ "typescript": "^5.6.0"
26
+ }
27
+ }