@agentsoc/beacon 0.0.5 → 0.0.7

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
@@ -95,10 +95,12 @@ _Note: You can use `AGENTSOC_API_KEY` instead of saving a key in config. Use `AG
95
95
  Install the background service daemon (systemd on Linux, launchd on macOS):
96
96
 
97
97
  ```bash
98
- sudo beacon install
98
+ sudo "$(command -v beacon)" install
99
99
  ```
100
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`. The service is configured to **run as your login user** (via `SUDO_USER` when you use `sudo`), so it uses the same config directory as `beacon config`—not root’s home. 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.
101
+ On macOS the plist is written under `/Library/LaunchDaemons/`, and on Linux the unit file goes under `/etc/systemd/system/`—both require root. The service **runs as your login user** (via `SUDO_USER` when you use `sudo`), so it uses the same config directory as `beacon config`—not root’s home.
102
+
103
+ **`sudo beacon` says “command not found”:** sudo resets `PATH`, so it often cannot see npm/nvm global bins. Use `sudo "$(command -v beacon)" install` (as above), or `sudo env "PATH=$PATH" beacon install`, or the full path from `which beacon`.
102
104
 
103
105
  ### Status and stats
104
106
 
@@ -1 +1 @@
1
- {"version":3,"file":"service-install.d.ts","sourceRoot":"","sources":["../src/service-install.ts"],"names":[],"mappings":"AA4IA,wBAAsB,cAAc,kBA4EnC"}
1
+ {"version":3,"file":"service-install.d.ts","sourceRoot":"","sources":["../src/service-install.ts"],"names":[],"mappings":"AAkJA,wBAAsB,cAAc,kBA4EnC"}
@@ -4,11 +4,17 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { execFileSync, execSync } from 'node:child_process';
6
6
  import { BEACON_CONFIG_REQUIRED_MESSAGE, loadConfig } from './config.js';
7
- const PERMISSION_DENIED_HINT = `That location is only writable as root. Run the same command with sudo, for example:
7
+ const PERMISSION_DENIED_HINT = `That location is only writable as root. From the same shell where \`beacon\` works, run:
8
8
 
9
- sudo beacon install
9
+ sudo "$(command -v beacon)" install
10
10
 
11
- You will be prompted for your administrator password.`;
11
+ You will be prompted for your administrator password.
12
+
13
+ Why not \`sudo beacon install\`? sudo uses a minimal PATH. When beacon was installed with npm/nvm/fnm as your user, root often cannot find the \`beacon\` command (you will see "command not found"). The line above resolves the full path before sudo runs.
14
+
15
+ Other options:
16
+ sudo env "PATH=$PATH" beacon install
17
+ sudo /path/from/which/beacon install`;
12
18
  function permissionDeniedInstallError(destPath) {
13
19
  return new Error(`Cannot install the system service: permission denied for ${path.dirname(destPath)}.\n\n${PERMISSION_DENIED_HINT}`);
14
20
  }
@@ -123,7 +129,7 @@ function launchctlBootstrapSystem(plistPath) {
123
129
  const err = e;
124
130
  const detail = err.stderr?.toString('utf8').trim() ?? '';
125
131
  const suffix = detail ? `\n${detail}` : '';
126
- throw new Error(`launchctl bootstrap failed for ${plistPath}.${suffix}\n\nTry:\n sudo launchctl bootout system ${plistPath}\n sudo beacon install`);
132
+ throw new Error(`launchctl bootstrap failed for ${plistPath}.${suffix}\n\nTry:\n sudo launchctl bootout system ${plistPath}\n sudo "$(command -v beacon)" install`);
127
133
  }
128
134
  }
