server-health-telegram 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.
Files changed (54) hide show
  1. package/.env.example +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +181 -0
  4. package/dist/__tests__/config.test.d.ts +2 -0
  5. package/dist/__tests__/config.test.d.ts.map +1 -0
  6. package/dist/__tests__/config.test.js +49 -0
  7. package/dist/__tests__/config.test.js.map +1 -0
  8. package/dist/__tests__/format.test.d.ts +2 -0
  9. package/dist/__tests__/format.test.d.ts.map +1 -0
  10. package/dist/__tests__/format.test.js +29 -0
  11. package/dist/__tests__/format.test.js.map +1 -0
  12. package/dist/__tests__/report.test.d.ts +2 -0
  13. package/dist/__tests__/report.test.d.ts.map +1 -0
  14. package/dist/__tests__/report.test.js +148 -0
  15. package/dist/__tests__/report.test.js.map +1 -0
  16. package/dist/cli.d.ts +3 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +32 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/config.d.ts +3 -0
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +81 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/docker.d.ts +9 -0
  25. package/dist/docker.d.ts.map +1 -0
  26. package/dist/docker.js +57 -0
  27. package/dist/docker.js.map +1 -0
  28. package/dist/format.d.ts +5 -0
  29. package/dist/format.d.ts.map +1 -0
  30. package/dist/format.js +41 -0
  31. package/dist/format.js.map +1 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +19 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/prometheus.d.ts +7 -0
  37. package/dist/prometheus.d.ts.map +1 -0
  38. package/dist/prometheus.js +47 -0
  39. package/dist/prometheus.js.map +1 -0
  40. package/dist/report.d.ts +3 -0
  41. package/dist/report.d.ts.map +1 -0
  42. package/dist/report.js +134 -0
  43. package/dist/report.js.map +1 -0
  44. package/dist/telegram.d.ts +2 -0
  45. package/dist/telegram.d.ts.map +1 -0
  46. package/dist/telegram.js +17 -0
  47. package/dist/telegram.js.map +1 -0
  48. package/dist/types.d.ts +37 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +3 -0
  51. package/dist/types.js.map +1 -0
  52. package/examples/minimal.health-report.config.json +17 -0
  53. package/examples/respira.health-report.config.json +68 -0
  54. package/package.json +45 -0
