@agentsoc/beacon 0.0.3 → 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/dist/cli.js CHANGED
@@ -60,7 +60,8 @@ program
60
60
  await installService();
61
61
  }
62
62
  catch (err) {
63
- 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`);
64
65
  process.exit(1);
65
66
  }
66
67
  });
@@ -1 +1 @@
1
- {"version":3,"file":"service-install.d.ts","sourceRoot":"","sources":["../src/service-install.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,kBAgEnC"}
1
+ {"version":3,"file":"service-install.d.ts","sourceRoot":"","sources":["../src/service-install.ts"],"names":[],"mappings":"AA2GA,wBAAsB,cAAc,kBAuEnC"}
@@ -1,7 +1,101 @@
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 { execFileSync, execSync } from 'node:child_process';
4
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
101
  // Service units do not inherit the install shell's env — key must be saved in config.
@@ -28,22 +122,25 @@ Environment=NODE_ENV=production
28
122
  WantedBy=multi-user.target
29
123
  `;
30
124
  const dest = '/etc/systemd/system/syslog-beacon.service';
31
- await fs.writeFile(dest, serviceUnit, 'utf8');
125
+ await assertServiceInstallWritable(dest);
126
+ await writeServiceFile(dest, serviceUnit);
32
127
  execSync('systemctl daemon-reload');
33
128
  execSync('systemctl enable syslog-beacon');
34
129
  execSync('systemctl start syslog-beacon');
35
130
  console.log('[syslog-beacon] Installed systemd service');
36
131
  }
37
132
  else if (process.platform === 'darwin') {
133
+ const label = 'com.agentsoc.syslog-beacon';
134
+ const launchdRunner = resolveLaunchdRunner();
38
135
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
39
136
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
40
137
  <plist version="1.0">
41
138
  <dict>
42
139
  <key>Label</key>
43
- <string>com.agentsoc.syslog-beacon</string>
140
+ <string>${label}</string>
44
141
  <key>ProgramArguments</key>
45
142
  <array>
46
- <string>${runner}</string>
143
+ <string>${launchdRunner}</string>
47
144
  <string>${cliPath}</string>
48
145
  <string>run</string>
49
146
  </array>
@@ -54,9 +151,13 @@ WantedBy=multi-user.target
54
151
  </dict>
55
152
  </plist>
56
153
  `;
57
- const dest = '/Library/LaunchDaemons/com.agentsoc.syslog-beacon.plist';
58
- await fs.writeFile(dest, plist, 'utf8');
59
- 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);
60
161
  console.log('[syslog-beacon] Installed launchd service');
61
162
  }
62
163
  else if (process.platform === 'win32') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsoc/beacon",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Lightweight, background-running log forwarder (beacon) for AgentSOC",
5
5
  "type": "module",
6
6
  "bin": {