bm2 1.0.35 → 1.0.36

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/src/daemon.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * License: GPL-3.0-only
14
14
  * Author: Zak <zak@maxxpainn.com>
15
15
  */
16
-
16
+
17
17
  import { ProcessManager } from "./process-manager";
18
18
  import { Dashboard } from "./dashboard";
19
19
  import { ModuleManager } from "./module-manager";
@@ -24,221 +24,264 @@ import {
24
24
  METRICS_PORT,
25
25
  } from "./constants";
26
26
  import { ensureDirs } from "./utils";
27
- import { unlinkSync, existsSync } from "fs";
28
27
  import type { DaemonMessage, DaemonResponse } from "./types";
29
- import type { BunRequest, Server } from "bun";
28
+ import type { Server } from "bun";
30
29
 
31
- ensureDirs();
32
30
 
33
- let server: Server<any> | null = null
34
- const pm = new ProcessManager();
35
- const dashboard = new Dashboard(pm);
36
- const moduleManager = new ModuleManager(pm);
31
+ export default class Daemon {
37
32
 
38
- const args = process.argv.slice(2);
33
+ initialized: boolean = false;
39
34
 
40
- // Checks if '--debug' exists anywhere in the arguments
41
- const debugMode = args.includes('--debug');
35
+ server: Server<any> | null = null;
36
+ pm: ProcessManager | null = null;
37
+ dashboard: Dashboard | null = null;
38
+ moduleManager: ModuleManager | null = null;
39
+ metricsInterval: NodeJS.Timeout | null = null;
40
+ args = process.argv.slice(2);
42
41
 
43
- // Clean up existing socket
44
- if (existsSync(DAEMON_SOCKET)) {
45
- try { unlinkSync(DAEMON_SOCKET); } catch {}
46
- }
42
+ debugMode: boolean = false;
43
+ daemonEnabled: boolean = true;
44
+
45
+ // ── Bound once so Bun.serve always has the right `this` ──────────────────
46
+ private boundFetch = (req: Request) => this.handleServerRequests(req);
47
+
48
+ getServerOpts = () => ({
49
+ unix: DAEMON_SOCKET,
50
+ fetch: this.boundFetch,
51
+ });
52
+
53
+
54
+ async initialize(_daemonEnabled: boolean = true) {
55
+
56
+ await ensureDirs();
47
57
 
48
- // Write PID file
49
- await Bun.write(DAEMON_PID_FILE, String(process.pid));
58
+ this.daemonEnabled = _daemonEnabled;
59
+ this.pm = new ProcessManager();
60
+ this.dashboard = new Dashboard(this.pm);
61
+ this.moduleManager = new ModuleManager(this.pm);
50
62
 
51
- // Load modules
52
- await moduleManager.loadAll();
63
+ this.args = process.argv.slice(2);
64
+ this.debugMode = this.args.includes("--debug");
53
65
 
54
- // Start metric collection
55
- const metricsInterval = setInterval(() => {
56
- pm.getMetrics();
57
- }, 2000);
66
+ if (_daemonEnabled) {
67
+
68
+ const sock = Bun.file(DAEMON_SOCKET);
69
+
70
+ // Clean up existing socket
71
+ if (await sock.exists()) {
72
+ try { await sock.delete(); } catch {}
73
+ }
74
+
75
+ // Write PID file
76
+ await Bun.write(DAEMON_PID_FILE, String(process.pid));
77
+
78
+ }
58
79
 
80
+ // Load modules
81
+ await this.moduleManager.loadAll();
59
82
 
60
- const handleServerRequests = async (req: Request) => {
61
-
62
- if (req.method !== "POST") {
63
- return Response.json(
64
- { type: "error", error: "Method Not Allowed", success: false },
65
- { status: 405 }
66
- )
83
+ this.metricsInterval = setInterval(() => {
84
+ this.pm!.getMetrics();
85
+ }, 2000);
86
+
87
+ this.initialized = true;
88
+
89
+ } // end initialize
90
+
91
+ async handleServerRequests(req: Request): Promise<Response> {
92
+
93
+ if (req.method !== "POST") {
94
+ return Response.json(
95
+ { type: "error", error: "Method Not Allowed", success: false },
96
+ { status: 405 }
97
+ );
98
+ }
99
+
100
+ try {
101
+
102
+ const msg = (await req.json()) as DaemonMessage;
103
+ const response = await this.handleMessage(msg);
104
+ return Response.json(response);
105
+
106
+ } catch (err: any) {
107
+ return Response.json(
108
+ { type: "error", error: err.message, success: false },
109
+ { status: 500 }
110
+ );
111
+ }
67
112
  }
68
113
 
69
- try {
70
-
71
- const msg: DaemonMessage = await req.json() as DaemonMessage;
72
-
73
- const response = await handleMessage(msg);
74
-
75
- return Response.json(response);
76
-
77
- } catch (err: any) {
78
- return Response.json(
79
- { type: "error", error: err.message, success: false },
80
- { status: 500 }
81
- );
114
+ // initialize MUST be called before startServer
115
+ startServer(): Server<any> {
116
+
117
+ if (!this.initialized) {
118
+ throw new Error("Daemon.initialize() must be called before startServer()");
119
+ }
120
+
121
+ this.server = Bun.serve(this.getServerOpts());
122
+ return this.server;
82
123
  }
83
- };
84
124
 
85
- const serverOptions = {
86
- unix: DAEMON_SOCKET,
87
- fetch: handleServerRequests
88
- }
125
+ async handleMessage(msg: DaemonMessage): Promise<DaemonResponse> {
126
+ try {
89
127
 
90
- async function handleMessage(msg: DaemonMessage): Promise<DaemonResponse> {
91
- try {
92
- switch (msg.type) {
93
- case "start": {
94
- const states = await pm.start(msg.data);
95
- return { type: "start", data: states, success: true, id: msg.id };
96
- }
97
- case "stop": {
98
- const states = await pm.stop(msg.data.target);
99
- return { type: "stop", data: states, success: true, id: msg.id };
100
- }
101
- case "restart": {
102
- const states = await pm.restart(msg.data.target);
103
- return { type: "restart", data: states, success: true, id: msg.id };
104
- }
105
- case "reload": {
106
- const states = await pm.reload(msg.data.target);
107
- return { type: "reload", data: states, success: true, id: msg.id };
108
- }
109
- case "delete": {
110
- const states = await pm.del(msg.data.target);
111
- return { type: "delete", data: states, success: true, id: msg.id };
112
- }
113
- case "scale": {
114
- const states = await pm.scale(msg.data.target, msg.data.count);
115
- return { type: "scale", data: states, success: true, id: msg.id };
116
- }
117
- case "stopAll": {
118
- const states = await pm.stopAll();
119
- return { type: "stopAll", data: states, success: true, id: msg.id };
120
- }
121
- case "restartAll": {
122
- const states = await pm.restartAll();
123
- return { type: "restartAll", data: states, success: true, id: msg.id };
124
- }
125
- case "reloadAll": {
126
- const states = await pm.reloadAll();
127
- return { type: "reloadAll", data: states, success: true, id: msg.id };
128
- }
129
- case "deleteAll": {
130
- const states = await pm.deleteAll();
131
- return { type: "deleteAll", data: states, success: true, id: msg.id };
132
- }
133
- case "list": {
134
- return { type: "list", data: pm.list(), success: true, id: msg.id };
135
- }
136
- case "describe": {
137
- return { type: "describe", data: pm.describe(msg.data.target), success: true, id: msg.id };
138
- }
139
- case "logs": {
140
- const logs = await pm.getLogs(msg.data.target, msg.data.lines);
141
- return { type: "logs", data: logs, success: true, id: msg.id };
142
- }
143
- case "flush": {
144
- await pm.flushLogs(msg.data?.target);
145
- return { type: "flush", success: true, id: msg.id };
146
- }
147
- case "save": {
148
- await pm.save();
149
- return { type: "save", success: true, id: msg.id };
150
- }
151
- case "resurrect": {
152
- const states = await pm.resurrect();
153
- return { type: "resurrect", data: states, success: true, id: msg.id };
154
- }
155
- case "ecosystem": {
156
- const states = await pm.startEcosystem(msg.data);
157
- return { type: "ecosystem", data: states, success: true, id: msg.id };
158
- }
159
- case "signal": {
160
- await pm.sendSignal(msg.data.target, msg.data.signal);
161
- return { type: "signal", success: true, id: msg.id };
162
- }
163
- case "reset": {
164
- const states = await pm.reset(msg.data.target);
165
- return { type: "reset", data: states, success: true, id: msg.id };
166
- }
167
- case "metrics": {
168
- const metrics = await pm.getMetrics();
169
- return { type: "metrics", data: metrics, success: true, id: msg.id };
170
- }
171
- case "metricsHistory": {
172
- const history = pm.getMetricsHistory(msg.data?.seconds || 300);
173
- return { type: "metricsHistory", data: history, success: true, id: msg.id };
174
- }
175
- case "prometheus": {
176
- const prom = pm.getPrometheusMetrics();
177
- return { type: "prometheus", data: prom, success: true, id: msg.id };
178
- }
179
- case "dashboard": {
180
- const port = msg.data?.port || DASHBOARD_PORT;
181
- const metricsPort = msg.data?.metricsPort || METRICS_PORT;
182
- dashboard.start(port, metricsPort);
183
- return { type: "dashboard", data: { port, metricsPort }, success: true, id: msg.id };
128
+ if (!this.initialized) {
129
+ await this.initialize();
184
130
  }
185
- case "dashboardStop": {
186
- dashboard.stop();
187
- return { type: "dashboardStop", success: true, id: msg.id };
188
- }
189
- case "moduleInstall": {
190
- const path = await moduleManager.install(msg.data.module);
191
- return { type: "moduleInstall", data: { path }, success: true, id: msg.id };
192
- }
193
- case "moduleUninstall": {
194
- await moduleManager.uninstall(msg.data.module);
195
- return { type: "moduleUninstall", success: true, id: msg.id };
196
- }
197
-
198
- case "moduleList": {
199
- return { type: "moduleList", data: moduleManager.list(), success: true, id: msg.id };
200
- }
201
-
202
- case "daemonReload": {
203
- if (!server) {
204
- server = Bun.serve(serverOptions);
205
- } else {
206
- server.reload(serverOptions)
207
- }
208
-
209
- return { type: "daemonReload", data: `Daemon reloaded`, success: true, id: msg.id };
210
- }
211
-
212
- case "ping": {
213
- return {
214
- type: "pong",
215
- data: { pid: process.pid, uptime: process.uptime() },
216
- success: true,
217
- id: msg.id,
218
- };
131
+
132
+ const pm = this.pm!;
133
+ const dashboard = this.dashboard!;
134
+ const moduleManager = this.moduleManager!;
135
+ const metricsInterval = this.metricsInterval!;
136
+
137
+ switch (msg.type) {
138
+ case "start": {
139
+ const states = await pm.start(msg.data);
140
+ return { type: "start", data: states, success: true, id: msg.id };
141
+ }
142
+ case "stop": {
143
+ const states = await pm.stop(msg.data.target);
144
+ return { type: "stop", data: states, success: true, id: msg.id };
145
+ }
146
+ case "restart": {
147
+ const states = await pm.restart(msg.data.target);
148
+ return { type: "restart", data: states, success: true, id: msg.id };
149
+ }
150
+ case "reload": {
151
+ const states = await pm.reload(msg.data.target);
152
+ return { type: "reload", data: states, success: true, id: msg.id };
153
+ }
154
+ case "delete": {
155
+ const states = await pm.del(msg.data.target);
156
+ return { type: "delete", data: states, success: true, id: msg.id };
157
+ }
158
+ case "scale": {
159
+ const states = await pm.scale(msg.data.target, msg.data.count);
160
+ return { type: "scale", data: states, success: true, id: msg.id };
161
+ }
162
+ case "stopAll": {
163
+ const states = await pm.stopAll();
164
+ return { type: "stopAll", data: states, success: true, id: msg.id };
165
+ }
166
+ case "restartAll": {
167
+ const states = await pm.restartAll();
168
+ return { type: "restartAll", data: states, success: true, id: msg.id };
169
+ }
170
+ case "reloadAll": {
171
+ const states = await pm.reloadAll();
172
+ return { type: "reloadAll", data: states, success: true, id: msg.id };
173
+ }
174
+ case "deleteAll": {
175
+ const states = await pm.deleteAll();
176
+ return { type: "deleteAll", data: states, success: true, id: msg.id };
177
+ }
178
+ case "list": {
179
+ return { type: "list", data: pm.list(), success: true, id: msg.id };
180
+ }
181
+ case "describe": {
182
+ return { type: "describe", data: pm.describe(msg.data.target), success: true, id: msg.id };
183
+ }
184
+ case "logs": {
185
+ const logs = await pm.getLogs(msg.data.target, msg.data.lines);
186
+ return { type: "logs", data: logs, success: true, id: msg.id };
187
+ }
188
+ case "flush": {
189
+ await pm.flushLogs(msg.data?.target);
190
+ return { type: "flush", success: true, id: msg.id };
191
+ }
192
+ case "save": {
193
+ await pm.save();
194
+ return { type: "save", success: true, id: msg.id };
195
+ }
196
+ case "resurrect": {
197
+ const states = await pm.resurrect();
198
+ return { type: "resurrect", data: states, success: true, id: msg.id };
199
+ }
200
+ case "ecosystem": {
201
+ const states = await pm.startEcosystem(msg.data);
202
+ return { type: "ecosystem", data: states, success: true, id: msg.id };
203
+ }
204
+ case "signal": {
205
+ await pm.sendSignal(msg.data.target, msg.data.signal);
206
+ return { type: "signal", success: true, id: msg.id };
207
+ }
208
+ case "reset": {
209
+ const states = await pm.reset(msg.data.target);
210
+ return { type: "reset", data: states, success: true, id: msg.id };
211
+ }
212
+ case "metrics": {
213
+ const metrics = await pm.getMetrics();
214
+ return { type: "metrics", data: metrics, success: true, id: msg.id };
215
+ }
216
+ case "metricsHistory": {
217
+ const history = pm.getMetricsHistory(msg.data?.seconds || 300);
218
+ return { type: "metricsHistory", data: history, success: true, id: msg.id };
219
+ }
220
+ case "prometheus": {
221
+ const prom = pm.getPrometheusMetrics();
222
+ return { type: "prometheus", data: prom, success: true, id: msg.id };
223
+ }
224
+ case "dashboard": {
225
+ const port = msg.data?.port || DASHBOARD_PORT;
226
+ const metricsPort = msg.data?.metricsPort || METRICS_PORT;
227
+ dashboard.start(port, metricsPort);
228
+ return { type: "dashboard", data: { port, metricsPort }, success: true, id: msg.id };
229
+ }
230
+ case "dashboardStop": {
231
+ dashboard.stop();
232
+ return { type: "dashboardStop", success: true, id: msg.id };
233
+ }
234
+ case "moduleInstall": {
235
+ const path = await moduleManager.install(msg.data.module);
236
+ return { type: "moduleInstall", data: { path }, success: true, id: msg.id };
237
+ }
238
+ case "moduleUninstall": {
239
+ await moduleManager.uninstall(msg.data.module);
240
+ return { type: "moduleUninstall", success: true, id: msg.id };
241
+ }
242
+ case "moduleList": {
243
+ return { type: "moduleList", data: moduleManager.list(), success: true, id: msg.id };
244
+ }
245
+ case "ping": {
246
+ return {
247
+ type: "pong",
248
+ data: { pid: process.pid, uptime: process.uptime() },
249
+ success: true,
250
+ id: msg.id,
251
+ };
252
+ }
253
+ case "kill": {
254
+ await pm.stopAll();
255
+ dashboard.stop();
256
+ clearInterval(metricsInterval);
257
+ setTimeout(() => process.exit(0), 200);
258
+ return { type: "kill", success: true, id: msg.id };
259
+ }
260
+ default:
261
+ return { type: "error", error: `Unknown command: ${(msg as any).type}`, success: false, id: msg.id };
219
262
  }
220
-
221
- case "kill": {
222
- await pm.stopAll();
223
- dashboard.stop();
224
- clearInterval(metricsInterval);
225
- setTimeout(() => process.exit(0), 200);
226
- return { type: "kill", success: true, id: msg.id };
263
+
264
+ } catch (err: Error | any) {
265
+
266
+ let error = err.message;
267
+
268
+ if (this.debugMode) {
269
+ error = `Message: ${err.message}\nStack: ${err.stack}`;
270
+ console.error(err, err.stack);
227
271
  }
228
- default:
229
- return { type: "error", error: `Unknown command: ${msg.type}`, success: false, id: msg.id };
230
- }
231
- } catch (err: Error | any) {
232
- let error = err.message;
233
- if (debugMode) {
234
- error = `Message: ${err.message}\nStack: ${err.stack}`
235
- console.error(err, err.stack)
272
+
273
+ return { type: "error", error, success: false, id: msg.id };
274
+
236
275
  }
237
- return { type: "error", error, success: false, id: msg.id };
238
276
  }
239
- }
240
277
 
278
+ } // end class
241
279
 
242
- server = Bun.serve(serverOptions);
243
280
 
244
- console.log(`Listening on ${server.url}`);
281
+ // ── Entrypoint (spawned by CLI) ───────────────────────────────────────────
282
+ if (import.meta.main) {
283
+ const dm = new Daemon();
284
+ await dm.initialize(); // initialize first — writes PID, sets up pm/dashboard
285
+ const s = dm.startServer(); // then bind the socket
286
+ console.log(`Daemon listening on ${s.url}`);
287
+ }