@agentsoc/beacon 0.0.2 → 0.0.4

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 CHANGED
@@ -39,7 +39,7 @@ npm install -g .
39
39
  Then use `beacon` / `syslog-beacon` like a normal global install:
40
40
 
41
41
  ```bash
42
- beacon config --url <url> --key <key>
42
+ beacon config --key <key> [--env production|development]
43
43
  beacon run
44
44
  ```
45
45
 
@@ -52,21 +52,22 @@ The CLI provides the following commands:
52
52
  Configure the AgentSOC connection (saves to a local configuration file):
53
53
 
54
54
  ```bash
55
- beacon config --url <url> --key <key>
55
+ beacon config --key <key> [--env production|development]
56
56
  ```
57
57
 
58
- **Required Parameters:**
58
+ **Parameters:**
59
59
 
60
- - `--url, -u <url>`: AgentSOC ingest URL (e.g., `https://api.agentsoc.com/ingest`)
61
- - `--key, -k <key>`: AgentSOC API Key (e.g., `sk_1234567890abcdef`)
60
+ - `--key, -k <key>`: AgentSOC API Key (required; e.g., `sk_1234567890abcdef`)
61
+ - `--env, -e <env>`: `production` (default) or `development` — selects default URLs for **log ingest** and for **`beacon status`** (platform API). Production: ingest `https://ingest.agentsoc.com/api/v1/webhooks/syslog`, platform `https://api.agentsoc.com`. Development: ingest `http://localhost:8110/api/v1/webhooks/syslog`, platform `http://localhost:8100`.
62
62
 
63
- **Example:**
63
+ **Examples:**
64
64
 
65
65
  ```bash
66
- beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef
66
+ beacon config --key sk_1234567890abcdef
67
+ beacon config --key sk_1234567890abcdef --env development
67
68
  ```
68
69
 
69
- **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.
70
+ **Note:** If you run `bun run src/cli.ts config` without `--key`, you'll receive usage help. URLs are not set in `config.json` (use `--env` or `AGENTSOC_ENV`). At runtime you can override **ingest** with `AGENTSOC_INGEST_URL` and the **platform API** (for `beacon status` only) with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL`.
70
71
 
71
72
  ### Run Foreground Daemon
72
73
 
@@ -87,16 +88,32 @@ beacon run [options]
87
88
  beacon run --batch-size 50 --flush-ms 5000
88
89
  ```
89
90
 
90
- _Note: You can also use environment variables `AGENTSOC_API_KEY` and `AGENTSOC_INGEST_URL` instead of running the `config` command._
91
+ _Note: You can use `AGENTSOC_API_KEY` instead of saving a key in config. Use `AGENTSOC_ENV` (`production` / `development`) to override the saved environment for a single run. `AGENTSOC_INGEST_URL` overrides log shipping only. For `beacon status`, the CLI calls `GET {platform}/api/v1/siem/beacon/validate/key` (default platform from env above); override the platform origin with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL` if ingest and API live on different hosts._
91
92
 
92
93
  ### Install Service
93
94
 
94
95
  Install the background service daemon (systemd on Linux, launchd on macOS):
95
96
 
96
97
  ```bash
97
- beacon install
98
+ sudo beacon install
99
+ ```
100
+
101
+ On macOS the plist is written under `/Library/LaunchDaemons/`, and on Linux the unit file goes under `/etc/systemd/system/`—both require root, so use `sudo`. If `sudo` cannot find `beacon` (for example with nvm), run `sudo env "PATH=$PATH" beacon install` or invoke the CLI with the full path to the global binary.
102
+
103
+ ### Status and stats
104
+
105
+ Show whether the systemd/launchd service is present, resolve **organization name and API key label** via the platform API (`GET /api/v1/siem/beacon/validate/key` using your ingest key), and print forwarding counters from the local stats file:
106
+
107
+ ```bash
108
+ beacon status
98
109
  ```
99
110
 
111
+ Use `beacon status --json` for machine-readable output (includes `validateKeyUrl` on errors).
112
+
113
+ After successful batches, the CLI updates **`stats.json`** next to your config (same config directory as `config.json`—for example `~/Library/Application Support/agentsoc-beacon/` on macOS). The file tracks `logsForwarded`, `batchesSucceeded`, `batchesFailed`, optional `lastError`, and `updatedAt`. If the beacon runs as another user (e.g. root), that user’s config directory holds the stats file.
114
+
115
+ The marketing site’s product demo terminal runs through **`beacon status`** so visitors can see a sample of this output.
116
+
100
117
  ## Development
101
118
 
102
119
  - Start the daemon using npm script: `npm start` or `bun run start`
