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.
- package/.env.example +12 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +49 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/format.test.d.ts +2 -0
- package/dist/__tests__/format.test.d.ts.map +1 -0
- package/dist/__tests__/format.test.js +29 -0
- package/dist/__tests__/format.test.js.map +1 -0
- package/dist/__tests__/report.test.d.ts +2 -0
- package/dist/__tests__/report.test.d.ts.map +1 -0
- package/dist/__tests__/report.test.js +148 -0
- package/dist/__tests__/report.test.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/docker.d.ts +9 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +57 -0
- package/dist/docker.js.map +1 -0
- package/dist/format.d.ts +5 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +41 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/prometheus.d.ts +7 -0
- package/dist/prometheus.d.ts.map +1 -0
- package/dist/prometheus.js +47 -0
- package/dist/prometheus.js.map +1 -0
- package/dist/report.d.ts +3 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +134 -0
- package/dist/report.js.map +1 -0
- package/dist/telegram.d.ts +2 -0
- package/dist/telegram.d.ts.map +1 -0
- package/dist/telegram.js +17 -0
- package/dist/telegram.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/examples/minimal.health-report.config.json +17 -0
- package/examples/respira.health-report.config.json +68 -0
- 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
|
+
[](https://github.com/YOUR_USERNAME/server-health-telegram/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/server-health-telegram)
|
|
5
|
+
[](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 @@
|
|
|
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 @@
|
|
|
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":";;AAAA,4CAAiE;AAEjE,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACrB,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,eAAG,EAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAC1E,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,eAAG,EAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IACvE,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,eAAG,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IACxE,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,eAAG,EAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACjE,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChF,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChF,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAChE,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/E,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3E,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC"}
|
|
@@ -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 @@
|
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -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"}
|
package/dist/config.d.ts
ADDED
|
@@ -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":";;;;;AAsBA,gCA2DC;AAjFD,4CAAoB;AACpB,gDAAwB;AAGxB,SAAS,YAAY,CAAC,QAAgB;IACpC,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IACxC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACpB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,GAAG,CAAC,GAA2B,EAAE,GAAW,EAAE,QAAQ,GAAG,EAAE;IAClE,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;AAClD,CAAC;AAED,SAAgB,UAAU,CAAC,OAAgB,EAAE,UAAmB;IAC9D,6EAA6E;IAC7E,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAC9F,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAElC,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,OAAO,kCAAkC,CAAC,CAAC;IACvG,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAE5C,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IAED,6EAA6E;IAC7E,MAAM,OAAO,GAAG,UAAU;QACxB,CAAC,CAAC,cAAI,CAAC,OAAO,CAAC,UAAU,CAAC;QAC1B,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAElE,IAAI,MAAoB,CAAC;IAEzB,IAAI,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAiB,CAAC;QACxE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;SAAM,CAAC;QACN,yEAAyE;QACzE,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;QAEjG,MAAM,UAAU,GAA+B,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACpF,IAAI,SAAS,IAAI,SAAS;YAAE,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;QAEpF,MAAM,GAAG;YACP,UAAU;YACV,IAAI,EAAE,EAAE;YACR,QAAQ,EAAE,EAAE;YACZ,gBAAgB,EAAE,EAAE;YACpB,wBAAwB,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,0BAA0B,EAAE,KAAK,CAAC,CAAC;YAC7E,cAAc,EAAE,EAAE;SACnB,CAAC;QAEF,uCAAuC;QACvC,MAAM,cAAc,GAAG,GAAG,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;QACvD,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YACvD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2EAA2E,CAAC,CAAC;YACpG,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACxE,CAAC"}
|
package/dist/docker.d.ts
ADDED
|
@@ -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
|