@omnixal/openclaw-nats-plugin 0.1.3 → 0.1.5

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/cli/bun-setup.ts CHANGED
@@ -78,7 +78,7 @@ export async function bunSetup(): Promise<void> {
78
78
  after: `${NATS_SERVICE}.service`,
79
79
  });
80
80
  installSystemdUnit(SIDECAR_SERVICE, sidecarUnit);
81
- } else {
81
+ } else if (manager === 'launchd') {
82
82
  const natsPlist = generateLaunchdPlist({
83
83
  label: 'com.openclaw.nats',
84
84
  program: NATS_SERVER_BIN,
@@ -94,6 +94,9 @@ export async function bunSetup(): Promise<void> {
94
94
  workingDirectory: SIDECAR_DIR,
95
95
  });
96
96
  installLaunchdPlist('com.openclaw.nats-sidecar', sidecarPlist);
97
+ } else {
98
+ // Direct mode — containers or systems without init
99
+ console.log('No init system detected, using direct process management');
97
100
  }
98
101
 
99
102
  // 9. Start services
@@ -1,7 +1,8 @@
1
- import { mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
2
- import { execFileSync } from 'node:child_process';
1
+ import { mkdirSync, writeFileSync, readFileSync, rmSync, existsSync, openSync } from 'node:fs';
2
+ import { execFileSync, spawn } from 'node:child_process';
3
3
  import { join } from 'node:path';
4
4
  import { homedir, platform } from 'node:os';
5
+ import { NATS_SERVER_BIN, NATS_CONF, PLUGIN_DIR, SIDECAR_DIR } from './paths';
5
6
 
6
7
  // --- Interfaces ---
7
8
 
@@ -23,8 +24,27 @@ export interface LaunchdPlistOptions {
23
24
 
24
25
  // --- Detection ---
25
26
 
26
- export function getServiceManager(): 'systemd' | 'launchd' {
27
- return platform() === 'darwin' ? 'launchd' : 'systemd';
27
+ function isContainer(): boolean {
28
+ try {
29
+ return existsSync('/.dockerenv') || readFileSync('/proc/1/cgroup', 'utf-8').includes('docker');
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function hasSystemctl(): boolean {
36
+ try {
37
+ execFileSync('systemctl', ['--version'], { stdio: 'pipe' });
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ export function getServiceManager(): 'systemd' | 'launchd' | 'direct' {
45
+ if (platform() === 'darwin') return 'launchd';
46
+ if (isContainer() || !hasSystemctl()) return 'direct';
47
+ return 'systemd';
28
48
  }
29
49
 
30
50
  // --- Generators ---
@@ -103,34 +123,109 @@ export function installLaunchdPlist(label: string, content: string): void {
103
123
  writeFileSync(filePath, content, 'utf-8');
104
124
  }
105
125
 
126
+ // --- Direct mode (containers / no init system) ---
127
+
128
+ function pidDir(): string {
129
+ const dir = join(homedir(), '.openclaw', 'nats-plugin', 'pids');
130
+ mkdirSync(dir, { recursive: true });
131
+ return dir;
132
+ }
133
+
134
+ function pidFile(name: string): string {
135
+ return join(pidDir(), `${name}.pid`);
136
+ }
137
+
138
+ function readPid(name: string): number | null {
139
+ try {
140
+ const pid = parseInt(readFileSync(pidFile(name), 'utf-8').trim(), 10);
141
+ process.kill(pid, 0); // check if alive
142
+ return pid;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ const NATS_SERVICE_NAME = 'openclaw-nats';
149
+ const SIDECAR_SERVICE_NAME = 'openclaw-nats-sidecar';
150
+
151
+ function getDirectCommand(name: string): { cmd: string; args: string[]; cwd: string; logFile: string } {
152
+ const logsDir = join(PLUGIN_DIR, 'logs');
153
+ mkdirSync(logsDir, { recursive: true });
154
+
155
+ if (name === NATS_SERVICE_NAME) {
156
+ return { cmd: NATS_SERVER_BIN, args: ['-c', NATS_CONF], cwd: PLUGIN_DIR, logFile: join(logsDir, 'nats.log') };
157
+ }
158
+ if (name === SIDECAR_SERVICE_NAME) {
159
+ return { cmd: 'bun', args: ['run', join(SIDECAR_DIR, 'src/index.ts')], cwd: SIDECAR_DIR, logFile: join(logsDir, 'sidecar.log') };
160
+ }
161
+ throw new Error(`Unknown service "${name}"`);
162
+ }
163
+
164
+ function startDirect(name: string): void {
165
+ const { cmd, args, cwd, logFile } = getDirectCommand(name);
166
+ const out = openSync(logFile, 'a');
167
+ const child = spawn(cmd, args, {
168
+ cwd,
169
+ stdio: ['ignore', out, out],
170
+ detached: true,
171
+ });
172
+ child.unref();
173
+ if (child.pid) {
174
+ writeFileSync(pidFile(name), String(child.pid));
175
+ }
176
+ }
177
+
178
+ function stopDirect(name: string): void {
179
+ const pid = readPid(name);
180
+ if (pid) {
181
+ try { process.kill(pid, 'SIGTERM'); } catch { /* already dead */ }
182
+ rmSync(pidFile(name), { force: true });
183
+ }
184
+ }
185
+
186
+ function isRunningDirect(name: string): boolean {
187
+ return readPid(name) !== null;
188
+ }
189
+
190
+ // --- Public API ---
191
+
106
192
  export function startService(name: string): void {
107
- if (getServiceManager() === 'systemd') {
193
+ const mgr = getServiceManager();
194
+ if (mgr === 'systemd') {
108
195
  execFileSync('systemctl', ['--user', 'enable', '--now', `${name}.service`]);
109
- } else {
196
+ } else if (mgr === 'launchd') {
110
197
  const plistPath = join(launchdAgentsDir(), `${name}.plist`);
111
198
  execFileSync('launchctl', ['load', '-w', plistPath]);
199
+ } else {
200
+ startDirect(name);
112
201
  }
113
202
  }
114
203
 
115
204
  export function stopService(name: string): void {
116
- if (getServiceManager() === 'systemd') {
205
+ const mgr = getServiceManager();
206
+ if (mgr === 'systemd') {
117
207
  execFileSync('systemctl', ['--user', 'stop', `${name}.service`]);
118
- } else {
208
+ } else if (mgr === 'launchd') {
119
209
  const plistPath = join(launchdAgentsDir(), `${name}.plist`);
120
210
  execFileSync('launchctl', ['unload', plistPath]);
211
+ } else {
212
+ stopDirect(name);
121
213
  }
122
214
  }
123
215
 
124
216
  export function isServiceRunning(name: string): boolean {
125
217
  try {
126
- if (getServiceManager() === 'systemd') {
218
+ const mgr = getServiceManager();
219
+ if (mgr === 'systemd') {
127
220
  const result = execFileSync('systemctl', ['--user', 'is-active', `${name}.service`], {
128
221
  encoding: 'utf-8',
129
222
  });
130
223
  return result.trim() === 'active';
131
- } else {
224
+ } else if (mgr === 'launchd') {
132
225
  const result = execFileSync('launchctl', ['list', name], { encoding: 'utf-8' });
133
226
  return result.includes(name);
227
+ } else {
228
+ return isRunningDirect(name);
134
229
  }
135
230
  } catch {
136
231
  return false;
@@ -138,31 +233,19 @@ export function isServiceRunning(name: string): boolean {
138
233
  }
139
234
 
140
235
  export function removeServiceUnit(name: string): void {
141
- if (getServiceManager() === 'systemd') {
142
- try {
143
- execFileSync('systemctl', ['--user', 'stop', `${name}.service`]);
144
- } catch {
145
- // service may not be running
146
- }
147
- try {
148
- execFileSync('systemctl', ['--user', 'disable', `${name}.service`]);
149
- } catch {
150
- // service may not be enabled
151
- }
236
+ const mgr = getServiceManager();
237
+ if (mgr === 'systemd') {
238
+ try { execFileSync('systemctl', ['--user', 'stop', `${name}.service`]); } catch { /* not running */ }
239
+ try { execFileSync('systemctl', ['--user', 'disable', `${name}.service`]); } catch { /* not enabled */ }
152
240
  const filePath = join(systemdUserDir(), `${name}.service`);
153
241
  rmSync(filePath, { force: true });
154
- try {
155
- execFileSync('systemctl', ['--user', 'daemon-reload']);
156
- } catch {
157
- // best effort
158
- }
159
- } else {
242
+ try { execFileSync('systemctl', ['--user', 'daemon-reload']); } catch { /* best effort */ }
243
+ } else if (mgr === 'launchd') {
160
244
  const plistPath = join(launchdAgentsDir(), `${name}.plist`);
161
- try {
162
- execFileSync('launchctl', ['unload', plistPath]);
163
- } catch {
164
- // may not be loaded
165
- }
245
+ try { execFileSync('launchctl', ['unload', plistPath]); } catch { /* may not be loaded */ }
166
246
  rmSync(plistPath, { force: true });
247
+ } else {
248
+ stopDirect(name);
249
+ rmSync(pidFile(name), { force: true });
167
250
  }
168
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnixal/openclaw-nats-plugin",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "NATS JetStream event-driven plugin for OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",