@@ -0,0 +1,14 @@
1
+ export interface BeaconContextOk {
2
+ ok: true;
3
+ organizationName: string | null;
4
+ organizationSlug: string | null;
5
+ apiKeyName: string;
6
+ }
7
+ export interface BeaconContextErr {
8
+ ok: false;
9
+ message: string;
10
+ }
11
+ export type BeaconContextResult = BeaconContextOk | BeaconContextErr;
12
+ /** Calls platform `GET /api/v1/siem/beacon/validate/key` with the ingest API key. */
13
+ export declare function fetchBeaconContext(validateKeyUrl: string, apiKey: string): Promise<BeaconContextResult>;
14
+ //# sourceMappingURL=beacon-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-context.d.ts","sourceRoot":"","sources":["../src/beacon-context.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAWrE,qFAAqF;AACrF,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC,CAgD9B"}
@@ -0,0 +1,49 @@
1
+ /** Calls platform `GET /api/v1/siem/beacon/validate/key` with the ingest API key. */
2
+ export async function fetchBeaconContext(validateKeyUrl, apiKey) {
3
+ const controller = new AbortController();
4
+ const t = setTimeout(() => controller.abort(), 12_000);
5
+ try {
6
+ const res = await fetch(validateKeyUrl, {
7
+ method: "GET",
8
+ headers: { "X-API-Key": apiKey },
9
+ signal: controller.signal,
10
+ });
11
+ const text = await res.text();
12
+ let body;
13
+ try {
14
+ body = text ? JSON.parse(text) : null;
15
+ }
16
+ catch {
17
+ return { ok: false, message: "Invalid response from platform API" };
18
+ }
19
+ if (!res.ok) {
20
+ const err = body;
21
+ const msg = err && typeof err.error === "string"
22
+ ? err.error
23
+ : `HTTP ${res.status}`;
24
+ return { ok: false, message: msg };
25
+ }
26
+ const data = body;
27
+ if (!data?.success || !data.apiKey?.name) {
28
+ return { ok: false, message: "Unexpected response from platform API" };
29
+ }
30
+ return {
31
+ ok: true,
32
+ organizationName: data.organization?.name ?? null,
33
+ organizationSlug: data.organization?.slug ?? null,
34
+ apiKeyName: data.apiKey.name,
35
+ };
36
+ }
37
+ catch (e) {
38
+ if (e instanceof Error && e.name === "AbortError") {
39
+ return { ok: false, message: "Request timed out" };
40
+ }
41
+ return {
42
+ ok: false,
43
+ message: e instanceof Error ? e.message : String(e),
44
+ };
45
+ }
46
+ finally {
47
+ clearTimeout(t);
48
+ }
49
+ }
@@ -0,0 +1,23 @@
1
+ export type BeaconUpdateInfo = {
2
+ success: boolean;
3
+ package: string;
4
+ latestVersion: string | null;
5
+ registryResolved: boolean;
6
+ install: {
7
+ npm: string;
8
+ bun: string;
9
+ pnpm: string;
10
+ };
11
+ docsUrl: string;
12
+ installScriptUrl: string;
13
+ };
14
+ export type BeaconUpdateFetchResult = {
15
+ ok: true;
16
+ info: BeaconUpdateInfo;
17
+ } | {
18
+ ok: false;
19
+ message: string;
20
+ };
21
+ /** Latest @agentsoc/beacon version from the public npm registry (no AgentSOC API). */
22
+ export declare function fetchBeaconUpdateFromNpm(): Promise<BeaconUpdateFetchResult>;
23
+ //# sourceMappingURL=beacon-update-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-update-info.d.ts","sourceRoot":"","sources":["../src/beacon-update-info.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACpC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBnC,sFAAsF;AACtF,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAyCjF"}
@@ -0,0 +1,62 @@
1
+ const BEACON_PACKAGE = "@agentsoc/beacon";
2
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(BEACON_PACKAGE)}`;
3
+ const DOC_URL = "https://agentsoc.com/products/agentsoc-beacon";
4
+ const INSTALL_SCRIPT_URL = "https://agentsoc.com/connectors/beacon.sh";
5
+ function buildInfo(latestVersion, registryResolved) {
6
+ return {
7
+ success: true,
8
+ package: BEACON_PACKAGE,
9
+ latestVersion,
10
+ registryResolved,
11
+ install: {
12
+ npm: `npm install -g ${BEACON_PACKAGE}@latest`,
13
+ bun: `bun add -g ${BEACON_PACKAGE}@latest`,
14
+ pnpm: `pnpm add -g ${BEACON_PACKAGE}@latest`,
15
+ },
16
+ docsUrl: DOC_URL,
17
+ installScriptUrl: INSTALL_SCRIPT_URL,
18
+ };
19
+ }
20
+ /** Latest @agentsoc/beacon version from the public npm registry (no AgentSOC API). */
21
+ export async function fetchBeaconUpdateFromNpm() {
22
+ const controller = new AbortController();
23
+ const t = setTimeout(() => controller.abort(), 12_000);
24
+ try {
25
+ const res = await fetch(NPM_REGISTRY_URL, {
26
+ headers: { Accept: "application/json" },
27
+ signal: controller.signal,
28
+ });
29
+ const text = await res.text();
30
+ let body;
31
+ try {
32
+ body = text ? JSON.parse(text) : null;
33
+ }
34
+ catch {
35
+ return { ok: false, message: "Invalid JSON from registry.npmjs.org" };
36
+ }
37
+ if (!res.ok) {
38
+ return {
39
+ ok: false,
40
+ message: `registry.npmjs.org returned HTTP ${res.status}`,
41
+ };
42
+ }
43
+ const data = body;
44
+ const latest = data["dist-tags"]?.latest?.trim() ?? null;
45
+ return {
46
+ ok: true,
47
+ info: buildInfo(latest, true),
48
+ };
49
+ }
50
+ catch (e) {
51
+ if (e instanceof Error && e.name === "AbortError") {
52
+ return { ok: false, message: "Request timed out" };
53
+ }
54
+ return {
55
+ ok: false,
56
+ message: e instanceof Error ? e.message : String(e),
57
+ };
58
+ }
59
+ finally {
60
+ clearTimeout(t);
61
+ }
62
+ }
@@ -0,0 +1,5 @@
1
+ /** Resolve @agentsoc/beacon version from the installed package.json (works for global npm install). */
2
+ export declare function readBeaconCliVersion(): string;
3
+ /** Simple semver compare for numeric x.y.z (ignores prerelease tail for ordering). */
4
+ export declare function semverLessThan(a: string, b: string): boolean;
5
+ //# sourceMappingURL=beacon-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-version.d.ts","sourceRoot":"","sources":["../src/beacon-version.ts"],"names":[],"mappings":"AAIA,uGAAuG;AACvG,wBAAgB,oBAAoB,IAAI,MAAM,CAsB7C;AAOD,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAW5D"}
@@ -0,0 +1,45 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /** Resolve @agentsoc/beacon version from the installed package.json (works for global npm install). */
5
+ export function readBeaconCliVersion() {
6
+ let dir = dirname(fileURLToPath(import.meta.url));
7
+ for (let i = 0; i < 8; i++) {
8
+ const pkgPath = join(dir, "package.json");
9
+ if (existsSync(pkgPath)) {
10
+ try {
11
+ const j = JSON.parse(readFileSync(pkgPath, "utf8"));
12
+ if (j.name === "@agentsoc/beacon" && typeof j.version === "string") {
13
+ return j.version;
14
+ }
15
+ }
16
+ catch {
17
+ /* try parent */
18
+ }
19
+ }
20
+ const parent = dirname(dir);
21
+ if (parent === dir)
22
+ break;
23
+ dir = parent;
24
+ }
25
+ return "0.0.0";
26
+ }
27
+ function stripPrerelease(v) {
28
+ const i = v.indexOf("-");
29
+ return i === -1 ? v : v.slice(0, i);
30
+ }
31
+ /** Simple semver compare for numeric x.y.z (ignores prerelease tail for ordering). */
32
+ export function semverLessThan(a, b) {
33
+ const pa = stripPrerelease(a).split(".").map((x) => parseInt(x, 10) || 0);
34
+ const pb = stripPrerelease(b).split(".").map((x) => parseInt(x, 10) || 0);
35
+ const n = Math.max(pa.length, pb.length);
36
+ for (let i = 0; i < n; i++) {
37
+ const da = pa[i] ?? 0;
38
+ const db = pb[i] ?? 0;
39
+ if (da < db)
40
+ return true;
41
+ if (da > db)
42
+ return false;
43
+ }
44
+ return false;
45
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js CHANGED
@@ -1,37 +1,56 @@
1
1
  #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
2
3
  import { Command } from "commander";
3
- import { loadConfig, saveConfig } from "./config.js";
4
+ import { BEACON_CONFIG_REQUIRED_MESSAGE, coerceBeaconEnvironment, loadConfig, parseBeaconEnvironment, resolveBeaconApiKey, resolveSiemBeaconValidateKeyUrl, resolveIngestUrl, saveConfig, } from "./config.js";
5
+ import { fetchBeaconContext } from "./beacon-context.js";
6
+ import { fetchBeaconUpdateFromNpm } from "./beacon-update-info.js";
7
+ import { readBeaconCliVersion, semverLessThan } from "./beacon-version.js";
4
8
  import { runBeacon } from "./run-beacon.js";
5
9
  import { installService } from "./service-install.js";
6
10
  import { probeServiceStatus } from "./service-status.js";
7
11
  import { getStatsFilePath, readStats } from "./stats.js";
12
+ const BEACON_VERSION = readBeaconCliVersion();
8
13
  const program = new Command();
9
14
  program
10
15
  .name("beacon")
11
16
  .description("AgentSOC Syslog Beacon - Lightweight background log forwarder")
12
- .version("1.0.0", "-v, --version");
17
+ .version(BEACON_VERSION, "-v, --version");
13
18
  program
14
19
  .command("config")
15
20
  .description("Configure the AgentSOC connection")
16
- .option("-u, --url <url>", "AgentSOC ingest URL")
21
+ .option("-e, --env <env>", "Environment: production (default) or development")
17
22
  .option("-k, --key <key>", "AgentSOC API Key")
18
23
  .action(async (options) => {
19
- if (!options.url || !options.key) {
20
- console.log("\n[beacon] Missing required options.\n");
21
- console.log("Usage: beacon config --url <url> --key <key>\n");
24
+ if (!options.key) {
25
+ console.log("\n[beacon] Missing required option.\n");
26
+ console.log("Usage: beacon config --key <key> [--env production|development]\n");
22
27
  console.log("Examples:");
23
- console.log(" bun run src/cli.ts config -u https://api.agentsoc.com/ingest -k sk_1234567890abcdef");
24
- console.log(" beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef\n");
28
+ console.log(" beacon config --key sk_1234567890abcdef");
29
+ console.log(" beacon config --key sk_1234567890abcdef --env development\n");
25
30
  console.log("Options:");
26
- console.log(" -u, --url <url> AgentSOC ingest URL");
27
- console.log(" -k, --key <key> AgentSOC API Key\n");
31
+ console.log(" -k, --key <key> AgentSOC API Key (required)");
32
+ console.log(" -e, --env <env> production (default) or development; sets ingest + platform API defaults\n");
33
+ process.exit(1);
34
+ }
35
+ const existing = await loadConfig();
36
+ let environment;
37
+ try {
38
+ environment =
39
+ options.env !== undefined
40
+ ? parseBeaconEnvironment(options.env)
41
+ : (coerceBeaconEnvironment(existing.environment) ?? "production");
42
+ }
43
+ catch (e) {
44
+ console.error("[beacon]", e instanceof Error ? e.message : String(e));
28
45
  process.exit(1);
29
46
  }
30
47
  await saveConfig({
31
- ingestUrl: options.url,
32
48
  apiKey: options.key,
49
+ environment,
50
+ agentId: existing.agentId,
33
51
  });
34
52
  console.log("[beacon] Config saved.");
53
+ console.log(`[beacon] Environment: ${environment}`);
35
54
  });
36
55
  program
37
56
  .command("install")
@@ -41,42 +60,157 @@ program
41
60
  await installService();
42
61
  }
43
62
  catch (err) {
44
- console.error("[beacon] Install failed:", err);
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ console.error(`[beacon] Install failed:\n\n${message}\n`);
45
65
  process.exit(1);
46
66
  }
47
67
  });
48
68
  program
49
69
  .command("status")
50
70
  .description("Show service state and forwarding statistics")
51
- .action(async () => {
71
+ .option("--json", "Print machine-readable JSON on stdout")
72
+ .action(async (opts) => {
52
73
  const svc = probeServiceStatus();
53
74
  const stats = await readStats();
54
75
  const statsPath = getStatsFilePath();
55
- console.log("\n[beacon] Status\n");
56
- console.log(` Service (${svc.manager}): ${svc.installed ? svc.stateLabel : "not installed"}`);
76
+ const cfg = await loadConfig();
77
+ const apiKey = resolveBeaconApiKey(cfg);
78
+ const validateKeyUrl = resolveSiemBeaconValidateKeyUrl(cfg);
79
+ const context = apiKey
80
+ ? await fetchBeaconContext(validateKeyUrl, apiKey)
81
+ : ({
82
+ ok: false,
83
+ message: BEACON_CONFIG_REQUIRED_MESSAGE,
84
+ });
85
+ if (opts.json) {
86
+ console.log(JSON.stringify({
87
+ service: svc,
88
+ stats,
89
+ statsPath,
90
+ validateKeyUrl,
91
+ context: context.ok
92
+ ? {
93
+ organizationName: context.organizationName,
94
+ organizationSlug: context.organizationSlug,
95
+ apiKeyName: context.apiKeyName,
96
+ }
97
+ : { error: context.message },
98
+ }, null, 2));
99
+ return;
100
+ }
101
+ const labelW = 26;
102
+ const line = (label, value) => {
103
+ console.log(` ${label.padEnd(labelW)}${value}`);
104
+ };
105
+ console.log("\n AgentSOC Beacon — Status\n");
106
+ console.log(" Account");
107
+ if (context.ok) {
108
+ line("Organization", context.organizationName ??
109
+ "(name unavailable — check platform API / database)");
110
+ line("API key", context.apiKeyName);
111
+ }
112
+ else {
113
+ line("Connection", context.message);
114
+ }
115
+ console.log("\n Service");
116
+ line(`Daemon (${svc.manager})`, svc.installed ? svc.stateLabel : "not installed");
57
117
  if (svc.active === true)
58
- console.log(" Running: yes");
118
+ line("Running", "yes");
59
119
  else if (svc.active === false)
60
- console.log(" Running: no");
120
+ line("Running", "no");
61
121
  else
62
- console.log(" Running: n/a");
122
+ line("Running", "n/a");
63
123
  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}`);
