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