package/.env.example ADDED
@@ -0,0 +1,12 @@
1
+ # ── Required ──────────────────────────────────────────────────────────────────
2
+ TELEGRAM_BOT_TOKEN=your_bot_token_here
3
+ TELEGRAM_CHAT_ID=your_chat_id_here
4
+
5
+ # ── Optional — defaults shown ─────────────────────────────────────────────────
6
+ PROMETHEUS_URL=http://localhost:9090
7
+ PROMETHEUS_BB_URL=http://localhost:9091 # set empty to disable BB section
8
+ MONITOR_BB=true # false to suppress Secondary section
9
+ DEFAULT_MEM_THRESHOLD_MB=150 # alert threshold for unlisted containers
10
+
11
+ # Override specific container thresholds (MB) as a JSON object
12
+ # MEM_THRESHOLDS_JSON={"my-container": 500, "another-one": 100}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emaad Nahed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # server-health-telegram
2
+
3
+ [![CI](https://github.com/YOUR_USERNAME/server-health-telegram/actions/workflows/ci.yml/badge.svg)](https://github.com/YOUR_USERNAME/server-health-telegram/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/server-health-telegram)](https://www.npmjs.com/package/server-health-telegram)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Sends a detailed VPS health report to a Telegram chat on a schedule. Reads Docker container stats and Prometheus metrics. **Zero runtime dependencies** — only Node.js built-ins.
8
+
9
+ ## Report includes
10
+
11
+ - 📊 **System** — RAM, CPU, Disk usage with bar charts and load average
12
+ - 🚨 **Memory alerts** — per-container threshold breach warnings (shown first)
13
+ - ✅ **Containers** — running count, stopped container names
14
+ - 🧠 **Top memory** — 12 highest-memory containers
15
+ - ⚡ **App metrics** — heap size and event-loop lag per Prometheus job (NestJS, Express, etc.)
16
+ - 🎯 **Prometheus targets** — which scrape targets are down
17
+ - 🐇 **RabbitMQ** — consumer count per queue (configurable, any number of clusters)
18
+ - ⚡ **Circuit breakers** — any open breakers (works with opossum or any Prometheus gauge)
19
+
20
+ ## Requirements
21
+
22
+ - Node.js ≥ 18
23
+ - Docker CLI accessible from the process user
24
+ - Prometheus scraping your server (node-exporter for system metrics)
25
+
26
+ ## Quick start
27
+
28
+ ### 1. Install
29
+
30
+ ```bash
31
+ npm install -g server-health-telegram
32
+ # or run without installing:
33
+ npx server-health-telegram --env .env.monitoring --config health-report.config.json
34
+ ```
35
+
36
+ ### 2. Create your secrets file
37
+
38
+ ```env
39
+ # .env.monitoring
40
+ TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather
41
+ TELEGRAM_CHAT_ID=your_chat_id
42
+ ```
43
+
44
+ ### 3. Create your config file
45
+
46
+ ```json
47
+ // health-report.config.json
48
+ {
49
+ "prometheus": [
50
+ { "label": "My Server", "url": "http://localhost:9090" }
51
+ ],
52
+ "apps": [
53
+ { "label": "API prod (3 svc)", "promJob": "api-prod" },
54
+ { "label": "API staging (3 svc)", "promJob": "api-staging" }
55
+ ],
56
+ "rabbitmq": [
57
+ {
58
+ "label": "Prod",
59
+ "promJob": "rabbitmq-prod-detailed",
60
+ "queues": ["orders_queue", "notifications_queue"]
61
+ }
62
+ ],
63
+ "circuitBreakers": {},
64
+ "defaultMemoryThresholdMb": 150
65
+ }
66
+ ```
67
+
68
+ ### 4. Test it
69
+
70
+ ```bash
71
+ server-health-telegram --env .env.monitoring --config health-report.config.json
72
+ ```
73
+
74
+ ### 5. Schedule with cron (hourly)
75
+
76
+ ```bash
77
+ crontab -e
78
+ ```
79
+
80
+ ```cron
81
+ 0 * * * * server-health-telegram --env /path/to/.env.monitoring --config /path/to/health-report.config.json >> /var/log/health-report.log 2>&1
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Config reference
87
+
88
+ ### `health-report.config.json`
89
+
90
+ | Field | Type | Required | Description |
91
+ |---|---|---|---|
92
+ | `prometheus` | `PrometheusSection[]` | ✅ | Prometheus instances to check targets on |
93
+ | `apps` | `AppSection[]` | | App heap + event-loop metrics sections |
94
+ | `rabbitmq` | `RabbitmqSection[]` | | RabbitMQ consumer health sections |
95
+ | `circuitBreakers` | `CircuitBreakerConfig` | | Circuit breaker state check. Omit to skip. |
96
+ | `memoryThresholds` | `Record<string, number>` | | Per-container alert thresholds in MB |
97
+ | `defaultMemoryThresholdMb` | `number` | | Default threshold for unlisted containers (default: 150) |
98
+ | `skipContainers` | `string[]` | | Container names to exclude from the memory table |
99
+
100
+ ### `PrometheusSection`
101
+
102
+ ```json
103
+ { "label": "My Cluster", "url": "http://localhost:9090" }
104
+ ```
105
+
106
+ ### `AppSection`
107
+
108
+ ```json
109
+ { "label": "My App prod (4 svc)", "promJob": "my-app-prod", "prometheusUrl": "http://localhost:9090" }
110
+ ```
111
+
112
+ `prometheusUrl` is optional — defaults to the first entry in `prometheus[]`.
113
+
114
+ ### `RabbitmqSection`
115
+
116
+ ```json
117
+ {
118
+ "label": "Production",
119
+ "promJob": "rabbitmq-prod-detailed",
120
+ "queues": ["orders_queue", "payments_queue"],
121
+ "prometheusUrl": "http://localhost:9090"
122
+ }
123
+ ```
124
+
125
+ ### `CircuitBreakerConfig`
126
+
127
+ ```json
128
+ {
129
+ "query": "rpc_circuit_breaker_open == 1",
130
+ "labelEnv": "env",
131
+ "labelService": "service",
132
+ "labelPattern": "pattern",
133
+ "prometheusUrl": "http://localhost:9090"
134
+ }
135
+ ```
136
+
137
+ All fields optional. Defaults shown above.
138
+
139
+ ---
140
+
141
+ ## Programmatic API
142
+
143
+ ```ts
144
+ import { run, loadConfig, buildReport, sendTelegram } from 'server-health-telegram';
145
+
146
+ // Simple
147
+ await run('path/to/.env.monitoring', 'path/to/health-report.config.json');
148
+
149
+ // With full control
150
+ const config = loadConfig('.env.monitoring', 'health-report.config.json');
151
+ const message = await buildReport(config);
152
+ await sendTelegram(config.telegramBotToken, config.telegramChatId, message);
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Examples
158
+
159
+ See the [`examples/`](./examples) directory for:
160
+
161
+ - [`minimal.health-report.config.json`](./examples/minimal.health-report.config.json) — bare minimum config
162
+ - [`respira.health-report.config.json`](./examples/respira.health-report.config.json) — full NestJS microservices + RabbitMQ + two Prometheus instances
163
+
164
+ ---
165
+
166
+ ## VPS migration
167
+
168
+ Because all configuration lives in two files (`.env.monitoring` and `health-report.config.json`), migrating to a new server is:
169
+
170
+ ```bash
171
+ git clone https://github.com/YOUR_USERNAME/server-health-telegram
172
+ cd server-health-telegram && npm ci && npm run build
173
+ # copy your .env.monitoring and health-report.config.json
174
+ crontab -e # add the cron entry
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Contributing
180
+
181
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const config_js_1 = require("../config.js");
10
+ function writeTmp(name, content) {
11
+ const p = path_1.default.join(os_1.default.tmpdir(), name);
12
+ fs_1.default.writeFileSync(p, content);
13
+ return p;
14
+ }
15
+ describe('loadConfig()', () => {
16
+ it('loads BOT_TOKEN and CHAT_ID from env file', () => {
17
+ const env = writeTmp('test.env', 'TELEGRAM_BOT_TOKEN=mytoken\nTELEGRAM_CHAT_ID=12345\n');
18
+ const cfg = (0, config_js_1.loadConfig)(env);
19
+ expect(cfg.telegramBotToken).toBe('mytoken');
20
+ expect(cfg.telegramChatId).toBe('12345');
21
+ });
22
+ it('throws when TELEGRAM_BOT_TOKEN is missing', () => {
23
+ const env = writeTmp('test-missing.env', 'TELEGRAM_CHAT_ID=12345\n');
24
+ expect(() => (0, config_js_1.loadConfig)(env)).toThrow('TELEGRAM_BOT_TOKEN');
25
+ });
26
+ it('loads JSON config file when present', () => {
27
+ const env = writeTmp('test2.env', 'TELEGRAM_BOT_TOKEN=tok\nTELEGRAM_CHAT_ID=99\n');
28
+ const cfg = writeTmp('test.config.json', JSON.stringify({
29
+ prometheus: [{ label: 'Primary', url: 'http://localhost:9090' }],
30
+ apps: [{ label: 'My App', promJob: 'my-app' }],
31
+ rabbitmq: [],
32
+ }));
33
+ const result = (0, config_js_1.loadConfig)(env, cfg);
34
+ expect(result.report.apps[0].label).toBe('My App');
35
+ expect(result.report.apps[0].promJob).toBe('my-app');
36
+ });
37
+ it('strips quotes from env values', () => {
38
+ const env = writeTmp('test-quotes.env', 'TELEGRAM_BOT_TOKEN="quoted"\nTELEGRAM_CHAT_ID=\'123\'\n');
39
+ const cfg = (0, config_js_1.loadConfig)(env);
40
+ expect(cfg.telegramBotToken).toBe('quoted');
41
+ expect(cfg.telegramChatId).toBe('123');
42
+ });
43
+ it('ignores comment lines in env file', () => {
44
+ const env = writeTmp('test-comments.env', '# comment\nTELEGRAM_BOT_TOKEN=tok\nTELEGRAM_CHAT_ID=1\n');
45
+ const cfg = (0, config_js_1.loadConfig)(env);
46
+ expect(cfg.telegramBotToken).toBe('tok');
47
+ });
48
+ });
49
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":";;;;;AAAA,4CAAoB;AACpB,gDAAwB;AACxB,4CAAoB;AACpB,4CAA0C;AAE1C,SAAS,QAAQ,CAAC,IAAY,EAAE,OAAe;IAC7C,MAAM,CAAC,GAAG,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;IACvC,YAAE,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,CAAC,CAAC;AACX,CAAC;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,EAAE,sDAAsD,CAAC,CAAC;QACzF,MAAM,GAAG,GAAG,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,kBAAkB,EAAE,0BAA0B,CAAC,CAAC;QACrE,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,+CAA+C,CAAC,CAAC;QACnF,MAAM,GAAG,GAAG,QAAQ,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC;YACtD,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,uBAAuB,EAAE,CAAC;YAChE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;YAC9C,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC,CAAC;QACJ,MAAM,MAAM,GAAG,IAAA,sBAAU,EAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,iBAAiB,EAAE,yDAAyD,CAAC,CAAC;QACnG,MAAM,GAAG,GAAG,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,mBAAmB,EAAE,yDAAyD,CAAC,CAAC;QACrG,MAAM,GAAG,GAAG,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=format.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const format_js_1 = require("../format.js");
4
+ describe('bar()', () => {
5
+ it('returns full bar at 100%', () => expect((0, format_js_1.bar)(100)).toBe('██████████'));
6
+ it('returns empty bar at 0%', () => expect((0, format_js_1.bar)(0)).toBe('░░░░░░░░░░'));
7
+ it('returns half bar at 50%', () => expect((0, format_js_1.bar)(50)).toBe('█████░░░░░'));
8
+ it('respects custom width', () => expect((0, format_js_1.bar)(50, 4)).toBe('██░░'));
9
+ });
10
+ describe('fmtBytes()', () => {
11
+ it('formats bytes', () => expect((0, format_js_1.fmtBytes)(512)).toBe('512.0 B'));
12
+ it('formats kilobytes', () => expect((0, format_js_1.fmtBytes)(2048)).toBe('2.0 KB'));
13
+ it('formats megabytes', () => expect((0, format_js_1.fmtBytes)(5 * 1024 * 1024)).toBe('5.0 MB'));
14
+ it('formats gigabytes', () => expect((0, format_js_1.fmtBytes)(2.5 * 1024 ** 3)).toBe('2.5 GB'));
15
+ it('returns ? for null', () => expect((0, format_js_1.fmtBytes)(null)).toBe('?'));
16
+ });
17
+ describe('pctEmoji()', () => {
18
+ it('returns green below 70', () => expect((0, format_js_1.pctEmoji)(50)).toBe('🟢'));
19
+ it('returns yellow 70-84', () => expect((0, format_js_1.pctEmoji)(75)).toBe('🟡'));
20
+ it('returns red at 85+', () => expect((0, format_js_1.pctEmoji)(90)).toBe('🔴'));
21
+ it('returns question mark for null', () => expect((0, format_js_1.pctEmoji)(null)).toBe('❓'));
22
+ });
23
+ describe('lagEmoji()', () => {
24
+ it('returns green for low lag', () => expect((0, format_js_1.lagEmoji)(50)).toBe('🟢'));
25
+ it('returns yellow for 101-200ms', () => expect((0, format_js_1.lagEmoji)(150)).toBe('🟡'));
26
+ it('returns red above 200ms', () => expect((0, format_js_1.lagEmoji)(250)).toBe('🔴'));
27
+ it('returns green for null', () => expect((0, format_js_1.lagEmoji)(null)).toBe('🟢'));
28
+ });
29
+ //# sourceMappingURL=format.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=report.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/report.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const report_js_1 = require("../report.js");
37
+ const prometheus = __importStar(require("../prometheus.js"));
38
+ const docker = __importStar(require("../docker.js"));
39
+ jest.mock('../prometheus.js');
40
+ jest.mock('../docker.js');
41
+ const mockProm = prometheus;
42
+ const mockDocker = docker;
43
+ const baseConfig = {
44
+ telegramBotToken: 'tok',
45
+ telegramChatId: '1',
46
+ report: {
47
+ prometheus: [{ label: 'Test', url: 'http://localhost:9090' }],
48
+ apps: [{ label: 'My App prod', promJob: 'my-app-prod' }],
49
+ rabbitmq: [{
50
+ label: 'Prod',
51
+ promJob: 'rabbitmq-prod',
52
+ queues: ['my_queue'],
53
+ }],
54
+ circuitBreakers: {},
55
+ defaultMemoryThresholdMb: 150,
56
+ skipContainers: [],
57
+ },
58
+ };
59
+ beforeEach(() => {
60
+ jest.clearAllMocks();
61
+ mockProm.promQuery.mockResolvedValue(null);
62
+ mockProm.promTargets.mockResolvedValue({ total: 5, downJobs: [] });
63
+ mockProm.promRawResults.mockResolvedValue([]);
64
+ mockDocker.getContainers.mockReturnValue([
65
+ { name: 'my-app', status: 'Up 2 hours', running: true },
66
+ ]);
67
+ mockDocker.getMemStats.mockReturnValue({ 'my-app': 50 * 1024 * 1024 });
68
+ });
69
+ describe('buildReport()', () => {
70
+ it('includes timestamp in the header', async () => {
71
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
72
+ expect(msg).toMatch(/VPS Health Report.*UTC/);
73
+ });
74
+ it('shows all containers running when all are up', async () => {
75
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
76
+ expect(msg).toMatch(/✅ Containers 1\/1 running/);
77
+ });
78
+ it('shows warning when containers are stopped', async () => {
79
+ mockDocker.getContainers.mockReturnValue([
80
+ { name: 'app', status: 'Up 1h', running: true },
81
+ { name: 'worker', status: 'Exited (1) 5 min ago', running: false },
82
+ ]);
83
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
84
+ expect(msg).toMatch(/⚠️ Containers 1\/2 running/);
85
+ expect(msg).toContain('worker');
86
+ });
87
+ it('shows memory alert when container exceeds threshold', async () => {
88
+ mockDocker.getMemStats.mockReturnValue({ 'my-app': 200 * 1024 * 1024 });
89
+ const cfg = {
90
+ ...baseConfig,
91
+ report: { ...baseConfig.report, memoryThresholds: { 'my-app': 100 } },
92
+ };
93
+ const msg = await (0, report_js_1.buildReport)(cfg);
94
+ expect(msg).toMatch(/🚨 Memory Alerts/);
95
+ expect(msg).toContain('my-app');
96
+ });
97
+ it('shows green targets when all Prometheus targets are up', async () => {
98
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
99
+ expect(msg).toMatch(/✅ Test: all 5 targets up/);
100
+ });
101
+ it('shows down targets when some are unhealthy', async () => {
102
+ mockProm.promTargets.mockResolvedValue({ total: 3, downJobs: ['broken-job'] });
103
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
104
+ expect(msg).toMatch(/⚠️ Test: 1 target\(s\) down: broken-job/);
105
+ });
106
+ it('shows all queues healthy when consumers are present', async () => {
107
+ mockProm.promRawResults.mockResolvedValueOnce([
108
+ { metric: { queue: 'my_queue' }, value: ['', '1'] },
109
+ ]);
110
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
111
+ expect(msg).toMatch(/✅ Prod: all 1 queues have consumers/);
112
+ });
113
+ it('shows dead queue when consumer count is 0', async () => {
114
+ mockProm.promRawResults.mockResolvedValueOnce([
115
+ { metric: { queue: 'my_queue' }, value: ['', '0'] },
116
+ ]);
117
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
118
+ expect(msg).toMatch(/🔴 Prod: 0 consumers on: my/);
119
+ });
120
+ it('shows all circuit breakers closed when none are open', async () => {
121
+ mockProm.promRawResults.mockResolvedValue([]);
122
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
123
+ expect(msg).toMatch(/✅ All circuit breakers closed/);
124
+ });
125
+ it('shows open circuit breakers', async () => {
126
+ mockProm.promRawResults
127
+ .mockResolvedValueOnce([]) // rabbitmq call
128
+ .mockResolvedValueOnce([
129
+ { metric: { env: 'prod', service: 'my-service', pattern: 'some.pattern' }, value: ['', '1'] },
130
+ ]);
131
+ const msg = await (0, report_js_1.buildReport)(baseConfig);
132
+ expect(msg).toMatch(/🔴 \[prod\] my-service → some\.pattern/);
133
+ });
134
+ it('omits app section when no apps configured', async () => {
135
+ const cfg = { ...baseConfig, report: { ...baseConfig.report, apps: [] } };
136
+ const msg = await (0, report_js_1.buildReport)(cfg);
137
+ expect(msg).not.toMatch(/App metrics/);
138
+ });
139
+ it('omits circuit breaker section when not configured', async () => {
140
+ const cfg = {
141
+ ...baseConfig,
142
+ report: { ...baseConfig.report, circuitBreakers: undefined },
143
+ };
144
+ const msg = await (0, report_js_1.buildReport)(cfg);
145
+ expect(msg).not.toMatch(/Circuit breakers/);
146
+ });
147
+ });
148
+ //# sourceMappingURL=report.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.test.js","sourceRoot":"","sources":["../../src/__tests__/report.test.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("./index.js");
5
+ // Usage: server-health-telegram [--env <file>] [--config <file>]
6
+ const args = process.argv.slice(2);
7
+ let envFile;
8
+ let configFile;
9
+ for (let i = 0; i < args.length; i++) {
10
+ if (args[i] === '--env' && args[i + 1]) {
11
+ envFile = args[++i];
12
+ continue;
13
+ }
14
+ if (args[i] === '--config' && args[i + 1]) {
15
+ configFile = args[++i];
16
+ continue;
17
+ }
18
+ // Legacy: first positional arg = env file
19
+ if (!args[i].startsWith('--') && !envFile) {
20
+ envFile = args[i];
21
+ }
22
+ }
23
+ (0, index_js_1.run)(envFile, configFile)
24
+ .then(() => {
25
+ console.log('[server-health-telegram] Report sent ✓');
26
+ process.exit(0);
27
+ })
28
+ .catch((err) => {
29
+ console.error('[server-health-telegram] Failed:', err.message);
30
+ process.exit(1);
31
+ });
32
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AACA,yCAAiC;AAEjC,iEAAiE;AACjE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,OAA2B,CAAC;AAChC,IAAI,UAA8B,CAAC;AAEnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;IACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAK,CAAC;QAAC,OAAO,GAAM,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAAC,SAAS;IAAC,CAAC;IAChF,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAAC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAAC,SAAS;IAAC,CAAC;IAChF,0CAA0C;IAC1C,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;AACnE,CAAC;AAED,IAAA,cAAG,EAAC,OAAO,EAAE,UAAU,CAAC;KACrB,IAAI,CAAC,GAAG,EAAE;IACT,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACpB,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Config } from './types.js';
2
+ export declare function loadConfig(envFile?: string, configFile?: string): Config;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAgB,MAAM,YAAY,CAAC;AAoBlD,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CA2DxE"}
package/dist/config.js ADDED
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = loadConfig;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function parseEnvFile(filePath) {
10
+ if (!fs_1.default.existsSync(filePath))
11
+ return {};
12
+ const result = {};
13
+ for (const line of fs_1.default.readFileSync(filePath, 'utf8').split('\n')) {
14
+ const trimmed = line.trim();
15
+ if (!trimmed || trimmed.startsWith('#') || !trimmed.includes('='))
16
+ continue;
17
+ const eqIdx = trimmed.indexOf('=');
18
+ const key = trimmed.slice(0, eqIdx).trim();
19
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
20
+ result[key] = val;
21
+ }
22
+ return result;
23
+ }
24
+ function get(env, key, fallback = '') {
25
+ return env[key] ?? process.env[key] ?? fallback;
26
+ }
27
+ function loadConfig(envFile, configFile) {
28
+ // ── secrets from .env file ────────────────────────────────────────────────
29
+ const envPath = envFile ? path_1.default.resolve(envFile) : path_1.default.join(process.cwd(), '.env.monitoring');
30
+ const env = parseEnvFile(envPath);
31
+ if (!fs_1.default.existsSync(envPath)) {
32
+ process.stderr.write(`[server-health-telegram] WARNING: ${envPath} not found — using process.env\n`);
33
+ }
34
+ const botToken = get(env, 'TELEGRAM_BOT_TOKEN');
35
+ const chatId = get(env, 'TELEGRAM_CHAT_ID');
36
+ if (!botToken || !chatId) {
37
+ throw new Error('TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are required.');
38
+ }
39
+ // ── report structure from JSON config file ────────────────────────────────
40
+ const cfgPath = configFile
41
+ ? path_1.default.resolve(configFile)
42
+ : path_1.default.join(path_1.default.dirname(envPath), 'health-report.config.json');
43
+ let report;
44
+ if (fs_1.default.existsSync(cfgPath)) {
45
+ try {
46
+ report = JSON.parse(fs_1.default.readFileSync(cfgPath, 'utf8'));
47
+ }
48
+ catch (e) {
49
+ throw new Error(`Failed to parse ${cfgPath}: ${e.message}`);
50
+ }
51
+ }
52
+ else {
53
+ // ── fallback: build config from legacy .env keys ──────────────────────
54
+ const promUrl = get(env, 'PROMETHEUS_URL', 'http://localhost:9090');
55
+ const promBbUrl = get(env, 'PROMETHEUS_BB_URL', '');
56
+ const monitorBb = get(env, 'MONITOR_BB', 'true').toLowerCase() !== 'false' && Boolean(promBbUrl);
57
+ const prometheus = [{ label: 'Primary', url: promUrl }];
58
+ if (monitorBb && promBbUrl)
59
+ prometheus.push({ label: 'Secondary', url: promBbUrl });
60
+ report = {
61
+ prometheus,
62
+ apps: [],
63
+ rabbitmq: [],
64
+ memoryThresholds: {},
65
+ defaultMemoryThresholdMb: Number(get(env, 'DEFAULT_MEM_THRESHOLD_MB', '150')),
66
+ skipContainers: [],
67
+ };
68
+ // Parse MEM_THRESHOLDS_JSON if present
69
+ const thresholdsJson = get(env, 'MEM_THRESHOLDS_JSON');
70
+ if (thresholdsJson) {
71
+ try {
72
+ report.memoryThresholds = JSON.parse(thresholdsJson);
73
+ }
74
+ catch {
75
+ process.stderr.write('[server-health-telegram] WARNING: MEM_THRESHOLDS_JSON invalid — ignored\n');
76
+ }
77
+ }
78
+ }
79
+ return { telegramBotToken: botToken, telegramChatId: chatId, report };
80
+ }
81
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface ContainerInfo {
2
+ name: string;
3
+ status: string;
4
+ running: boolean;
5
+ }
6
+ export type MemStats = Record<string, number>;
7
+ export declare function getContainers(): ContainerInfo[];
8
+ export declare function getMemStats(): MemStats;
9
+ //# sourceMappingURL=docker.d.ts.map