@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 +4 -1
- package/cli/service-units.ts +115 -32
- package/package.json +1 -1
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
|
package/cli/service-units.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
}
|