124
+ line("Note", svc.detail);
125
+ console.log("\n Forwarding (this machine)");
126
+ line("Log entries forwarded", String(stats.logsForwarded));
127
+ line("Successful batches", String(stats.batchesSucceeded));
128
+ line("Failed batches", String(stats.batchesFailed));
70
129
  if (stats.updatedAt && stats.updatedAt !== new Date(0).toISOString()) {
71
- console.log(` Last stats update: ${stats.updatedAt}`);
130
+ line("Last stats update", stats.updatedAt);
72
131
  }
73
132
  else {
74
- console.log(" Last stats update: (never - no successful forwards yet)");
133
+ line("Last stats update", "(none yet)");
75
134
  }
76
135
  if (stats.lastError) {
77
- console.log(` Last error: ${stats.lastError}`);
136
+ line("Last error", stats.lastError);
137
+ }
138
+ console.log();
139
+ });
140
+ program
141
+ .command("update")
142
+ .description("Check for a newer Beacon CLI and optionally install it")
143
+ .option("--json", "Print machine-readable JSON on stdout")
144
+ .option("-y, --yes", "Run npm install -g @agentsoc/beacon@latest (non-interactive upgrade)")
145
+ .action(async (opts) => {
146
+ const current = BEACON_VERSION;
147
+ const fetched = await fetchBeaconUpdateFromNpm();
148
+ if (opts.json) {
149
+ const latest = fetched.ok ? fetched.info.latestVersion : null;
150
+ const outdated = latest != null &&
151
+ latest.length > 0 &&
152
+ semverLessThan(current, latest);
153
+ console.log(JSON.stringify({
154
+ currentVersion: current,
155
+ registry: "https://registry.npmjs.org/@agentsoc%2fbeacon",
156
+ remote: fetched.ok ? fetched.info : null,
157
+ error: fetched.ok ? undefined : fetched.message,
158
+ updateAvailable: fetched.ok ? outdated : null,
159
+ }, null, 2));
160
+ if (!fetched.ok)
161
+ process.exit(1);
162
+ return;
163
+ }
164
+ if (!fetched.ok) {
165
+ console.error(`[beacon] Could not reach npm registry: ${fetched.message}`);
166
+ console.error("[beacon] You can still run: npm install -g @agentsoc/beacon@latest");
167
+ process.exit(1);
168
+ }
169
+ const { info } = fetched;
170
+ const latest = info.latestVersion;
171
+ console.log("\n AgentSOC Beacon — Update check\n");
172
+ console.log(` This install ${current}`);
173
+ if (latest) {
174
+ console.log(` Latest on registry ${latest}`);
175
+ }
176
+ else {
177
+ console.log(" Latest on registry (unavailable — unexpected npm response)");
178
+ }
179
+ const canCompare = Boolean(latest && latest.length > 0);
180
+ const outdated = canCompare && semverLessThan(current, latest);
181
+ if (canCompare && !outdated) {
182
+ console.log("\n You are on the latest published version.\n");
183
+ return;
184
+ }
185
+ if (canCompare && outdated) {
186
+ console.log("\n A newer version is available.\n");
187
+ }
188
+ else {
189
+ console.log("\n Compare versions manually, or reinstall with:\n");
190
+ }
191
+ console.log(` ${info.install.npm}`);
192
+ console.log(` ${info.install.bun}`);
193
+ console.log(` ${info.install.pnpm}`);
194
+ if (info.docsUrl) {
195
+ console.log(`\n Docs: ${info.docsUrl}`);
196
+ }
197
+ if (opts.yes) {
198
+ if (!outdated && canCompare) {
199
+ console.log("\n[beacon] Already up to date — skipping install.\n");
200
+ return;
201
+ }
202
+ console.log("\n[beacon] Running npm install -g @agentsoc/beacon@latest …\n");
203
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
204
+ try {
205
+ execFileSync(npm, ["install", "-g", "@agentsoc/beacon@latest"], { stdio: "inherit", env: process.env });
206
+ console.log("\n[beacon] Update finished. Run `beacon --version` to verify.\n");
207
+ }
208
+ catch {
209
+ process.exit(1);
210
+ }
211
+ return;
78
212
  }
79
- console.log("\n If the service runs as another user (e.g. root), stats may be under that user's config directory.\n");
213
+ console.log("\n To upgrade now, run the same command with --yes\n");
80
214
  });
