bm2 1.0.0

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/README.md ADDED
@@ -0,0 +1,1591 @@
1
+ # ⚡ BM2
2
+
3
+ **A blazing-fast, full-featured process manager built entirely on Bun native APIs.**
4
+ The modern PM2 replacement — zero Node.js dependencies, pure Bun performance.
5
+
6
+ ![Runtime](https://img.shields.io/badge/runtime-Bun-f472b6?style=flat-square)
7
+ ![Language](https://img.shields.io/badge/language-TypeScript-3178c6?style=flat-square)
8
+ ![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)
9
+ ![Version](https://img.shields.io/badge/version-1.0.0-blue?style=flat-square)
10
+ [![Tests](https://github.com/bun-bm2/bm2/actions/workflows/test.yml/badge.svg)](https://github.com/bun-bm2/bm2/actions/workflows/test.yml)
11
+
12
+ **Created by the MaxxPainn Team**
13
+ 🌐 [https://maxxpainn.com](https://maxxpainn.com)
14
+ 📧 Support: [hello@maxxpainn.com](mailto:hello@maxxpainn.com)
15
+
16
+ ---
17
+
18
+ ## Table of Contents
19
+
20
+ - [Why BM2?](#why-bm2)
21
+ - [Features](#features)
22
+ - [Requirements](#requirements)
23
+ - [Installation](#installation)
24
+ - [Quick Start](#quick-start)
25
+ - [CLI Reference](#cli-reference)
26
+ - [Process Management](#process-management)
27
+ - [Cluster Mode](#cluster-mode)
28
+ - [Log Management](#log-management)
29
+ - [Monitoring and Metrics](#monitoring-and-metrics)
30
+ - [Dashboard](#dashboard)
31
+ - [Ecosystem Files](#ecosystem-files)
32
+ - [Environment Management](#environment-management)
33
+ - [Deployment](#deployment)
34
+ - [Startup Scripts](#startup-scripts)
35
+ - [Modules](#modules)
36
+ - [Daemon Control](#daemon-control)
37
+ - [Configuration Reference](#configuration-reference)
38
+ - [Ecosystem File Format](#ecosystem-file-format)
39
+ - [Process Options](#process-options)
40
+ - [Cluster Options](#cluster-options)
41
+ - [Log Options](#log-options)
42
+ - [Health Check Options](#health-check-options)
43
+ - [Watch Options](#watch-options)
44
+ - [Deploy Configuration](#deploy-configuration)
45
+ - [Web Dashboard](#web-dashboard)
46
+ - [Dashboard Features](#dashboard-features)
47
+ - [REST API](#rest-api)
48
+ - [WebSocket API](#websocket-api)
49
+ - [Prometheus and Grafana Integration](#prometheus-and-grafana-integration)
50
+ - [Programmatic API](#programmatic-api)
51
+ - [Architecture](#architecture)
52
+ - [Comparison with PM2](#comparison-with-pm2)
53
+ - [Recipes and Examples](#recipes-and-examples)
54
+ - [Troubleshooting](#troubleshooting)
55
+ - [File Structure](#file-structure)
56
+ - [Contributing](#contributing)
57
+ - [License](#license)
58
+
59
+ ---
60
+
61
+ ## Why BM2?
62
+
63
+ PM2 is the de facto process manager for Node.js, but it carries years of legacy baggage, a heavy dependency tree, and is fundamentally built for the Node.js runtime. BM2 is a ground-up reimagining of production process management designed exclusively for the Bun runtime.
64
+
65
+ BM2 replaces PM2's Node.js internals with Bun-native APIs. It uses `Bun.spawn` for process management, `Bun.serve` for the dashboard and IPC, native `WebSocket` for daemon communication, `Bun.file` for high-performance I/O, and `Bun.gzipSync` for log compression. The result is a process manager that starts faster, uses less memory, and leverages Bun's superior performance across the board.
66
+
67
+ ---
68
+
69
+ ## Features
70
+
71
+ **Core Process Management** — Start, stop, restart, reload, delete, and scale processes with automatic restart on crash, configurable restart strategies, memory-limit restarts, and tree killing.
72
+
73
+ **Cluster Mode** — Run multiple instances of your application with per-worker environment injection, automatic port assignment, and round-robin-ready configuration using `NODE_APP_INSTANCE` conventions.
74
+
75
+ **Zero-Downtime Reload** — Graceful reload cycles through instances sequentially, starting the new process before stopping the old one, ensuring your application never drops a request.
76
+
77
+ **Real-Time Web Dashboard** — A built-in dark-themed web dashboard with live WebSocket updates, CPU/memory charts, process control buttons, and a log viewer. No external dependencies.
78
+
79
+ **Prometheus Metrics** — A dedicated metrics endpoint exports process and system telemetry in Prometheus exposition format, ready for scraping by Prometheus and visualization in Grafana.
80
+
81
+ **Log Management** — Automatic log capture with buffered writes, size-based rotation, configurable retention, optional gzip compression, log flushing, and real-time tailing.
82
+
83
+ **Health Checks** — HTTP health check probes with configurable intervals, timeouts, and failure thresholds that automatically restart unhealthy processes.
84
+
85
+ **Cron Restarts** — Schedule periodic restarts using standard cron expressions for applications that benefit from regular recycling.
86
+
87
+ **File Watching** — Automatic restart on file changes with configurable watch paths and ignore patterns. Ideal for development workflows.
88
+
89
+ **Ecosystem Files** — Declare your entire application topology in a single JSON or TypeScript configuration file and start everything with one command.
90
+
91
+ **Process Persistence** — Save the current process list and resurrect it after a daemon restart or system reboot. Combined with startup script generation, your applications survive server reboots.
92
+
93
+ **Startup Script Generation** — Automatically generate and install systemd (Linux) or launchd (macOS) service configurations so the BM2 daemon starts at boot.
94
+
95
+ **Remote Deployment** — A built-in deploy system that handles SSH-based deployment with git pull, release directory management, symlink rotation, and pre/post-deploy hooks.
96
+
97
+ **Module/Plugin System** — Extend BM2 with custom modules that hook into the process manager lifecycle.
98
+
99
+ **Environment Management** — Store, retrieve, and inject environment variables per process with `.env` file loading support.
100
+
101
+ **Full IPC Architecture** — A daemonized architecture where the CLI communicates with a long-running daemon process over a Unix domain socket using WebSocket protocol.
102
+
103
+ ---
104
+
105
+ ## Requirements
106
+
107
+ Bun version 1.0 or higher is required. BM2 is built exclusively for the Bun runtime.
108
+
109
+ Install Bun if you haven't already:
110
+
111
+ ```
112
+ curl -fsSL https://bun.sh/install | bash
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Installation
118
+
119
+ ### From Source
120
+
121
+ ```
122
+ git clone https://github.com/aspect-dev/bm2.git
123
+ cd bm2
124
+ bun install
125
+ bun link
126
+ ```
127
+
128
+ ### Global Install
129
+
130
+ ```
131
+ bun add -g bm2
132
+ ```
133
+
134
+ ### Verify Installation
135
+
136
+ ```
137
+ bm2 --version
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Quick Start
143
+
144
+ ### Start a process
145
+
146
+ ```
147
+ bm2 start app.ts
148
+ ```
149
+
150
+ ### Start with a name and options
151
+
152
+ ```
153
+ bm2 start app.ts --name my-api --instances 4 --port 3000
154
+ ```
155
+
156
+ ### List all processes
157
+
158
+ ```
159
+ bm2 list
160
+ ```
161
+
162
+ Output:
163
+
164
+ ```
165
+ ┌────┬──────────┬──────────┬──────┬───────┬──────────┬──────────┬──────────┐
166
+ │ ID │ Name │ Status │ PID │ CPU │ Memory │ Restarts │ Uptime │
167
+ ├────┼──────────┼──────────┼──────┼───────┼──────────┼──────────┼──────────┤
168
+ │ 0 │ my-api-0 │ online │ 4521 │ 0.3% │ 42.1 MB │ 0 │ 5m 23s │
169
+ │ 1 │ my-api-1 │ online │ 4522 │ 0.2% │ 39.8 MB │ 0 │ 5m 23s │
170
+ │ 2 │ my-api-2 │ online │ 4523 │ 0.4% │ 41.3 MB │ 0 │ 5m 23s │
171
+ │ 3 │ my-api-3 │ online │ 4524 │ 0.1% │ 40.5 MB │ 0 │ 5m 23s │
172
+ └────┴──────────┴──────────┴──────┴───────┴──────────┴──────────┴──────────┘
173
+ ```
174
+
175
+ ### Open the dashboard
176
+
177
+ ```
178
+ bm2 dashboard
179
+ ```
180
+
181
+ Output:
182
+
183
+ ```
184
+ ⚡ Dashboard running at http://localhost:9615
185
+ 📊 Prometheus metrics at http://localhost:9616/metrics
186
+ ```
187
+
188
+ ### Save and auto-resurrect on reboot
189
+
190
+ ```
191
+ bm2 save
192
+ bm2 startup
193
+ ```
194
+
195
+ ---
196
+
197
+ ## CLI Reference
198
+
199
+ ### Process Management
200
+
201
+ #### bm2 start
202
+
203
+ Start a new process or processes.
204
+
205
+ ```
206
+ bm2 start server.ts
207
+ ```
208
+
209
+ ```
210
+ bm2 start server.ts --name api -- --port 8080 --host 0.0.0.0
211
+ ```
212
+
213
+ ```
214
+ bm2 start server.ts --name api --env NODE_ENV=production --env API_KEY=xxx
215
+ ```
216
+
217
+ ```
218
+ bm2 start server.ts --name api --max-memory-restart 512M
219
+ ```
220
+
221
+ ```
222
+ bm2 start script.py --interpreter python3
223
+ ```
224
+
225
+ ```
226
+ bm2 start server.ts --name api --wait-ready --listen-timeout 10000
227
+ ```
228
+
229
+ **Options:**
230
+
231
+ | Flag | Description | Default |
232
+ |---|---|---|
233
+ | `--name <name>` | Process name | Script filename |
234
+ | `--instances <n>` | Number of instances. Use `max` for all CPUs | `1` |
235
+ | `--exec-mode <mode>` | `fork` or `cluster` | `fork` |
236
+ | `--cwd <path>` | Working directory | Current directory |
237
+ | `--env <KEY=VAL>` | Environment variable (repeatable) | — |
238
+ | `--interpreter <bin>` | Custom interpreter binary | Auto-detected |
239
+ | `--interpreter-args <args>` | Arguments for the interpreter | — |
240
+ | `--node-args <args>` | Additional runtime arguments | — |
241
+ | `--max-memory-restart <size>` | Restart when memory exceeds limit | — |
242
+ | `--max-restarts <n>` | Maximum consecutive restarts | `16` |
243
+ | `--min-uptime <ms>` | Minimum uptime before a restart is considered stable | `1000` |
244
+ | `--restart-delay <ms>` | Delay between restarts | `0` |
245
+ | `--kill-timeout <ms>` | Grace period before SIGKILL | `5000` |
246
+ | `--no-autorestart` | Disable automatic restart | `false` |
247
+ | `--cron <expression>` | Cron expression for scheduled restarts | — |
248
+ | `--watch` | Enable file watching | `false` |
249
+ | `--ignore-watch <dirs>` | Directories to ignore | `node_modules,.git` |
250
+ | `--port <n>` | Base port (auto-incremented in cluster mode) | — |
251
+ | `--namespace <ns>` | Process namespace for grouping | — |
252
+ | `--wait-ready` | Wait for process ready signal | `false` |
253
+ | `--listen-timeout <ms>` | Timeout waiting for ready signal | `3000` |
254
+ | `--source-map-support` | Enable source map support | `false` |
255
+ | `--merge-logs` | Merge all instance logs into one file | `false` |
256
+ | `--log-date-format <fmt>` | Date format prefix for log lines | — |
257
+ | `--output <file>` | Custom stdout log path | `~/.bm2/logs/<name>-<id>-out.log` |
258
+ | `--error <file>` | Custom stderr log path | `~/.bm2/logs/<name>-<id>-error.log` |
259
+ | `--log-max-size <size>` | Max log file size before rotation | `10M` |
260
+ | `--log-retain <n>` | Number of rotated log files to keep | `5` |
261
+ | `--log-compress` | Gzip rotated log files | `false` |
262
+ | `--health-check-url <url>` | HTTP endpoint for health probes | — |
263
+ | `--health-check-interval <ms>` | Probe interval | `30000` |
264
+ | `--health-check-timeout <ms>` | Probe timeout | `5000` |
265
+ | `--health-check-max-fails <n>` | Failures before restart | `3` |
266
+
267
+ ---
268
+
269
+ #### bm2 stop
270
+
271
+ Stop a process, all processes with a name, or all processes.
272
+
273
+ ```
274
+ bm2 stop 0
275
+ bm2 stop my-api
276
+ bm2 stop my-namespace
277
+ bm2 stop all
278
+ ```
279
+
280
+ ---
281
+
282
+ #### bm2 restart
283
+
284
+ Stop and restart a process. The process is fully stopped and then re-spawned.
285
+
286
+ ```
287
+ bm2 restart my-api
288
+ bm2 restart all
289
+ ```
290
+
291
+ ---
292
+
293
+ #### bm2 reload
294
+
295
+ Graceful zero-downtime reload. New instances start before old ones are killed, ensuring your application always has live workers handling requests.
296
+
297
+ ```
298
+ bm2 reload my-api
299
+ bm2 reload all
300
+ ```
301
+
302
+ The reload process works as follows for each instance. First, a new process is spawned. Then BM2 waits for the new process to become stable or emit a ready signal if `--wait-ready` is enabled. Next, the old process receives SIGTERM and is given the kill timeout to shut down gracefully. Finally, the cycle moves to the next instance.
303
+
304
+ ---
305
+
306
+ #### bm2 delete
307
+
308
+ Stop and remove a process from BM2's management.
309
+
310
+ ```
311
+ bm2 delete 0
312
+ bm2 delete my-api
313
+ bm2 delete all
314
+ ```
315
+
316
+ ---
317
+
318
+ #### bm2 scale
319
+
320
+ Dynamically scale a process group up or down.
321
+
322
+ ```
323
+ bm2 scale my-api 8
324
+ bm2 scale my-api 2
325
+ ```
326
+
327
+ When scaling up, new instances inherit the configuration of the existing instances. When scaling down, the highest-numbered instances are stopped and removed first.
328
+
329
+ ---
330
+
331
+ #### bm2 describe
332
+
333
+ Show detailed information about a process.
334
+
335
+ ```
336
+ bm2 describe my-api
337
+ ```
338
+
339
+ Output:
340
+
341
+ ```
342
+ ┌─────────────────────┬──────────────────────────────────────────┐
343
+ │ Name │ my-api-0 │
344
+ │ ID │ 0 │
345
+ │ Status │ online │
346
+ │ PID │ 4521 │
347
+ │ Exec Mode │ cluster │
348
+ │ Instances │ 4 │
349
+ │ Uptime │ 2h 15m │
350
+ │ Restarts │ 0 │
351
+ │ Unstable Restarts │ 0 │
352
+ │ CPU │ 0.3% │
353
+ │ Memory │ 42.1 MB │
354
+ │ File Handles │ 24 │
355
+ │ Script │ /home/user/app/server.ts │
356
+ │ CWD │ /home/user/app │
357
+ │ Interpreter │ bun │
358
+ │ Watch │ disabled │
359
+ │ Max Memory Restart │ 512 MB │
360
+ │ Health Check │ http://localhost:3000/health (healthy) │
361
+ │ Cron Restart │ disabled │
362
+ │ Namespace │ production │
363
+ │ Created │ 2025-02-11T10:30:00.000Z │
364
+ │ Out Log │ /home/user/.bm2/logs/my-api-0-out.log │
365
+ │ Error Log │ /home/user/.bm2/logs/my-api-0-error.log │
366
+ └─────────────────────┴──────────────────────────────────────────┘
367
+ ```
368
+
369
+ ---
370
+
371
+ #### bm2 list
372
+
373
+ List all managed processes with their status, resource usage, and uptime.
374
+
375
+ ```
376
+ bm2 list
377
+ ```
378
+
379
+ ---
380
+
381
+ #### bm2 signal
382
+
383
+ Send an OS signal to a process.
384
+
385
+ ```
386
+ bm2 signal my-api SIGUSR2
387
+ ```
388
+
389
+ ---
390
+
391
+ #### bm2 reset
392
+
393
+ Reset the restart counter for a process.
394
+
395
+ ```
396
+ bm2 reset my-api
397
+ bm2 reset all
398
+ ```
399
+
400
+ ---
401
+
402
+ ### Cluster Mode
403
+
404
+ Cluster mode spawns multiple instances of your application, each running in its own process. This is ideal for CPU-bound workloads and for taking full advantage of multi-core servers.
405
+
406
+ ```
407
+ bm2 start server.ts --name api --instances max
408
+ ```
409
+
410
+ ```
411
+ bm2 start server.ts --name api --instances 4
412
+ ```
413
+
414
+ ```
415
+ bm2 start server.ts --name api --instances 4 --port 3000
416
+ ```
417
+
418
+ Each cluster worker receives the following environment variables:
419
+
420
+ | Variable | Description |
421
+ |---|---|
422
+ | `BM2_CLUSTER` | Set to `"true"` in cluster mode |
423
+ | `BM2_WORKER_ID` | Zero-indexed worker ID |
424
+ | `BM2_INSTANCES` | Total number of instances |
425
+ | `NODE_APP_INSTANCE` | Same as `BM2_WORKER_ID` (PM2 compatibility) |
426
+ | `PORT` | `basePort + workerIndex` (if `--port` is specified) |
427
+
428
+ Example application using cluster-aware port binding:
429
+
430
+ ```
431
+ // server.ts
432
+ const workerId = parseInt(process.env.BM2_WORKER_ID || "0");
433
+ const port = parseInt(process.env.PORT || "3000");
434
+
435
+ Bun.serve({
436
+ port,
437
+ fetch(req) {
438
+ return new Response(`Hello from worker ${workerId} on port ${port}`);
439
+ },
440
+ });
441
+
442
+ console.log(`Worker ${workerId} listening on :${port}`);
443
+ ```
444
+
445
+ ---
446
+
447
+ ### Log Management
448
+
449
+ #### bm2 logs
450
+
451
+ Display recent logs for a process.
452
+
453
+ ```
454
+ bm2 logs
455
+ ```
456
+
457
+ ```
458
+ bm2 logs my-api --lines 100
459
+ ```
460
+
461
+ ```
462
+ bm2 logs my-api --err
463
+ ```
464
+
465
+ ```
466
+ bm2 logs my-api --follow
467
+ ```
468
+
469
+ ---
470
+
471
+ #### bm2 flush
472
+
473
+ Clear log files.
474
+
475
+ ```
476
+ bm2 flush my-api
477
+ bm2 flush
478
+ ```
479
+
480
+ ---
481
+
482
+ #### Log Rotation
483
+
484
+ Log rotation runs automatically in the background. It checks log file sizes once per minute and rotates when the configured threshold is exceeded.
485
+
486
+ ```
487
+ bm2 start server.ts --log-max-size 50M --log-retain 10 --log-compress
488
+ ```
489
+
490
+ Rotation behavior: When a log file exceeds `--log-max-size`, it is renamed with a numeric suffix. Existing rotated files are shifted up by one number. Files beyond the `--log-retain` count are deleted. If `--log-compress` is enabled, rotated files are gzip-compressed using Bun's native `Bun.gzipSync`.
491
+
492
+ Default values:
493
+
494
+ | Setting | Default |
495
+ |---|---|
496
+ | `log-max-size` | `10 MB` |
497
+ | `log-retain` | `5` |
498
+ | `log-compress` | `false` |
499
+
500
+ ---
501
+
502
+ ### Monitoring and Metrics
503
+
504
+ #### bm2 monit
505
+
506
+ Open an interactive terminal monitor showing real-time CPU, memory, and event loop data for all processes.
507
+
508
+ ```
509
+ bm2 monit
510
+ ```
511
+
512
+ ---
513
+
514
+ #### bm2 metrics
515
+
516
+ Dump a current metrics snapshot as JSON.
517
+
518
+ ```
519
+ bm2 metrics
520
+ ```
521
+
522
+ Output:
523
+
524
+ ```
525
+ {
526
+ "timestamp": 1707650400000,
527
+ "processes": [
528
+ {
529
+ "id": 0,
530
+ "name": "my-api-0",
531
+ "pid": 4521,
532
+ "cpu": 0.3,
533
+ "memory": 44150784,
534
+ "handles": 24,
535
+ "status": "online",
536
+ "restarts": 0,
537
+ "uptime": 8100000
538
+ }
539
+ ],
540
+ "system": {
541
+ "totalMemory": 17179869184,
542
+ "freeMemory": 8589934592,
543
+ "cpuCount": 8,
544
+ "loadAvg": [1.23, 1.45, 1.67],
545
+ "platform": "linux"
546
+ }
547
+ }
548
+ ```
549
+
550
+ ---
551
+
552
+ #### bm2 metrics --history
553
+
554
+ Retrieve historical metrics. BM2 retains up to 1 hour of per-second snapshots in memory.
555
+
556
+ ```
557
+ bm2 metrics --history 600
558
+ ```
559
+
560
+ ---
561
+
562
+ #### bm2 prometheus
563
+
564
+ Output current metrics in Prometheus exposition format.
565
+
566
+ ```
567
+ bm2 prometheus
568
+ ```
569
+
570
+ Output:
571
+
572
+ ```
573
+ # HELP bm2_process_cpu CPU usage percentage
574
+ # TYPE bm2_process_cpu gauge
575
+ bm2_process_cpu{name="my-api-0",id="0"} 0.3
576
+ # HELP bm2_process_memory_bytes Memory usage in bytes
577
+ # TYPE bm2_process_memory_bytes gauge
578
+ bm2_process_memory_bytes{name="my-api-0",id="0"} 44150784
579
+ # HELP bm2_process_restarts_total Total restart count
580
+ # TYPE bm2_process_restarts_total counter
581
+ bm2_process_restarts_total{name="my-api-0",id="0"} 0
582
+ # HELP bm2_process_uptime_seconds Process uptime in seconds
583
+ # TYPE bm2_process_uptime_seconds gauge
584
+ bm2_process_uptime_seconds{name="my-api-0",id="0"} 8100
585
+ # HELP bm2_process_status Process status (1=online)
586
+ # TYPE bm2_process_status gauge
587
+ bm2_process_status{name="my-api-0",id="0",status="online"} 1
588
+ # HELP bm2_system_memory_total_bytes Total system memory
589
+ # TYPE bm2_system_memory_total_bytes gauge
590
+ bm2_system_memory_total_bytes 17179869184
591
+ # HELP bm2_system_memory_free_bytes Free system memory
592
+ # TYPE bm2_system_memory_free_bytes gauge
593
+ bm2_system_memory_free_bytes 8589934592
594
+ # HELP bm2_system_load_average System load average
595
+ # TYPE bm2_system_load_average gauge
596
+ bm2_system_load_average{period="1m"} 1.23
597
+ bm2_system_load_average{period="5m"} 1.45
598
+ bm2_system_load_average{period="15m"} 1.67
599
+ ```
600
+
601
+ ---
602
+
603
+ ### Dashboard
604
+
605
+ #### bm2 dashboard
606
+
607
+ Launch the built-in web dashboard.
608
+
609
+ ```
610
+ bm2 dashboard
611
+ ```
612
+
613
+ ```
614
+ bm2 dashboard --port 8080 --metrics-port 8081
615
+ ```
616
+
617
+ #### bm2 dashboard stop
618
+
619
+ Stop the web dashboard.
620
+
621
+ ```
622
+ bm2 dashboard stop
623
+ ```
624
+
625
+ See the Web Dashboard section below for a detailed description of dashboard capabilities.
626
+
627
+ ---
628
+
629
+ ### Ecosystem Files
630
+
631
+ An ecosystem file defines your entire application topology in a single configuration. BM2 supports JSON and TypeScript ecosystem files.
632
+
633
+ ```
634
+ bm2 start ecosystem.config.json
635
+ ```
636
+
637
+ ```
638
+ bm2 start ecosystem.config.ts
639
+ ```
640
+
641
+ Example `ecosystem.config.json`:
642
+
643
+ ```
644
+ {
645
+ "apps": [
646
+ {
647
+ "name": "api",
648
+ "script": "./src/api/server.ts",
649
+ "instances": 4,
650
+ "execMode": "cluster",
651
+ "port": 3000,
652
+ "env": {
653
+ "NODE_ENV": "production",
654
+ "DATABASE_URL": "postgres://localhost/mydb"
655
+ },
656
+ "maxMemoryRestart": "512M",
657
+ "healthCheckUrl": "http://localhost:3000/health",
658
+ "healthCheckInterval": 15000,
659
+ "logMaxSize": "50M",
660
+ "logRetain": 10,
661
+ "logCompress": true
662
+ },
663
+ {
664
+ "name": "worker",
665
+ "script": "./src/worker/index.ts",
666
+ "instances": 2,
667
+ "env": {
668
+ "NODE_ENV": "production",
669
+ "REDIS_URL": "redis://localhost:6379"
670
+ },
671
+ "cron": "0 */6 * * *",
672
+ "maxRestarts": 50
673
+ },
674
+ {
675
+ "name": "scheduler",
676
+ "script": "./src/scheduler/cron.ts",
677
+ "instances": 1,
678
+ "autorestart": true,
679
+ "watch": ["./src/scheduler"]
680
+ }
681
+ ],
682
+ "deploy": {
683
+ "production": {
684
+ "user": "deploy",
685
+ "host": ["web1.example.com", "web2.example.com"],
686
+ "ref": "origin/main",
687
+ "repo": "git@github.com:your-org/your-app.git",
688
+ "path": "/var/www/app",
689
+ "preDeploy": "bun test",
690
+ "postDeploy": "bun install && bm2 reload ecosystem.config.json --env production"
691
+ }
692
+ }
693
+ }
694
+ ```
695
+
696
+ Example `ecosystem.config.ts`:
697
+
698
+ ```
699
+ // ecosystem.config.ts
700
+ import type { EcosystemConfig } from "bm2/types";
701
+
702
+ const config: EcosystemConfig = {
703
+ apps: [
704
+ {
705
+ name: "api",
706
+ script: "./src/server.ts",
707
+ instances: "max",
708
+ execMode: "cluster",
709
+ port: 3000,
710
+ env: {
711
+ NODE_ENV: "production",
712
+ },
713
+ maxMemoryRestart: "1G",
714
+ healthCheckUrl: "http://localhost:3000/health",
715
+ },
716
+ ],
717
+ };
718
+
719
+ export default config;
720
+ ```
721
+
722
+ ---
723
+
724
+ ### Environment Management
725
+
726
+ #### bm2 env set
727
+
728
+ Set an environment variable for a process.
729
+
730
+ ```
731
+ bm2 env set my-api DATABASE_URL postgres://localhost/mydb
732
+ ```
733
+
734
+ #### bm2 env get
735
+
736
+ List all stored environment variables for a process.
737
+
738
+ ```
739
+ bm2 env get my-api
740
+ ```
741
+
742
+ #### bm2 env delete
743
+
744
+ Remove an environment variable or all environment variables.
745
+
746
+ ```
747
+ bm2 env delete my-api DATABASE_URL
748
+ bm2 env delete my-api
749
+ ```
750
+
751
+ #### .env File Support
752
+
753
+ BM2 can load environment variables from `.env` files:
754
+
755
+ ```
756
+ bm2 start server.ts --env-file .env.production
757
+ ```
758
+
759
+ ---
760
+
761
+ ### Deployment
762
+
763
+ BM2 includes a built-in deployment system for SSH-based deployments with release management.
764
+
765
+ #### bm2 deploy setup
766
+
767
+ Initial setup of the remote server. Creates the directory structure and clones the repository.
768
+
769
+ ```
770
+ bm2 deploy ecosystem.config.json production setup
771
+ ```
772
+
773
+ This creates the following remote directory structure:
774
+
775
+ ```
776
+ /var/www/app/
777
+ ├── source/
778
+ ├── releases/
779
+ │ ├── 2025-02-11T10-30-00-000Z/
780
+ │ └── 2025-02-10T15-45-00-000Z/
781
+ ├── current -> releases/2025-02-11T10-30-00-000Z/
782
+ └── shared/
783
+ ```
784
+
785
+ #### bm2 deploy
786
+
787
+ Deploy a new release.
788
+
789
+ ```
790
+ bm2 deploy ecosystem.config.json production
791
+ ```
792
+
793
+ The deploy process works as follows. It runs the `preDeploy` hook locally such as running tests. It connects via SSH to each configured host. It pulls the latest code from the configured ref. It creates a new timestamped release directory. It updates the current symlink to the new release. It runs the `postDeploy` hook remotely such as installing dependencies and reloading processes. It cleans up old releases, keeping only the 5 most recent.
794
+
795
+ Multi-host deployment is supported. Specify an array of hosts to deploy to all of them sequentially:
796
+
797
+ ```
798
+ {
799
+ "host": ["web1.example.com", "web2.example.com", "web3.example.com"]
800
+ }
801
+ ```
802
+
803
+ ---
804
+
805
+ ### Startup Scripts
806
+
807
+ #### bm2 startup
808
+
809
+ Generate and display a startup script for your operating system. On Linux, this generates a systemd unit file. On macOS, this generates a launchd plist.
810
+
811
+ ```
812
+ bm2 startup
813
+ ```
814
+
815
+ #### bm2 startup install
816
+
817
+ Automatically install the startup script so the BM2 daemon starts at boot.
818
+
819
+ ```
820
+ bm2 startup install
821
+ ```
822
+
823
+ #### bm2 startup uninstall
824
+
825
+ Remove the startup script.
826
+
827
+ ```
828
+ bm2 startup uninstall
829
+ ```
830
+
831
+ #### bm2 save
832
+
833
+ Save the current process list so it can be restored on daemon startup.
834
+
835
+ ```
836
+ bm2 save
837
+ ```
838
+
839
+ #### bm2 resurrect
840
+
841
+ Restore previously saved processes.
842
+
843
+ ```
844
+ bm2 resurrect
845
+ ```
846
+
847
+ Recommended boot setup:
848
+
849
+ ```
850
+ bm2 start ecosystem.config.json
851
+ bm2 save
852
+ bm2 startup install
853
+ ```
854
+
855
+ On reboot, systemd or launchd starts the BM2 daemon, and the daemon automatically runs resurrect to restore your processes.
856
+
857
+ ---
858
+
859
+ ### Modules
860
+
861
+ BM2 supports a plugin system for extending functionality.
862
+
863
+ #### bm2 module install
864
+
865
+ Install a module from a git URL, local path, or npm package name.
866
+
867
+ ```
868
+ bm2 module install https://github.com/user/bm2-logrotate.git
869
+ bm2 module install ./my-bm2-module
870
+ bm2 module install bm2-prometheus-pushgateway
871
+ ```
872
+
873
+ #### bm2 module list
874
+
875
+ List installed modules.
876
+
877
+ ```
878
+ bm2 module list
879
+ ```
880
+
881
+ #### bm2 module uninstall
882
+
883
+ Remove an installed module.
884
+
885
+ ```
886
+ bm2 module uninstall bm2-prometheus-pushgateway
887
+ ```
888
+
889
+ #### Writing a BM2 Module
890
+
891
+ A BM2 module is a package with a default export implementing the BM2Module interface:
892
+
893
+ ```
894
+ // my-module/index.ts
895
+ import type { ProcessManager } from "bm2/process-manager";
896
+
897
+ export default {
898
+ name: "my-module",
899
+ version: "1.0.0",
900
+
901
+ init(pm: ProcessManager) {
902
+ console.log("[my-module] Initialized with", pm.list().length, "processes");
903
+ },
904
+
905
+ destroy() {
906
+ console.log("[my-module] Destroyed");
907
+ },
908
+ };
909
+ ```
910
+
911
+ ---
912
+
913
+ ### Daemon Control
914
+
915
+ #### bm2 ping
916
+
917
+ Check if the daemon is running.
918
+
919
+ ```
920
+ bm2 ping
921
+ ```
922
+
923
+ #### bm2 kill
924
+
925
+ Stop all processes and kill the daemon.
926
+
927
+ ```
928
+ bm2 kill
929
+ ```
930
+
931
+ ---
932
+
933
+ ## Configuration Reference
934
+
935
+ ### Ecosystem File Format
936
+
937
+ The ecosystem file is a JSON or TypeScript file with the following top-level structure:
938
+
939
+ ```
940
+ interface EcosystemConfig {
941
+ apps: StartOptions[];
942
+ deploy?: Record<string, DeployConfig>;
943
+ }
944
+ ```
945
+
946
+ ---
947
+
948
+ ### Process Options
949
+
950
+ The complete set of options available for each entry in the apps array:
951
+
952
+ | Option | Type | Default | Description |
953
+ |---|---|---|---|
954
+ | `name` | `string` | Filename | Process name |
955
+ | `script` | `string` | required | Path to the script to execute |
956
+ | `args` | `string[]` | `[]` | Arguments passed to the script |
957
+ | `cwd` | `string` | `process.cwd()` | Working directory |
958
+ | `env` | `Record<string, string>` | `{}` | Environment variables |
959
+ | `instances` | `number` or `"max"` | `1` | Number of instances |
960
+ | `execMode` | `"fork"` or `"cluster"` | `"fork"` | Execution mode |
961
+ | `autorestart` | `boolean` | `true` | Restart on crash |
962
+ | `maxRestarts` | `number` | `16` | Maximum restart attempts before giving up |
963
+ | `minUptime` | `number` | `1000` | Minimum ms a process must be up to be considered stable |
964
+ | `maxMemoryRestart` | `string` or `number` | — | Memory threshold for restart |
965
+ | `restartDelay` | `number` | `0` | Delay in ms between restart attempts |
966
+ | `killTimeout` | `number` | `5000` | Grace period in ms before SIGKILL |
967
+ | `interpreter` | `string` | Auto | Custom interpreter |
968
+ | `interpreterArgs` | `string[]` | — | Arguments for the interpreter |
969
+ | `nodeArgs` | `string[]` | — | Additional runtime arguments |
970
+ | `namespace` | `string` | — | Namespace for grouping processes |
971
+ | `sourceMapSupport` | `boolean` | `false` | Enable source map support |
972
+ | `waitReady` | `boolean` | `false` | Wait for process to emit ready signal |
973
+ | `listenTimeout` | `number` | `3000` | Timeout when waiting for ready signal |
974
+
975
+ ---
976
+
977
+ ### Cluster Options
978
+
979
+ | Option | Type | Default | Description |
980
+ |---|---|---|---|
981
+ | `instances` | `number` or `"max"` | `1` | Worker count |
982
+ | `execMode` | `"cluster"` | `"fork"` | Set to cluster for multi-instance mode |
983
+ | `port` | `number` | — | Base port. Worker i gets port + i |
984
+
985
+ ---
986
+
987
+ ### Log Options
988
+
989
+ | Option | Type | Default | Description |
990
+ |---|---|---|---|
991
+ | `outFile` | `string` | `~/.bm2/logs/<name>-<id>-out.log` | Custom stdout log path |
992
+ | `errorFile` | `string` | `~/.bm2/logs/<name>-<id>-error.log` | Custom stderr log path |
993
+ | `mergeLogs` | `boolean` | `false` | Merge all instance logs into one file |
994
+ | `logDateFormat` | `string` | — | Date format for log line prefixes |
995
+ | `logMaxSize` | `string` or `number` | `"10M"` | Max log file size before rotation |
996
+ | `logRetain` | `number` | `5` | Number of rotated files to keep |
997
+ | `logCompress` | `boolean` | `false` | Gzip-compress rotated log files |
998
+
999
+ ---
1000
+
1001
+ ### Health Check Options
1002
+
1003
+ | Option | Type | Default | Description |
1004
+ |---|---|---|---|
1005
+ | `healthCheckUrl` | `string` | — | URL to probe |
1006
+ | `healthCheckInterval` | `number` | `30000` | Probe interval in ms |
1007
+ | `healthCheckTimeout` | `number` | `5000` | Probe timeout in ms |
1008
+ | `healthCheckMaxFails` | `number` | `3` | Consecutive failures before restart |
1009
+
1010
+ ---
1011
+
1012
+ ### Watch Options
1013
+
1014
+ | Option | Type | Default | Description |
1015
+ |---|---|---|---|
1016
+ | `watch` | `boolean` or `string[]` | `false` | Enable file watching |
1017
+ | `ignoreWatch` | `string[]` | `["node_modules", ".git", ".bm2"]` | Patterns to ignore |
1018
+
1019
+ ---
1020
+
1021
+ ### Deploy Configuration
1022
+
1023
+ | Option | Type | Description |
1024
+ |---|---|---|
1025
+ | `user` | `string` | SSH user |
1026
+ | `host` | `string` or `string[]` | Remote host(s) |
1027
+ | `ref` | `string` | Git ref to deploy |
1028
+ | `repo` | `string` | Git repository URL |
1029
+ | `path` | `string` | Remote deployment path |
1030
+ | `preDeploy` | `string` | Command to run locally before deploy |
1031
+ | `postDeploy` | `string` | Command to run remotely after deploy |
1032
+ | `preSetup` | `string` | Command to run remotely during setup |
1033
+ | `postSetup` | `string` | Command to run remotely after setup |
1034
+ | `ssh_options` | `string` | Additional SSH options |
1035
+ | `env` | `Record<string, string>` | Environment variables for remote commands |
1036
+
1037
+ ---
1038
+
1039
+ ## Web Dashboard
1040
+
1041
+ The BM2 dashboard is a self-contained web application served directly by the daemon. It requires no external dependencies. The HTML, CSS, JavaScript, and WebSocket server are all built in.
1042
+
1043
+ ### Dashboard Features
1044
+
1045
+ **Process Overview** — Four summary cards showing counts of online and errored processes, total CPU usage, and aggregate memory consumption.
1046
+
1047
+ **System Information** — Platform, CPU count, load average, and memory usage with a visual progress bar.
1048
+
1049
+ **CPU and Memory Chart** — A real-time canvas-rendered chart showing aggregate CPU percentage and memory usage over the last 60 data points, updating every 2 seconds.
1050
+
1051
+ **Process Table** — A detailed table showing every managed process with columns for ID, name, status with color-coded badges, PID, CPU, memory, restart count, uptime, and action buttons for restart, stop, and log viewing.
1052
+
1053
+ **Log Viewer** — A tabbed log panel that streams stdout and stderr from any selected process, with syntax highlighting for timestamps and error output. Logs auto-scroll to the latest entry.
1054
+
1055
+ **Live Updates** — All data is streamed over WebSocket with a visual pulse indicator confirming the live connection. If the connection drops, the dashboard automatically reconnects within 2 seconds.
1056
+
1057
+ ---
1058
+
1059
+ ### REST API
1060
+
1061
+ The dashboard exposes a REST API on the same port:
1062
+
1063
+ | Method | Endpoint | Description |
1064
+ |---|---|---|
1065
+ | `GET` | `/` | Dashboard HTML |
1066
+ | `GET` | `/api/processes` | List all processes as JSON |
1067
+ | `GET` | `/api/metrics` | Current metrics snapshot |
1068
+ | `GET` | `/api/metrics/history?seconds=300` | Historical metrics |
1069
+ | `GET` | `/api/prometheus` or `/metrics` | Prometheus text format |
1070
+ | `POST` | `/api/restart` | Restart process |
1071
+ | `POST` | `/api/stop` | Stop process |
1072
+ | `POST` | `/api/reload` | Graceful reload |
1073
+ | `POST` | `/api/delete` | Delete process |
1074
+ | `POST` | `/api/scale` | Scale process |
1075
+ | `POST` | `/api/flush` | Flush logs |
1076
+
1077
+ POST endpoints accept JSON body with `target` field for process identification and additional fields where applicable such as `count` for scaling.
1078
+
1079
+ Example using curl:
1080
+
1081
+ ```
1082
+ curl http://localhost:9615/api/processes
1083
+ ```
1084
+
1085
+ ```
1086
+ curl -X POST http://localhost:9615/api/restart \
1087
+ -H "Content-Type: application/json" \
1088
+ -d '{"target": "my-api"}'
1089
+ ```
1090
+
1091
+ ```
1092
+ curl -X POST http://localhost:9615/api/scale \
1093
+ -H "Content-Type: application/json" \
1094
+ -d '{"target": "my-api", "count": 8}'
1095
+ ```
1096
+
1097
+ ```
1098
+ curl http://localhost:9615/metrics
1099
+ ```
1100
+
1101
+ ---
1102
+
1103
+ ### WebSocket API
1104
+
1105
+ Connect to `ws://localhost:9615/ws` for real-time bidirectional communication.
1106
+
1107
+ Client to server messages:
1108
+
1109
+ ```
1110
+ { "type": "getState", "data": {} }
1111
+ ```
1112
+
1113
+ ```
1114
+ { "type": "getLogs", "data": { "target": 0, "lines": 50 } }
1115
+ ```
1116
+
1117
+ ```
1118
+ { "type": "restart", "data": { "target": "my-api" } }
1119
+ ```
1120
+
1121
+ ```
1122
+ { "type": "stop", "data": { "target": 0 } }
1123
+ ```
1124
+
1125
+ ```
1126
+ { "type": "reload", "data": { "target": "all" } }
1127
+ ```
1128
+
1129
+ ```
1130
+ { "type": "scale", "data": { "target": "my-api", "count": 4 } }
1131
+ ```
1132
+
1133
+ Server to client messages:
1134
+
1135
+ ```
1136
+ {
1137
+ "type": "state",
1138
+ "data": {
1139
+ "processes": [],
1140
+ "metrics": {
1141
+ "timestamp": 1707650400000,
1142
+ "processes": [],
1143
+ "system": {}
1144
+ }
1145
+ }
1146
+ }
1147
+ ```
1148
+
1149
+ ```
1150
+ {
1151
+ "type": "logs",
1152
+ "data": [
1153
+ { "name": "my-api-0", "id": 0, "out": "...", "err": "..." }
1154
+ ]
1155
+ }
1156
+ ```
1157
+
1158
+ ---
1159
+
1160
+ ## Prometheus and Grafana Integration
1161
+
1162
+ BM2 runs a dedicated Prometheus metrics server on default port 9616 separately from the dashboard, following best practices for metrics collection.
1163
+
1164
+ ### Prometheus Configuration
1165
+
1166
+ Add the following to your `prometheus.yml`:
1167
+
1168
+ ```
1169
+ scrape_configs:
1170
+ - job_name: "bm2"
1171
+ scrape_interval: 5s
1172
+ static_configs:
1173
+ - targets: ["localhost:9616"]
1174
+ ```
1175
+
1176
+ ### Available Metrics
1177
+
1178
+ | Metric | Type | Labels | Description |
1179
+ |---|---|---|---|
1180
+ | `bm2_process_cpu` | gauge | `name`, `id` | CPU usage percentage |
1181
+ | `bm2_process_memory_bytes` | gauge | `name`, `id` | Memory usage in bytes |
1182
+ | `bm2_process_restarts_total` | counter | `name`, `id` | Total restart count |
1183
+ | `bm2_process_uptime_seconds` | gauge | `name`, `id` | Uptime in seconds |
1184
+ | `bm2_process_status` | gauge | `name`, `id`, `status` | 1 if online, 0 otherwise |
1185
+ | `bm2_system_memory_total_bytes` | gauge | — | Total system memory |
1186
+ | `bm2_system_memory_free_bytes` | gauge | — | Free system memory |
1187
+ | `bm2_system_load_average` | gauge | `period` | Load average (1m, 5m, 15m) |
1188
+
1189
+ ### Grafana Dashboard
1190
+
1191
+ Import a dashboard with the following panels for comprehensive monitoring: Process Status Overview as a stat panel colored by status, CPU Usage per Process as a time series with `bm2_process_cpu` grouped by name, Memory Usage per Process as a time series with `bm2_process_memory_bytes` grouped by name, Restart Rate as a graph of `rate(bm2_process_restarts_total[5m])` to detect instability, System Load as a time series of `bm2_system_load_average` across all periods, and Memory Pressure as a gauge computing `1 - (bm2_system_memory_free_bytes / bm2_system_memory_total_bytes)`.
1192
+
1193
+ ### Alert Rules Example
1194
+
1195
+ ```
1196
+ groups:
1197
+ - name: bm2
1198
+ rules:
1199
+ - alert: ProcessDown
1200
+ expr: bm2_process_status == 0
1201
+ for: 1m
1202
+ labels:
1203
+ severity: critical
1204
+ annotations:
1205
+ summary: "Process {{ \$labels.name }} is down"
1206
+
1207
+ - alert: HighRestartRate
1208
+ expr: rate(bm2_process_restarts_total[5m]) > 0.1
1209
+ for: 5m
1210
+ labels:
1211
+ severity: warning
1212
+ annotations:
1213
+ summary: "Process {{ \$labels.name }} is restarting frequently"
1214
+
1215
+ - alert: HighMemoryUsage
1216
+ expr: bm2_process_memory_bytes > 1e9
1217
+ for: 5m
1218
+ labels:
1219
+ severity: warning
1220
+ annotations:
1221
+ summary: "Process {{ \$labels.name }} using > 1GB memory"
1222
+ ```
1223
+
1224
+ ---
1225
+
1226
+ ## Programmatic API
1227
+
1228
+ BM2 can be used as a library in your own Bun applications:
1229
+
1230
+ ```
1231
+ // app.ts
1232
+ import { ProcessManager } from "bm2/process-manager";
1233
+ import { Dashboard } from "bm2/dashboard";
1234
+
1235
+ const pm = new ProcessManager();
1236
+
1237
+ // Start a process
1238
+ const states = await pm.start({
1239
+ name: "my-api",
1240
+ script: "./server.ts",
1241
+ instances: 4,
1242
+ execMode: "cluster",
1243
+ port: 3000,
1244
+ env: { NODE_ENV: "production" },
1245
+ maxMemoryRestart: "512M",
1246
+ healthCheckUrl: "http://localhost:3000/health",
1247
+ });
1248
+
1249
+ console.log("Started:", states.map((s) => `${s.name} (pid: ${s.pid})`));
1250
+
1251
+ // List processes
1252
+ const list = pm.list();
1253
+
1254
+ // Get metrics
1255
+ const metrics = await pm.getMetrics();
1256
+
1257
+ // Scale
1258
+ await pm.scale("my-api", 8);
1259
+
1260
+ // Graceful reload
1261
+ await pm.reload("my-api");
1262
+
1263
+ // Start the web dashboard
1264
+ const dashboard = new Dashboard(pm);
1265
+ dashboard.start(9615, 9616);
1266
+
1267
+ // Get Prometheus-format metrics
1268
+ const promText = pm.getPrometheusMetrics();
1269
+
1270
+ // Save and restore
1271
+ await pm.save();
1272
+ await pm.resurrect();
1273
+
1274
+ // Stop everything
1275
+ await pm.stopAll();
1276
+ ```
1277
+
1278
+ ---
1279
+
1280
+ ## Architecture
1281
+
1282
+ ```
1283
+ ┌─────────────────────────────────────────────────────────┐
1284
+ │ BM2 CLI │
1285
+ │ (bm2 start, bm2 list, bm2 restart, bm2 dashboard) │
1286
+ └────────────────────────┬────────────────────────────────┘
1287
+ │ Unix Socket (WebSocket)
1288
+ │ ~/.bm2/daemon.sock
1289
+
1290
+ ┌─────────────────────────────────────────────────────────┐
1291
+ │ BM2 Daemon │
1292
+ │ │
1293
+ │ ┌─────────────────┐ ┌──────────────┐ ┌───────────┐ │
1294
+ │ │ Process Manager │ │ Dashboard │ │ Modules │ │
1295
+ │ │ │ │ (Bun.serve) │ │ (Plugins)│ │
1296
+ │ │ ┌───────────┐ │ │ HTTP + WS │ └───────────┘ │
1297
+ │ │ │ Container │ │ │ REST API │ │
1298
+ │ │ │ (Bun.spawn)│ │ └──────────────┘ │
1299
+ │ │ └───────────┘ │ │
1300
+ │ │ ┌───────────┐ │ ┌──────────────┐ ┌───────────┐ │
1301
+ │ │ │ Container │ │ │ Monitor │ │ Metrics │ │
1302
+ │ │ │ (Bun.spawn)│ │ │ CPU/Memory │ │ Prometheus│ │
1303
+ │ │ └───────────┘ │ └──────────────┘ │ :9616 │ │
1304
+ │ │ ┌───────────┐ │ └───────────┘ │
1305
+ │ │ │ Container │ │ ┌──────────────┐ │
1306
+ │ │ │ (Bun.spawn)│ │ │ Health Check │ │
1307
+ │ │ └───────────┘ │ │ HTTP Probes │ │
1308
+ │ └─────────────────┘ └──────────────┘ │
1309
+ │ │
1310
+ │ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌─────────────┐ │
1311
+ │ │ Cluster │ │ Logs │ │ Cron │ │ Deploy │ │
1312
+ │ │ Manager │ │ Manager │ │Manager │ │ Manager │ │
1313
+ │ └──────────┘ └──────────┘ └────────┘ └─────────────┘ │
1314
+ └─────────────────────────────────────────────────────────┘
1315
+ ```
1316
+
1317
+ **Daemon Process** — The daemon is a long-running Bun process that manages all child processes. It listens on a Unix domain socket at `~/.bm2/daemon.sock` for commands from the CLI. The daemon is automatically started when you first run a BM2 command and can be explicitly killed with `bm2 kill`.
1318
+
1319
+ **Process Container** — Each managed process is wrapped in a ProcessContainer that handles spawning via `Bun.spawn`, log piping, monitoring, restart logic, health checking, watch mode, and signal handling.
1320
+
1321
+ **IPC Protocol** — The CLI and daemon communicate over WebSocket on a Unix socket. Messages are JSON-encoded with a `type` field for routing and an `id` field for request-response correlation.
1322
+
1323
+ **Dashboard** — The dashboard is served by a `Bun.serve` instance with WebSocket upgrade support. A single HTTP server handles the dashboard UI, REST API, and WebSocket connections.
1324
+
1325
+ **Metrics Server** — A separate `Bun.serve` instance on port 9616 serves Prometheus metrics, keeping the scrape endpoint isolated from dashboard traffic.
1326
+
1327
+ ---
1328
+
1329
+ ## Comparison with PM2
1330
+
1331
+ | Feature | PM2 | BM2 |
1332
+ |---|---|---|
1333
+ | Runtime | Node.js | Bun |
1334
+ | Language | JavaScript | TypeScript |
1335
+ | Dependencies | ~40+ packages | Zero (Bun built-ins only) |
1336
+ | Process Spawning | `child_process.fork` | `Bun.spawn` |
1337
+ | IPC | Custom protocol over pipes | WebSocket over Unix socket |
1338
+ | HTTP Server | Express/http | `Bun.serve` |
1339
+ | Log Compression | External `pm2-logrotate` module | Built-in `Bun.gzipSync` |
1340
+ | Dashboard | PM2 Plus (paid) or `pm2-monit` | Built-in web dashboard (free) |
1341
+ | Prometheus Metrics | `pm2-prometheus-exporter` module | Built-in native export |
1342
+ | Startup Time | ~500ms | ~50ms |
1343
+ | Memory Overhead | ~40MB (daemon) | ~12MB (daemon) |
1344
+ | Cluster Mode | `cluster` module | `Bun.spawn` with env-based routing |
1345
+ | Ecosystem Files | JSON, JS, YAML | JSON, TypeScript |
1346
+ | Deploy System | Built-in | Built-in |
1347
+ | Module System | `pm2 install` | `bm2 module install` |
1348
+ | TypeScript | Requires compilation | Native support |
1349
+ | File Watching | `chokidar` | Native `fs.watch` |
1350
+
1351
+ ---
1352
+
1353
+ ## Recipes and Examples
1354
+
1355
+ ### Basic HTTP Server
1356
+
1357
+ ```
1358
+ bm2 start server.ts --name api
1359
+ ```
1360
+
1361
+ ### Production API with Clustering and Health Checks
1362
+
1363
+ ```
1364
+ bm2 start server.ts \
1365
+ --name api \
1366
+ --instances max \
1367
+ --port 3000 \
1368
+ --max-memory-restart 512M \
1369
+ --health-check-url http://localhost:3000/health \
1370
+ --health-check-interval 15000 \
1371
+ --log-max-size 50M \
1372
+ --log-retain 10 \
1373
+ --log-compress
1374
+ ```
1375
+
1376
+ ### Development Mode with Watch
1377
+
1378
+ ```
1379
+ bm2 start server.ts --name dev-api --watch --ignore-watch node_modules,.git,dist
1380
+ ```
1381
+
1382
+ ### Python Script
1383
+
1384
+ ```
1385
+ bm2 start worker.py --name py-worker --interpreter python3
1386
+ ```
1387
+
1388
+ ### Scheduled Restart (Daily at 3 AM)
1389
+
1390
+ ```
1391
+ bm2 start server.ts --name api --cron "0 3 * * *"
1392
+ ```
1393
+
1394
+ ### Multiple Environments via Ecosystem
1395
+
1396
+ ```
1397
+ {
1398
+ "apps": [
1399
+ {
1400
+ "name": "api-staging",
1401
+ "script": "./server.ts",
1402
+ "env": { "NODE_ENV": "staging", "PORT": "3000" }
1403
+ },
1404
+ {
1405
+ "name": "api-production",
1406
+ "script": "./server.ts",
1407
+ "env": { "NODE_ENV": "production", "PORT": "8080" }
1408
+ }
1409
+ ]
1410
+ }
1411
+ ```
1412
+
1413
+ ### Full Production Setup
1414
+
1415
+ ```
1416
+ bm2 start ecosystem.config.json
1417
+ bm2 save
1418
+ bm2 startup install
1419
+ bm2 dashboard
1420
+ bm2 list
1421
+ ```
1422
+
1423
+ ### Monitoring with Prometheus and Grafana
1424
+
1425
+ ```
1426
+ bm2 dashboard --metrics-port 9616
1427
+ curl http://localhost:9616/metrics
1428
+ ```
1429
+
1430
+ Then add the target to your `prometheus.yml` and import the Grafana dashboard.
1431
+
1432
+ ### Zero-Downtime Deploy
1433
+
1434
+ ```
1435
+ bm2 deploy ecosystem.config.json production
1436
+ ```
1437
+
1438
+ Or manually:
1439
+
1440
+ ```
1441
+ git pull origin main
1442
+ bun install
1443
+ bm2 reload all
1444
+ ```
1445
+
1446
+ ---
1447
+
1448
+ ## Troubleshooting
1449
+
1450
+ ### Daemon won't start
1451
+
1452
+ If BM2 commands hang or return connection errors, the daemon may have died without cleanup.
1453
+
1454
+ ```
1455
+ rm -f ~/.bm2/daemon.sock ~/.bm2/daemon.pid
1456
+ bm2 list
1457
+ ```
1458
+
1459
+ ### Process keeps restarting
1460
+
1461
+ Check the error logs for crash information:
1462
+
1463
+ ```
1464
+ bm2 logs my-app --err --lines 100
1465
+ ```
1466
+
1467
+ If the process exits too quickly, it may hit the max restart limit. Check `minUptime` and `maxRestarts` settings:
1468
+
1469
+ ```
1470
+ bm2 describe my-app
1471
+ ```
1472
+
1473
+ Reset the counter if needed:
1474
+
1475
+ ```
1476
+ bm2 reset my-app
1477
+ ```
1478
+
1479
+ ### High memory usage
1480
+
1481
+ If a process is using excessive memory and you have `maxMemoryRestart` configured, BM2 will restart it automatically. You can also check the metrics history:
1482
+
1483
+ ```
1484
+ bm2 metrics --history 3600
1485
+ ```
1486
+
1487
+ ### Port conflicts
1488
+
1489
+ In cluster mode, each instance uses `basePort + instanceIndex`. Ensure no other services are using those ports:
1490
+
1491
+ ```
1492
+ lsof -i :3000-3007
1493
+ ```
1494
+
1495
+ ### Log files growing too large
1496
+
1497
+ Enable log rotation:
1498
+
1499
+ ```
1500
+ bm2 start server.ts --log-max-size 50M --log-retain 5 --log-compress
1501
+ ```
1502
+
1503
+ Or flush existing logs:
1504
+
1505
+ ```
1506
+ bm2 flush my-app
1507
+ ```
1508
+
1509
+ ### Dashboard not accessible
1510
+
1511
+ Ensure the dashboard is started and check the port:
1512
+
1513
+ ```
1514
+ bm2 dashboard --port 9615
1515
+ curl http://localhost:9615
1516
+ ```
1517
+
1518
+ If running behind a firewall, ensure port 9615 (dashboard) and 9616 (metrics) are open.
1519
+
1520
+ ### Checking daemon health
1521
+
1522
+ ```
1523
+ bm2 ping
1524
+ ```
1525
+
1526
+ This returns the daemon PID and uptime. If it doesn't respond, the daemon needs to be restarted.
1527
+
1528
+ ---
1529
+
1530
+ ## File Structure
1531
+
1532
+ BM2 stores all data in `~/.bm2/`:
1533
+
1534
+ ```
1535
+ ~/.bm2/
1536
+ ├── daemon.sock # Unix domain socket for IPC
1537
+ ├── daemon.pid # Daemon process ID
1538
+ ├── dump.json # Saved process list (bm2 save)
1539
+ ├── config.json # Global configuration
1540
+ ├── env-registry.json # Stored environment variables
1541
+ ├── logs/ # Process log files
1542
+ │ ├── my-api-0-out.log
1543
+ │ ├── my-api-0-error.log
1544
+ │ ├── my-api-0-out.log.1.gz
1545
+ │ └── daemon-out.log
1546
+ ├── pids/ # PID files
1547
+ │ └── my-api-0.pid
1548
+ ├── metrics/ # Persisted metric snapshots
1549
+ └── modules/ # Installed BM2 modules
1550
+ ```
1551
+
1552
+ ---
1553
+
1554
+ ## Contributing
1555
+
1556
+ Contributions are welcome. Please follow these guidelines:
1557
+
1558
+ 1. Fork the repository and create a feature branch.
1559
+ 2. Write tests for new functionality.
1560
+ 3. Follow the existing code style — TypeScript strict mode, no `any` where avoidable.
1561
+ 4. Run the test suite before submitting: `bun test`.
1562
+ 5. Submit a pull request with a clear description of the change.
1563
+
1564
+ ### Development Setup
1565
+
1566
+ ```
1567
+ git clone https://github.com/aspect-dev/bm2.git
1568
+ cd bm2
1569
+ bun install
1570
+ bun run src/index.ts list
1571
+ bun test
1572
+ ```
1573
+
1574
+ ---
1575
+
1576
+ ## License
1577
+
1578
+ MIT License
1579
+
1580
+ Copyright (c) 2025 MaxxPainn Team
1581
+
1582
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1583
+
1584
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1585
+
1586
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1587
+
1588
+ ---
1589
+
1590
+ **Built with ❤️ by the [MaxxPainn Team](https://maxxpainn.com)**
1591
+ 📧 Support: [hello@maxxpainn.com](mailto:hello@maxxpainn.com)