@agentsoc/beacon 0.0.1 → 0.0.2
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 +23 -49
- package/dist/cli.js +35 -0
- package/dist/forwarder.js +5 -0
- package/dist/service-status.js +95 -0
- package/dist/stats.js +71 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,50 +12,36 @@ The Syslog Beacon CLI allows you to configure, install, and run a lightweight lo
|
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Installers are **hosted on the marketing site** (not duplicated in this repo):
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
| Method | URL / command |
|
|
18
|
+
|--------|----------------|
|
|
19
|
+
| **macOS / Linux (curl)** | `curl -fsSL https://agentsoc.com/connectors/beacon.sh \| bash` |
|
|
20
|
+
| **Windows PowerShell** | `irm https://agentsoc.com/connectors/beacon-install.ps1 \| iex` |
|
|
21
|
+
| **Windows (batch)** | Download and run [beacon-install.bat](https://agentsoc.com/connectors/beacon-install.bat) |
|
|
22
|
+
| **npm** | `npm install -g @agentsoc/beacon` |
|
|
18
23
|
|
|
19
|
-
|
|
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
|
-
```
|
|
24
|
+
Published package: [`@agentsoc/beacon`](https://www.npmjs.com/package/@agentsoc/beacon) (npm scope is lowercase `agentsoc`).
|
|
36
25
|
|
|
37
26
|
The `syslog-beacon` command is also installed as an alias for the same CLI.
|
|
38
27
|
|
|
39
|
-
###
|
|
28
|
+
### Run from source (this monorepo)
|
|
40
29
|
|
|
41
30
|
If you have the repository cloned and [Bun](https://bun.sh/) installed:
|
|
42
31
|
|
|
43
32
|
```bash
|
|
44
33
|
cd apps/connectors/syslog-beacon-cli
|
|
45
34
|
bun install
|
|
46
|
-
bun run
|
|
47
|
-
|
|
35
|
+
bun run build:simple
|
|
36
|
+
npm install -g .
|
|
48
37
|
```
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Automated installation scripts are provided for easy setup:
|
|
39
|
+
Then use `beacon` / `syslog-beacon` like a normal global install:
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
These scripts automatically check for Node.js/npm, install the package, and verify the installation.
|
|
41
|
+
```bash
|
|
42
|
+
beacon config --url <url> --key <key>
|
|
43
|
+
beacon run
|
|
44
|
+
```
|
|
59
45
|
|
|
60
46
|
## Usage
|
|
61
47
|
|
|
@@ -115,34 +101,22 @@ beacon install
|
|
|
115
101
|
|
|
116
102
|
- Start the daemon using npm script: `npm start` or `bun run start`
|
|
117
103
|
- 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
|
|
104
|
+
- Build the distributable: `npm run build:simple` (compiles TypeScript to `dist/`)
|
|
121
105
|
|
|
122
|
-
|
|
106
|
+
## Building & distribution
|
|
123
107
|
|
|
124
|
-
|
|
108
|
+
### Build the package
|
|
125
109
|
|
|
126
110
|
```bash
|
|
127
111
|
npm run build:simple
|
|
128
112
|
```
|
|
129
113
|
|
|
130
|
-
###
|
|
114
|
+
### Publish to npm
|
|
131
115
|
|
|
132
|
-
|
|
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:
|
|
116
|
+
From this directory, after bumping `version` in `package.json`:
|
|
139
117
|
|
|
140
118
|
```bash
|
|
141
|
-
|
|
119
|
+
npm publish --access public
|
|
142
120
|
```
|
|
143
121
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
npm install -g @agentsoc/beacon
|
|
148
|
-
```
|
|
122
|
+
Public install entrypoints live under **`https://agentsoc.com/connectors/`** (`beacon.sh`, `beacon-install.ps1`, `beacon-install.bat`).
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Command } from "commander";
|
|
|
3
3
|
import { loadConfig, saveConfig } from "./config.js";
|
|
4
4
|
import { runBeacon } from "./run-beacon.js";
|
|
5
5
|
import { installService } from "./service-install.js";
|
|
6
|
+
import { probeServiceStatus } from "./service-status.js";
|
|
7
|
+
import { getStatsFilePath, readStats } from "./stats.js";
|
|
6
8
|
const program = new Command();
|
|
7
9
|
program
|
|
8
10
|
.name("beacon")
|
|
@@ -43,6 +45,39 @@ program
|
|
|
43
45
|
process.exit(1);
|
|
44
46
|
}
|
|
45
47
|
});
|
|
48
|
+
program
|
|
49
|
+
.command("status")
|
|
50
|
+
.description("Show service state and forwarding statistics")
|
|
51
|
+
.action(async () => {
|
|
52
|
+
const svc = probeServiceStatus();
|
|
53
|
+
const stats = await readStats();
|
|
54
|
+
const statsPath = getStatsFilePath();
|
|
55
|
+
console.log("\n[beacon] Status\n");
|
|
56
|
+
console.log(` Service (${svc.manager}): ${svc.installed ? svc.stateLabel : "not installed"}`);
|
|
57
|
+
if (svc.active === true)
|
|
58
|
+
console.log(" Running: yes");
|
|
59
|
+
else if (svc.active === false)
|
|
60
|
+
console.log(" Running: no");
|
|
61
|
+
else
|
|
62
|
+
console.log(" Running: n/a");
|
|
63
|
+
if (svc.detail)
|
|
64
|
+
console.log(` Note: ${svc.detail}`);
|
|
65
|
+
console.log("\n Forwarding (from this machine's beacon stats file):");
|
|
66
|
+
console.log(` Stats file: ${statsPath}`);
|
|
67
|
+
console.log(` Log entries forwarded (successful): ${stats.logsForwarded}`);
|
|
68
|
+
console.log(` Successful batches: ${stats.batchesSucceeded}`);
|
|
69
|
+
console.log(` Failed batches: ${stats.batchesFailed}`);
|
|
70
|
+
if (stats.updatedAt && stats.updatedAt !== new Date(0).toISOString()) {
|
|
71
|
+
console.log(` Last stats update: ${stats.updatedAt}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(" Last stats update: (never - no successful forwards yet)");
|
|
75
|
+
}
|
|
76
|
+
if (stats.lastError) {
|
|
77
|
+
console.log(` Last error: ${stats.lastError}`);
|
|
78
|
+
}
|
|
79
|
+
console.log("\n If the service runs as another user (e.g. root), stats may be under that user's config directory.\n");
|
|
80
|
+
});
|
|
46
81
|
program
|
|
47
82
|
.command("run")
|
|
48
83
|
.description("Run the forwarder daemon (foreground)")
|
package/dist/forwarder.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordFailedBatch, recordSuccessfulBatch } from "./stats.js";
|
|
1
2
|
function parseSyslogPri(line) {
|
|
2
3
|
// Default mapping if line does not start with <PRI>
|
|
3
4
|
let severity = 5; // notice (maps to 'low' severity in our logic)
|
|
@@ -114,14 +115,18 @@ export class Forwarder {
|
|
|
114
115
|
});
|
|
115
116
|
if (!res.ok) {
|
|
116
117
|
const text = await res.text();
|
|
118
|
+
const msg = `HTTP ${res.status} ${text}`;
|
|
117
119
|
console.error(`[syslog-beacon] Failed to push logs: HTTP ${res.status} ${text}`);
|
|
120
|
+
await recordFailedBatch(msg);
|
|
118
121
|
}
|
|
119
122
|
else {
|
|
120
123
|
console.log(`[syslog-beacon] Successfully pushed ${payload.length} logs`);
|
|
124
|
+
await recordSuccessfulBatch(payload.length);
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
catch (err) {
|
|
124
128
|
console.error(`[syslog-beacon] Ingest error:`, err);
|
|
129
|
+
await recordFailedBatch(err instanceof Error ? err.message : String(err));
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
132
|
async stop() {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
function trimDetail(s, max = 200) {
|
|
4
|
+
const one = s.replace(/\s+/g, " ").trim();
|
|
5
|
+
return one.length <= max ? one : `${one.slice(0, max)}…`;
|
|
6
|
+
}
|
|
7
|
+
export function probeServiceStatus() {
|
|
8
|
+
if (process.platform === "linux") {
|
|
9
|
+
const unitPath = "/etc/systemd/system/syslog-beacon.service";
|
|
10
|
+
const installed = fs.existsSync(unitPath);
|
|
11
|
+
if (!installed) {
|
|
12
|
+
return {
|
|
13
|
+
manager: "systemd",
|
|
14
|
+
installed: false,
|
|
15
|
+
active: null,
|
|
16
|
+
stateLabel: "not installed",
|
|
17
|
+
detail: "No unit at /etc/systemd/system/syslog-beacon.service",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const out = execFileSync("systemctl", ["is-active", "syslog-beacon"], {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
24
|
+
}).trim();
|
|
25
|
+
const active = out === "active";
|
|
26
|
+
return {
|
|
27
|
+
manager: "systemd",
|
|
28
|
+
installed: true,
|
|
29
|
+
active,
|
|
30
|
+
stateLabel: out || "unknown",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const status = err.status;
|
|
35
|
+
const stderr = err.stderr?.toString?.() ?? "";
|
|
36
|
+
if (status === 3) {
|
|
37
|
+
return {
|
|
38
|
+
manager: "systemd",
|
|
39
|
+
installed: true,
|
|
40
|
+
active: false,
|
|
41
|
+
stateLabel: "inactive",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
manager: "systemd",
|
|
46
|
+
installed: true,
|
|
47
|
+
active: false,
|
|
48
|
+
stateLabel: "unknown",
|
|
49
|
+
detail: trimDetail(stderr || String(err)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (process.platform === "darwin") {
|
|
54
|
+
const plistPath = "/Library/LaunchDaemons/com.agentsoc.syslog-beacon.plist";
|
|
55
|
+
const installed = fs.existsSync(plistPath);
|
|
56
|
+
if (!installed) {
|
|
57
|
+
return {
|
|
58
|
+
manager: "launchd",
|
|
59
|
+
installed: false,
|
|
60
|
+
active: null,
|
|
61
|
+
stateLabel: "not installed",
|
|
62
|
+
detail: `No plist at ${plistPath}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const out = execFileSync("launchctl", ["print", "system/com.agentsoc.syslog-beacon"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
67
|
+
const m = out.match(/^\s*state\s*=\s*(\S+)/m);
|
|
68
|
+
const state = m?.[1] ?? "unknown";
|
|
69
|
+
const active = state === "running";
|
|
70
|
+
return {
|
|
71
|
+
manager: "launchd",
|
|
72
|
+
installed: true,
|
|
73
|
+
active,
|
|
74
|
+
stateLabel: state,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const stderr = err.stderr?.toString?.() ?? "";
|
|
79
|
+
return {
|
|
80
|
+
manager: "launchd",
|
|
81
|
+
installed: true,
|
|
82
|
+
active: null,
|
|
83
|
+
stateLabel: "unknown",
|
|
84
|
+
detail: trimDetail(stderr || String(err)),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
manager: "none",
|
|
90
|
+
installed: false,
|
|
91
|
+
active: null,
|
|
92
|
+
stateLabel: "unsupported",
|
|
93
|
+
detail: "Automatic service detection is only available on Linux (systemd) and macOS (launchd).",
|
|
94
|
+
};
|
|
95
|
+
}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getConfigDir } from "./config.js";
|
|
4
|
+
const STATS_VERSION = 1;
|
|
5
|
+
const emptyStats = () => ({
|
|
6
|
+
version: STATS_VERSION,
|
|
7
|
+
updatedAt: new Date(0).toISOString(),
|
|
8
|
+
logsForwarded: 0,
|
|
9
|
+
batchesSucceeded: 0,
|
|
10
|
+
batchesFailed: 0,
|
|
11
|
+
});
|
|
12
|
+
export function getStatsFilePath() {
|
|
13
|
+
return path.join(getConfigDir(), "stats.json");
|
|
14
|
+
}
|
|
15
|
+
export async function readStats() {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(getStatsFilePath(), "utf8");
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (parsed.version !== STATS_VERSION)
|
|
20
|
+
return emptyStats();
|
|
21
|
+
return {
|
|
22
|
+
...emptyStats(),
|
|
23
|
+
...parsed,
|
|
24
|
+
version: STATS_VERSION,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return emptyStats();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function writeStatsAtomic(stats) {
|
|
32
|
+
const file = getStatsFilePath();
|
|
33
|
+
const dir = path.dirname(file);
|
|
34
|
+
await fs.mkdir(dir, { recursive: true });
|
|
35
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
36
|
+
await fs.writeFile(tmp, JSON.stringify(stats, null, 2), "utf8");
|
|
37
|
+
await fs.rename(tmp, file);
|
|
38
|
+
}
|
|
39
|
+
export async function recordSuccessfulBatch(logCount) {
|
|
40
|
+
try {
|
|
41
|
+
const cur = await readStats();
|
|
42
|
+
await writeStatsAtomic({
|
|
43
|
+
version: STATS_VERSION,
|
|
44
|
+
updatedAt: new Date().toISOString(),
|
|
45
|
+
logsForwarded: cur.logsForwarded + logCount,
|
|
46
|
+
batchesSucceeded: cur.batchesSucceeded + 1,
|
|
47
|
+
batchesFailed: cur.batchesFailed,
|
|
48
|
+
lastError: undefined,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// stats are best-effort; never break forwarding
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function recordFailedBatch(message) {
|
|
56
|
+
try {
|
|
57
|
+
const cur = await readStats();
|
|
58
|
+
const trimmed = message.replace(/\s+/g, " ").slice(0, 500);
|
|
59
|
+
await writeStatsAtomic({
|
|
60
|
+
version: STATS_VERSION,
|
|
61
|
+
updatedAt: new Date().toISOString(),
|
|
62
|
+
logsForwarded: cur.logsForwarded,
|
|
63
|
+
batchesSucceeded: cur.batchesSucceeded,
|
|
64
|
+
batchesFailed: cur.batchesFailed + 1,
|
|
65
|
+
lastError: trimmed || "Unknown error",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// best-effort
|
|
70
|
+
}
|
|
71
|
+
}
|