129
135
  export async function installService() {
package/dist/tail.d.ts CHANGED
@@ -3,7 +3,10 @@ export declare class SyslogSource {
3
3
  private onLog;
4
4
  private tailProc?;
5
5
  private udpServer?;
6
+ /** Avoid repeating the tip if stderr arrives in multiple chunks. */
7
+ private journalRestrictedHintShown;
6
8
  constructor(onLog: LogCallback);
9
+ private startUdpListener;
7
10
  start(): void;
8
11
  stop(): void;
9
12
  }
@@ -1 +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"}
1
+ {"version":3,"file":"tail.d.ts","sourceRoot":"","sources":["../src/tail.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAgCjD,qBAAa,YAAY;IAMX,OAAO,CAAC,KAAK;IALzB,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,SAAS,CAAC,CAAe;IACjC,oEAAoE;IACpE,OAAO,CAAC,0BAA0B,CAAS;gBAEvB,KAAK,EAAE,WAAW;IAEtC,OAAO,CAAC,gBAAgB;IAcjB,KAAK;IAqEL,IAAI;CAQZ"}
package/dist/tail.js CHANGED
@@ -1,19 +1,79 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn, spawnSync } from "node:child_process";
2
2
  import dgram from "node:dgram";
3
+ import fs from "node:fs";
4
+ /**
5
+ * Fallback when journalctl is unavailable (non-systemd or embedded images).
6
+ * Debian/Ubuntu rsyslog paths and common /var/log/messages.
7
+ */
8
+ const LINUX_FILE_LOG_CANDIDATES = [
9
+ "/var/log/syslog",
10
+ "/var/log/auth.log",
11
+ "/var/log/messages",
12
+ ];
13
+ function existingLinuxLogFiles() {
14
+ return LINUX_FILE_LOG_CANDIDATES.filter((p) => {
15
+ try {
16
+ return fs.existsSync(p) && fs.statSync(p).isFile();
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ });
22
+ }
23
+ /** True if systemd-journald CLI is on PATH (standard on systemd Linux; no extra packages). */
24
+ function journalctlAvailable() {
25
+ const r = spawnSync("journalctl", ["--version"], { stdio: "ignore" });
26
+ return !r.error && r.status === 0;
27
+ }
28
+ /** journalctl prints this when the user lacks system journal ACLs (first follow only). */
29
+ const JOURNAL_RESTRICTED_HINT = /not seeing messages from other users/i;
3
30
  export class SyslogSource {
4
31
  onLog;
5
32
  tailProc;
6
33
  udpServer;
34
+ /** Avoid repeating the tip if stderr arrives in multiple chunks. */
35
+ journalRestrictedHintShown = false;
7
36
  constructor(onLog) {
8
37
  this.onLog = onLog;
9
38
  }
39
+ startUdpListener() {
40
+ this.udpServer = dgram.createSocket("udp4");
41
+ this.udpServer.on("message", (msg) => {
42
+ this.onLog(msg.toString("utf8").trim());
43
+ });
44
+ this.udpServer.on("error", (err) => {
45
+ console.error(`[syslog-udp] error:\n${err.stack}`);
46
+ this.udpServer?.close();
47
+ });
48
+ this.udpServer.bind(5140, () => {
49
+ console.log("[syslog-udp] listening on UDP 5140");
50
+ });
51
+ }
10
52
  start() {
11
53
  if (process.platform === "linux") {
12
- this.tailProc = spawn("tail", [
13
- "-F",
14
- "/var/log/syslog",
15
- "/var/log/auth.log",
16
- ]);
54
+ let stderrIsJournalctl = false;
55
+ if (journalctlAvailable()) {
56
+ stderrIsJournalctl = true;
57
+ console.log("[syslog-tail] Streaming systemd journal (journalctl -f)");
58
+ this.tailProc = spawn("journalctl", [
59
+ "-f",
60
+ "--no-pager",
61
+ "-o",
62
+ "short-iso",
63
+ ]);
64
+ }
65
+ else {
66
+ const logFiles = existingLinuxLogFiles();
67
+ if (logFiles.length > 0) {
68
+ console.log(`[syslog-tail] Following: ${logFiles.join(", ")}`);
69
+ this.tailProc = spawn("tail", ["-F", ...logFiles]);
70
+ }
71
+ else {
72
+ console.log("[syslog-tail] No journalctl or log files under /var/log; listening on UDP 5140");
73
+ this.startUdpListener();
74
+ return;
75
+ }
76
+ }
17
77
  this.tailProc.stdout?.on("data", (data) => {
18
78
  const lines = data.toString().split("\n");
19
79
  for (const line of lines) {
@@ -22,7 +82,14 @@ export class SyslogSource {
22
82
  }
23
83
  });
24
84
  this.tailProc.stderr?.on("data", (data) => {
25
- console.error(`[syslog-tail] ${data.toString()}`);
85
+ const text = data.toString();
86
+ console.error(`[syslog-tail] ${text}`);
87
+ if (stderrIsJournalctl &&
88
+ !this.journalRestrictedHintShown &&
89
+ JOURNAL_RESTRICTED_HINT.test(text)) {
90
+ this.journalRestrictedHintShown = true;
91
+ console.error("[syslog-tail] Only your user journal is visible. For system-wide logs, add this user to group systemd-journal or adm, or run beacon as root (no extra packages).");
92
+ }
26
93
  });
27
94
  }
28
95
  else if (process.platform === "darwin") {
@@ -46,18 +113,8 @@ export class SyslogSource {
46
113
  });
47
114
  }
48
115
  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
- });
116
+ // Windows or other UDP listener for forwarded syslog
117
+ this.startUdpListener();
61
118
  }
62
119
  }
63
120
  stop() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsoc/beacon",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Lightweight, background-running log forwarder (beacon) for AgentSOC",
5
5
  "type": "module",
6
6
  "bin": {