81
215
  program
82
216
  .command("run")
@@ -85,10 +219,10 @@ program
85
219
  .option("--flush-ms <n>", "Flush interval in ms", "2000")
86
220
  .action(async (options) => {
87
221
  const config = await loadConfig();
88
- const apiKey = process.env.AGENTSOC_API_KEY || config.apiKey;
89
- const ingestUrl = process.env.AGENTSOC_INGEST_URL || config.ingestUrl;
90
- if (!apiKey || !ingestUrl) {
91
- console.error("[beacon] Missing apiKey or ingestUrl. Run `beacon config` or set ENV vars.");
222
+ const apiKey = resolveBeaconApiKey(config);
223
+ const ingestUrl = resolveIngestUrl(config);
224
+ if (!apiKey) {
225
+ console.error(`[beacon] ${BEACON_CONFIG_REQUIRED_MESSAGE}`);
92
226
  process.exit(1);
93
227
  }
94
228
  console.log("[beacon] Starting in foreground...");
@@ -0,0 +1,45 @@
1
+ export declare const INGEST_URL_PRODUCTION = "https://ingest.agentsoc.com/api/v1/webhooks/syslog";
2
+ export declare const INGEST_URL_DEVELOPMENT = "http://localhost:8110/api/v1/webhooks/syslog";
3
+ /** Platform API (for `beacon status` key validation — not log ingest). */
4
+ export declare const PLATFORM_API_PRODUCTION = "https://api.agentsoc.com";
5
+ export declare const PLATFORM_API_DEVELOPMENT = "http://localhost:8100";
6
+ /** Path on the platform API for SIEM ingest key validation (`beacon status`). */
7
+ export declare const SIEM_BEACON_VALIDATE_KEY_PATH = "/api/v1/siem/beacon/validate/key";
8
+ export type BeaconEnvironment = "production" | "development";
9
+ /** API key from saved config or process environment (trimmed). */
10
+ export declare function resolveBeaconApiKey(config: BeaconConfig): string | undefined;
11
+ /** Shown when run/install needs a key and none is configured. */
12
+ export declare const BEACON_CONFIG_REQUIRED_MESSAGE = "Beacon is not configured. Run: beacon config --key <your-api-key> [--env production|development]. Or set AGENTSOC_API_KEY in your environment.";
13
+ export interface BeaconConfig {
14
+ apiKey?: string;
15
+ /** @deprecated Resolved from environment; not set by the CLI */
16
+ ingestUrl?: string;
17
+ /** Defaults to production when omitted */
18
+ environment?: BeaconEnvironment;
19
+ agentId?: string;
20
+ }
21
+ export declare function parseBeaconEnvironment(input: string): BeaconEnvironment;
22
+ export declare function coerceBeaconEnvironment(value: unknown): BeaconEnvironment | undefined;
23
+ export declare function ingestUrlForEnvironment(env: BeaconEnvironment): string;
24
+ export declare function effectiveEnvironment(config: BeaconConfig): BeaconEnvironment;
25
+ /**
26
+ * Ingest URL used at runtime. Optional AGENTSOC_INGEST_URL overrides (legacy
27
+ * / advanced); otherwise derived from AGENTSOC_ENV or saved environment.
28
+ */
29
+ export declare function resolveIngestUrl(config: BeaconConfig): string;
30
+ /**
31
+ * Platform API origin (no trailing slash).
32
+ * Override with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL`.
33
+ */
34
+ export declare function resolvePlatformApiBase(config: BeaconConfig): string;
35
+ /**
36
+ * Full URL for `GET …/siem/beacon/validate/key` (organization + API key label for `beacon status`).
37
+ */
38
+ export declare function resolveSiemBeaconValidateKeyUrl(config: BeaconConfig): string;
39
+ /** @deprecated Use {@link resolveSiemBeaconValidateKeyUrl} */
40
+ export declare function resolveBeaconContextUrl(config: BeaconConfig): string;
41
+ export declare function getConfigDir(): string;
42
+ export declare function getConfigFile(): string;
43
+ export declare function loadConfig(): Promise<BeaconConfig>;
44
+ export declare function saveConfig(config: BeaconConfig): Promise<void>;
45
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,uDACoB,CAAC;AAEvD,eAAO,MAAM,sBAAsB,iDACa,CAAC;AAEjD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE,eAAO,MAAM,wBAAwB,0BAA0B,CAAC;AAEhE,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,qCACN,CAAC;AAErC,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE7D,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAK5E;AAED,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,mJACuG,CAAC;AAEnJ,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAOvE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,iBAAiB,GAAG,SAAS,CAU/B;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAItE;AAmBD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAM5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAMnE;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,YAAY,GACnB,MAAM,CAER;AAED,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEpE;AAED,wBAAgB,YAAY,IAAI,MAAM,CAarC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAQxD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAepE"}
package/dist/config.js CHANGED
@@ -1,6 +1,100 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs/promises";
4
+ export const INGEST_URL_PRODUCTION = "https://ingest.agentsoc.com/api/v1/webhooks/syslog";
5
+ export const INGEST_URL_DEVELOPMENT = "http://localhost:8110/api/v1/webhooks/syslog";
6
+ /** Platform API (for `beacon status` key validation — not log ingest). */
7
+ export const PLATFORM_API_PRODUCTION = "https://api.agentsoc.com";
8
+ export const PLATFORM_API_DEVELOPMENT = "http://localhost:8100";
9
+ /** Path on the platform API for SIEM ingest key validation (`beacon status`). */
10
+ export const SIEM_BEACON_VALIDATE_KEY_PATH = "/api/v1/siem/beacon/validate/key";
11
+ /** API key from saved config or process environment (trimmed). */
12
+ export function resolveBeaconApiKey(config) {
13
+ const fromEnv = process.env.AGENTSOC_API_KEY?.trim();
14
+ if (fromEnv)
15
+ return fromEnv;
16
+ const fromFile = config.apiKey?.trim();
17
+ return fromFile || undefined;
18
+ }
19
+ /** Shown when run/install needs a key and none is configured. */
20
+ export const BEACON_CONFIG_REQUIRED_MESSAGE = "Beacon is not configured. Run: beacon config --key <your-api-key> [--env production|development]. Or set AGENTSOC_API_KEY in your environment.";
21
+ export function parseBeaconEnvironment(input) {
22
+ const s = input.toLowerCase().trim();
23
+ if (s === "production" || s === "prod")
24
+ return "production";
25
+ if (s === "development" || s === "dev")
26
+ return "development";
27
+ throw new Error(`Invalid environment "${input}". Use production or development (prod / dev).`);
28
+ }
29
+ export function coerceBeaconEnvironment(value) {
30
+ if (value === "production" || value === "development")
31
+ return value;
32
+ if (typeof value === "string") {
33
+ try {
34
+ return parseBeaconEnvironment(value);
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ return undefined;
41
+ }
42
+ export function ingestUrlForEnvironment(env) {
43
+ return env === "development"
44
+ ? INGEST_URL_DEVELOPMENT
45
+ : INGEST_URL_PRODUCTION;
46
+ }
47
+ function platformApiBaseForEnvironment(env) {
48
+ return env === "development" ? PLATFORM_API_DEVELOPMENT : PLATFORM_API_PRODUCTION;
49
+ }
50
+ function environmentFromProcessEnv() {
51
+ const raw = process.env.AGENTSOC_ENV?.trim();
52
+ if (!raw)
53
+ return undefined;
54
+ try {
55
+ return parseBeaconEnvironment(raw);
56
+ }
57
+ catch {
58
+ console.warn(`[beacon] Ignoring invalid AGENTSOC_ENV="${raw}" (use production or development).`);
59
+ return undefined;
60
+ }
61
+ }
62
+ export function effectiveEnvironment(config) {
63
+ return (environmentFromProcessEnv() ??
64
+ coerceBeaconEnvironment(config.environment) ??
65
+ "production");
66
+ }
67
+ /**
68
+ * Ingest URL used at runtime. Optional AGENTSOC_INGEST_URL overrides (legacy
69
+ * / advanced); otherwise derived from AGENTSOC_ENV or saved environment.
70
+ */
71
+ export function resolveIngestUrl(config) {
72
+ const override = process.env.AGENTSOC_INGEST_URL?.trim();
73
+ if (override)
74
+ return override;
75
+ return ingestUrlForEnvironment(effectiveEnvironment(config));
76
+ }
77
+ /**
78
+ * Platform API origin (no trailing slash).
79
+ * Override with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL`.
80
+ */
81
+ export function resolvePlatformApiBase(config) {
82
+ const override = process.env.AGENTSOC_PLATFORM_URL?.trim() ||
83
+ process.env.AGENTSOC_PLATFORM_API_BASE_URL?.trim();
84
+ if (override)
85
+ return override.replace(/\/$/, "");
86
+ return platformApiBaseForEnvironment(effectiveEnvironment(config));
87
+ }
88
+ /**
89
+ * Full URL for `GET …/siem/beacon/validate/key` (organization + API key label for `beacon status`).
90
+ */
91
+ export function resolveSiemBeaconValidateKeyUrl(config) {
92
+ return `${resolvePlatformApiBase(config)}${SIEM_BEACON_VALIDATE_KEY_PATH}`;
93
+ }
94
+ /** @deprecated Use {@link resolveSiemBeaconValidateKeyUrl} */
95
+ export function resolveBeaconContextUrl(config) {
96
+ return resolveSiemBeaconValidateKeyUrl(config);
97
+ }
4
98
  export function getConfigDir() {
5
99
  const home = os.homedir();
6
100
  if (process.platform === "win32") {
@@ -0,0 +1,9 @@
1
+ export interface HostInfo {
2
+ agentId: string;
3
+ hostname: string;
4
+ ip: string;
5
+ osPlatform: string;
6
+ mac: string;
7
+ }
8
+ export declare function getHostInfo(): Promise<HostInfo>;
9
+ //# sourceMappingURL=enrich.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrich.d.ts","sourceRoot":"","sources":["../src/enrich.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,CA8BrD"}
@@ -0,0 +1,20 @@
1
+ import { HostInfo } from "./enrich.js";
2
+ export interface ForwarderOptions {
3
+ ingestUrl: string;
4
+ apiKey: string;
5
+ batchSize?: number;
6
+ flushMs?: number;
7
+ host: HostInfo;
8
+ }
9
+ export declare class Forwarder {
10
+ private opts;
11
+ private buffer;
12
+ private timer;
13
+ private readonly batchSize;
14
+ private readonly flushMs;
15
+ constructor(opts: ForwarderOptions);
16
+ pushLog(line: string): void;
17
+ private flush;
18
+ stop(): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=forwarder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.d.ts","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;CAChB;AA6ED,qBAAa,SAAS;IAMR,OAAO,CAAC,IAAI;IALxB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEb,IAAI,EAAE,gBAAgB;IAKnC,OAAO,CAAC,IAAI,EAAE,MAAM;YAoCb,KAAK;IA4CN,IAAI;CAKlB"}
@@ -0,0 +1,5 @@
1
+ import { ForwarderOptions } from './forwarder.js';
2
+ export declare function runBeacon(opts: Omit<ForwarderOptions, 'host'>): Promise<{
3
+ stop: () => Promise<void>;
4
+ }>;
5
+ //# sourceMappingURL=run-beacon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-beacon.d.ts","sourceRoot":"","sources":["../src/run-beacon.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC;;GAiBnE"}
@@ -0,0 +1,2 @@
1
+ export declare function installService(): Promise<void>;
2
+ //# sourceMappingURL=service-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-install.d.ts","sourceRoot":"","sources":["../src/service-install.ts"],"names":[],"mappings":"AA2GA,wBAAsB,cAAc,kBAuEnC"}
@@ -1,11 +1,106 @@
1
+ import { constants as fsConstants } from 'node:fs';
1
2
  import fs from 'node:fs/promises';
2
3
  import path from 'node:path';
3
- import { execSync } from 'node:child_process';
4
- import { loadConfig } from './config.js';
4
+ import { execFileSync, execSync } from 'node:child_process';
5
+ import { BEACON_CONFIG_REQUIRED_MESSAGE, loadConfig } from './config.js';
6
+ const PERMISSION_DENIED_HINT = `That location is only writable as root. Run the same command with sudo, for example:
7
+
8
+ sudo beacon install
9
+
10
+ You will be prompted for your administrator password.`;
11
+ function permissionDeniedInstallError(destPath) {
12
+ return new Error(`Cannot install the system service: permission denied for ${path.dirname(destPath)}.\n\n${PERMISSION_DENIED_HINT}`);
13
+ }
14
+ async function assertServiceInstallWritable(destPath) {
15
+ const dir = path.dirname(destPath);
16
+ try {
17
+ await fs.access(dir, fsConstants.W_OK);
18
+ }
19
+ catch (e) {
20
+ const code = e && typeof e === 'object' && 'code' in e
21
+ ? e.code
22
+ : undefined;
23
+ if (code === 'EACCES' || code === 'EPERM') {
24
+ throw permissionDeniedInstallError(destPath);
25
+ }
26
+ throw e;
27
+ }
28
+ }
29
+ async function writeServiceFile(destPath, contents) {
30
+ try {
31
+ await fs.writeFile(destPath, contents, 'utf8');
32
+ }
33
+ catch (e) {
34
+ const code = e && typeof e === 'object' && 'code' in e
35
+ ? e.code
36
+ : undefined;
37
+ if (code === 'EACCES' || code === 'EPERM') {
38
+ throw permissionDeniedInstallError(destPath);
39
+ }
40
+ throw e;
41
+ }
42
+ }
43
+ /** launchd does not use your shell PATH — use an absolute interpreter path. */
44
+ function resolveLaunchdRunner() {
45
+ const isBun = process.execPath.endsWith('bun') || process.execPath.endsWith('bun.exe');
46
+ if (isBun) {
47
+ return process.execPath;
48
+ }
49
+ const base = path.basename(process.execPath);
50
+ if (/^node(\.exe)?$/i.test(base)) {
51
+ return process.execPath;
52
+ }
53
+ try {
54
+ const nodePath = execFileSync('/usr/bin/which', ['node'], {
55
+ encoding: 'utf8',
56
+ }).trim();
57
+ if (nodePath) {
58
+ return nodePath;
59
+ }
60
+ }
61
+ catch {
62
+ // fall through
63
+ }
64
+ return 'node';
65
+ }
66
+ function launchctlBootoutSystem(plistPath, label) {
67
+ try {
68
+ execFileSync('launchctl', ['bootout', 'system', plistPath], {
69
+ stdio: 'ignore',
70
+ });
71
+ return;
72
+ }
73
+ catch {
74
+ // try service target (older registration style)
75
+ }
76
+ try {
77
+ execFileSync('launchctl', ['bootout', `system/${label}`], {
78
+ stdio: 'ignore',
79
+ });
80
+ }
81
+ catch {
82
+ // not loaded yet
83
+ }
84
+ }
85
+ function launchctlBootstrapSystem(plistPath) {
86
+ try {
87
+ execFileSync('launchctl', ['bootstrap', 'system', plistPath], {
88
+ encoding: 'utf8',
89
+ stdio: ['ignore', 'inherit', 'pipe'],
90
+ });
91
+ }
92
+ catch (e) {
93
+ const err = e;
94
+ const detail = err.stderr?.toString('utf8').trim() ?? '';
95
+ const suffix = detail ? `\n${detail}` : '';
96
+ throw new Error(`launchctl bootstrap failed for ${plistPath}.${suffix}\n\nTry:\n sudo launchctl bootout system ${plistPath}\n sudo beacon install`);
97
+ }
98
+ }
5
99
  export async function installService() {
6
100
  const config = await loadConfig();
7
- if (!config.apiKey || !config.ingestUrl) {
8
- throw new Error('Config missing apiKey or ingestUrl. Please configure first.');
101
+ // Service units do not inherit the install shell's env — key must be saved in config.
102
+ if (!config.apiKey?.trim()) {
103
+ throw new Error(BEACON_CONFIG_REQUIRED_MESSAGE);
9
104
  }
10
105
  const isBun = process.execPath.endsWith('bun') || process.execPath.endsWith('bun.exe');
11
106
  const runner = isBun ? process.execPath : 'node';
@@ -27,22 +122,25 @@ Environment=NODE_ENV=production
27
122
  WantedBy=multi-user.target
28
123
  `;
29
124
  const dest = '/etc/systemd/system/syslog-beacon.service';
30
- await fs.writeFile(dest, serviceUnit, 'utf8');
125
+ await assertServiceInstallWritable(dest);
126
+ await writeServiceFile(dest, serviceUnit);
31
127
  execSync('systemctl daemon-reload');
32
128
  execSync('systemctl enable syslog-beacon');
33
129
  execSync('systemctl start syslog-beacon');
34
130
  console.log('[syslog-beacon] Installed systemd service');
35
131
  }
36
132
  else if (process.platform === 'darwin') {
133
+ const label = 'com.agentsoc.syslog-beacon';
134
+ const launchdRunner = resolveLaunchdRunner();
37
135
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
38
136
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39
137
  <plist version="1.0">
40
138
  <dict>
41
139
  <key>Label</key>
42
- <string>com.agentsoc.syslog-beacon</string>
140
+ <string>${label}</string>
43
141
  <key>ProgramArguments</key>
44
142
  <array>
45
- <string>${runner}</string>
143
+ <string>${launchdRunner}</string>
46
144
  <string>${cliPath}</string>
47
145
  <string>run</string>
48
146
  </array>
@@ -53,9 +151,13 @@ WantedBy=multi-user.target
53
151
  </dict>
54
152
  </plist>
55
153
  `;
56
- const dest = '/Library/LaunchDaemons/com.agentsoc.syslog-beacon.plist';
57
- await fs.writeFile(dest, plist, 'utf8');
58
- execSync(`launchctl load -w ${dest}`);
154
+ const dest = `/Library/LaunchDaemons/${label}.plist`;
155
+ await assertServiceInstallWritable(dest);
156
+ launchctlBootoutSystem(dest, label);
157
+ await writeServiceFile(dest, plist);
158
+ execFileSync('chown', ['root:wheel', dest], { stdio: 'inherit' });
159
+ await fs.chmod(dest, 0o644);
160
+ launchctlBootstrapSystem(dest);
59
161
  console.log('[syslog-beacon] Installed launchd service');
60
162
  }
61
163
  else if (process.platform === 'win32') {
@@ -0,0 +1,11 @@
1
+ export type ServiceManager = "systemd" | "launchd" | "none";
2
+ export interface ServiceStatus {
3
+ manager: ServiceManager;
4
+ installed: boolean;
5
+ active: boolean | null;
6
+ /** Human-readable state from the platform (e.g. active, inactive, running) */
7
+ stateLabel: string;
8
+ detail?: string;
9
+ }
10
+ export declare function probeServiceStatus(): ServiceStatus;
11
+ //# sourceMappingURL=service-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-status.d.ts","sourceRoot":"","sources":["../src/service-status.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,kBAAkB,IAAI,aAAa,CA4FlD"}
@@ -0,0 +1,15 @@
1
+ declare const STATS_VERSION: 1;
2
+ export interface BeaconStatsFile {
3
+ version: typeof STATS_VERSION;
4
+ updatedAt: string;
5
+ logsForwarded: number;
6
+ batchesSucceeded: number;
7
+ batchesFailed: number;
8
+ lastError?: string;
9
+ }
10
+ export declare function getStatsFilePath(): string;
11
+ export declare function readStats(): Promise<BeaconStatsFile>;
12
+ export declare function recordSuccessfulBatch(logCount: number): Promise<void>;
13
+ export declare function recordFailedBatch(message: string): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,aAAa,EAAG,CAAU,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,aAAa,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAa1D;AAWD,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc3E;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAetE"}
package/dist/tail.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type LogCallback = (line: string) => void;
2
+ export declare class SyslogSource {
3
+ private onLog;
4
+ private tailProc?;
5
+ private udpServer?;
6
+ constructor(onLog: LogCallback);
7
+ start(): void;
8
+ stop(): void;
9
+ }
10
+ //# sourceMappingURL=tail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tail.d.ts","sourceRoot":"","sources":["../src/tail.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAEjD,qBAAa,YAAY;IAIX,OAAO,CAAC,KAAK;IAHzB,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,SAAS,CAAC,CAAe;gBAEb,KAAK,EAAE,WAAW;IAE/B,KAAK;IAkDL,IAAI;CAQZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsoc/beacon",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Lightweight, background-running log forwarder (beacon) for AgentSOC",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,11 +8,31 @@
8
8
  "syslog-beacon": "./dist/cli.js"
9
9
  },
10
10
  "main": "./dist/cli.js",
11
+ "exports": {
12
+ ".": "./dist/cli.js",
13
+ "./package.json": "./package.json",
14
+ "./config": {
15
+ "types": "./dist/config.d.ts",
16
+ "import": "./dist/config.js"
17
+ },
18
+ "./stats": {
19
+ "types": "./dist/stats.d.ts",
20
+ "import": "./dist/stats.js"
21
+ },
22
+ "./service-status": {
23
+ "types": "./dist/service-status.d.ts",
24
+ "import": "./dist/service-status.js"
25
+ },
26
+ "./service-install": {
27
+ "types": "./dist/service-install.d.ts",
28
+ "import": "./dist/service-install.js"
29
+ }
30
+ },
11
31
  "files": [
12
32
  "dist"
13
33
  ],
14
34
  "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",
35
+ "build": "bun run --cwd ../../.. bun build --target node --entry-points ./apps/connectors/syslog-beacon-cli/src/cli.ts --outdir ./apps/connectors/syslog-beacon-cli/dist --minify",
16
36
  "build:simple": "tsc",
17
37
  "start": "bun run src/cli.ts run",
18
38
  "install-service": "bun run src/cli.ts install"