@irsyadulibad/servermon 1.1.1 → 1.2.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 +134 -34
- package/cli.ts +3 -214
- package/index.ts +5 -118
- package/package.json +8 -1
- package/src/cli/banner.ts +65 -0
- package/src/cli/index.ts +284 -0
- package/src/cli/service.ts +306 -0
- package/src/config/index.ts +84 -0
- package/src/daemon/index.ts +198 -0
- package/src/{monitor.ts → monitor/index.ts} +32 -80
- package/src/{reporter.ts → reporter/index.ts} +25 -20
- package/src/types/index.ts +71 -0
- package/src/config.ts +0 -46
package/README.md
CHANGED
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
|
|
15
15
|
## ✨ Features
|
|
16
16
|
|
|
17
|
-
| Category
|
|
18
|
-
|
|
|
19
|
-
| 💻 **CPU**
|
|
20
|
-
| 🧠 **RAM**
|
|
21
|
-
| 💾 **Disk**
|
|
22
|
-
| 🌐 **Network**
|
|
23
|
-
| 🌡 **Temperature**
|
|
24
|
-
| 📊 **Top Processes**
|
|
25
|
-
| 🚨 **Alerts**
|
|
17
|
+
| Category | What it tracks |
|
|
18
|
+
| --------------------- | -------------------------------------------------------------- |
|
|
19
|
+
| 💻 **CPU** | Model, core count, usage %, load average (1/5/15 min) |
|
|
20
|
+
| 🧠 **RAM** | Used / total, usage %, swap usage |
|
|
21
|
+
| 💾 **Disk** | Per-mount used / total, usage % — deduplicated |
|
|
22
|
+
| 🌐 **Network** | RX / TX rate (bytes/sec, sampled over 1s) |
|
|
23
|
+
| 🌡 **Temperature** | CPU package temp via thermal zones (`/sys/class/thermal`) |
|
|
24
|
+
| 📊 **Top Processes** | Top 5 by CPU % — PID, name, CPU %, MEM % |
|
|
25
|
+
| 🚨 **Alerts** | Auto-flag when CPU > 85%, RAM > 90%, disk > 90%, or swap > 50% |
|
|
26
|
+
| 🌍 **Multi-server** | Monitor multiple servers with one bot via `--name` flag |
|
|
27
|
+
| ⏱ **One-shot report** | Send a report on demand without starting the daemon |
|
|
26
28
|
|
|
27
29
|
Each report is color-coded with 🟢🟡🔴 health indicators and visual bar charts.
|
|
28
30
|
|
|
@@ -39,7 +41,7 @@ bun i -g @irsyadulibad/servermon
|
|
|
39
41
|
### First run — interactive setup
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
|
-
servermon
|
|
44
|
+
servermon setup
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
You'll be prompted for:
|
|
@@ -56,31 +58,80 @@ DM your bot **once** (any message). The daemon auto-detects your chat ID.
|
|
|
56
58
|
### Start monitoring
|
|
57
59
|
|
|
58
60
|
```bash
|
|
59
|
-
servermon
|
|
61
|
+
servermon start
|
|
60
62
|
```
|
|
61
63
|
|
|
62
64
|
That's it. The daemon auto-detects your chat ID, sends an initial report, then loops.
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
66
|
-
##
|
|
68
|
+
## 📖 Usage
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Usage: servermon <command> [options]
|
|
72
|
+
|
|
73
|
+
Commands:
|
|
74
|
+
setup [--name <srv>] First-time setup (bot token + interval) for a server
|
|
75
|
+
start [--name <srv>] Start the monitoring daemon for a server
|
|
76
|
+
report [--name <srv>] Send a one-time report without starting the daemon
|
|
77
|
+
list List all configured servers
|
|
78
|
+
delete <name> Delete a configured server
|
|
79
|
+
service <sub> Manage systemd service
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
-n, --name Server name
|
|
83
|
+
-h, --help Show help
|
|
84
|
+
-v, --version Show version number
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Examples
|
|
67
88
|
|
|
68
89
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
# Basic setup
|
|
91
|
+
servermon setup
|
|
92
|
+
|
|
93
|
+
# Setup with a server name (for multi-server)
|
|
94
|
+
servermon setup --name prod
|
|
95
|
+
|
|
96
|
+
# Start monitoring a specific server
|
|
97
|
+
servermon start --name staging
|
|
98
|
+
|
|
99
|
+
# Start monitoring ALL configured servers at once
|
|
100
|
+
servermon start
|
|
101
|
+
|
|
102
|
+
# Send a one-time report (daemon not required)
|
|
103
|
+
servermon report --name prod
|
|
72
104
|
|
|
73
|
-
#
|
|
74
|
-
|
|
105
|
+
# List all configured servers
|
|
106
|
+
servermon list
|
|
75
107
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
servermon
|
|
108
|
+
# Delete a server config (interactive confirm)
|
|
109
|
+
servermon delete prod
|
|
79
110
|
|
|
80
|
-
#
|
|
81
|
-
|
|
111
|
+
# Force delete without confirmation
|
|
112
|
+
servermon delete prod --yes
|
|
82
113
|
```
|
|
83
114
|
|
|
115
|
+
### 🌍 Multi-server mode
|
|
116
|
+
|
|
117
|
+
`servermon start` (without `--name`) automatically runs **all configured servers** in a single daemon process — each server gets its own config, but they share one process and interval.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
┌─────────────────────────────┐
|
|
121
|
+
│ servermon start │
|
|
122
|
+
│ (multi-server daemon) │
|
|
123
|
+
│ │
|
|
124
|
+
│ ├─ default → Telegram │
|
|
125
|
+
│ ├─ prod → Telegram │
|
|
126
|
+
│ └─ staging → Telegram │
|
|
127
|
+
└─────────────────────────────┘
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Reports appear with the server name in the header:
|
|
131
|
+
|
|
132
|
+
> 🖥 **server01** [**prod**] — ✅ HEALTHY
|
|
133
|
+
> 🖥 **server02** [**staging**] — ⚠️ WARNING
|
|
134
|
+
|
|
84
135
|
---
|
|
85
136
|
|
|
86
137
|
## 📷 Example Report
|
|
@@ -111,7 +162,13 @@ bun unlink
|
|
|
111
162
|
✨ All systems normal
|
|
112
163
|
```
|
|
113
164
|
|
|
114
|
-
When thresholds are crossed, alerts appear inline
|
|
165
|
+
When thresholds are crossed, alerts appear inline:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
🚨 CRITICAL
|
|
169
|
+
🔴 RAM hampir penuh: 91.2%
|
|
170
|
+
🔴 Disk /: 91%
|
|
171
|
+
```
|
|
115
172
|
|
|
116
173
|
---
|
|
117
174
|
|
|
@@ -131,12 +188,23 @@ When thresholds are crossed, alerts appear inline.
|
|
|
131
188
|
|
|
132
189
|
```
|
|
133
190
|
server-monitoring/
|
|
134
|
-
├── cli.ts
|
|
135
|
-
├── index.ts
|
|
191
|
+
├── cli.ts # CLI wrapper (delegates to src/cli)
|
|
192
|
+
├── index.ts # Module entry (re-exports daemon functions)
|
|
136
193
|
├── src/
|
|
137
|
-
│ ├──
|
|
138
|
-
│
|
|
139
|
-
│
|
|
194
|
+
│ ├── types/ # Shared type definitions
|
|
195
|
+
│ │ └── index.ts
|
|
196
|
+
│ ├── config/ # Config CRUD (load, save, delete, listServers)
|
|
197
|
+
│ │ └── index.ts
|
|
198
|
+
│ ├── monitor/ # Metrics collector + formatting utilities
|
|
199
|
+
│ │ └── index.ts
|
|
200
|
+
│ ├── reporter/ # HTML formatter & Telegram sender
|
|
201
|
+
│ │ └── index.ts
|
|
202
|
+
│ ├── daemon/ # start(), startAll(), autoDetectChatId()
|
|
203
|
+
│ │ └── index.ts
|
|
204
|
+
│ └── cli/ # CLI command routing (CrustJS) + interactive setup + banner
|
|
205
|
+
│ ├── banner.ts
|
|
206
|
+
│ ├── index.ts
|
|
207
|
+
│ └── service.ts # systemd service subcommands
|
|
140
208
|
├── eslint.config.js
|
|
141
209
|
├── .prettierrc
|
|
142
210
|
├── package.json
|
|
@@ -157,16 +225,47 @@ Stored at `~/.irsyadulibad/servermon/config.json`:
|
|
|
157
225
|
}
|
|
158
226
|
```
|
|
159
227
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
228
|
+
For named servers, configs are stored separately:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
~/.irsyadulibad/servermon/
|
|
232
|
+
├── config.json # default server
|
|
233
|
+
├── config-prod.json # servermon setup --name prod
|
|
234
|
+
└── config-staging.json # servermon setup --name staging
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Field | Description |
|
|
238
|
+
| ---------- | --------------------------------------------------------- |
|
|
239
|
+
| `token` | Telegram bot token (required) |
|
|
240
|
+
| `interval` | Seconds between reports (min: 30, default: 300) |
|
|
241
|
+
| `chatId` | Auto-detected on first run, persisted for subsequent runs |
|
|
242
|
+
| `name` | Server name label (set via `--name`, shown in reports) |
|
|
163
243
|
|
|
164
244
|
---
|
|
165
245
|
|
|
166
246
|
## 📦 Deploy as systemd service
|
|
167
247
|
|
|
168
248
|
```bash
|
|
169
|
-
#
|
|
249
|
+
# Install & start
|
|
250
|
+
servermon service install
|
|
251
|
+
|
|
252
|
+
# Check health
|
|
253
|
+
servermon service status
|
|
254
|
+
|
|
255
|
+
# View real-time logs
|
|
256
|
+
servermon service logs
|
|
257
|
+
|
|
258
|
+
# Stop / restart / uninstall
|
|
259
|
+
servermon service stop
|
|
260
|
+
servermon service restart
|
|
261
|
+
servermon service uninstall --yes
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
This creates a user-level systemd service at `~/.config/systemd/user/servermon.service` that runs `servermon start` (multi-server mode) and automatically restarts on failure.
|
|
265
|
+
|
|
266
|
+
### Manual (if you prefer):
|
|
267
|
+
|
|
268
|
+
```bash
|
|
170
269
|
sudo tee /etc/systemd/system/servermon.service << 'EOF'
|
|
171
270
|
[Unit]
|
|
172
271
|
Description=Server Monitor Daemon
|
|
@@ -174,7 +273,7 @@ After=network.target
|
|
|
174
273
|
|
|
175
274
|
[Service]
|
|
176
275
|
Type=simple
|
|
177
|
-
ExecStart=/
|
|
276
|
+
ExecStart=/home/irsyad/.bun/bin/servermon start
|
|
178
277
|
Restart=always
|
|
179
278
|
RestartSec=10
|
|
180
279
|
|
|
@@ -190,9 +289,10 @@ sudo systemctl enable --now servermon
|
|
|
190
289
|
|
|
191
290
|
## 🛠 Built With
|
|
192
291
|
|
|
193
|
-
- [Bun](https://bun.
|
|
292
|
+
- [Bun](https://bun.sh) — fast all-in-one JavaScript runtime
|
|
194
293
|
- [TypeScript](https://www.typescriptlang.org/) — type safety
|
|
195
294
|
- [Telegram Bot API](https://core.telegram.org/bots/api) — message delivery
|
|
295
|
+
- [CrustJS](https://crustjs.com) — CLI framework (command routing, plugins, type-safe parsing)
|
|
196
296
|
- [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) — code quality
|
|
197
297
|
|
|
198
298
|
---
|
package/cli.ts
CHANGED
|
@@ -1,220 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Server Monitor —
|
|
4
|
-
*
|
|
3
|
+
* Server Monitor — CLI entry point.
|
|
4
|
+
* Uses @crustjs/core for command parsing and routing.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import { loadConfig, saveConfig, configPath, configDir } from "./src/config";
|
|
8
|
-
|
|
9
|
-
function banner() {
|
|
10
|
-
console.log("╔══════════════════════════════════════╗");
|
|
11
|
-
console.log("║ 🖥 SERVER MONITOR DAEMON 🖥 ║");
|
|
12
|
-
console.log("║ Telegram • Bun • TypeScript ║");
|
|
13
|
-
console.log("╚══════════════════════════════════════╝");
|
|
14
|
-
console.log();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ─────────────────────────────────────
|
|
18
|
-
// systemd auto‑setup
|
|
19
|
-
// ─────────────────────────────────────
|
|
20
|
-
async function setupSystemd(): Promise<void> {
|
|
21
|
-
banner();
|
|
22
|
-
console.log("⚙️ Systemd Service Setup\n");
|
|
23
|
-
|
|
24
|
-
const config = await loadConfig();
|
|
25
|
-
if (!config) {
|
|
26
|
-
console.error("❌ No config found. Run `servermon` first to set up.");
|
|
27
|
-
console.error(" Then try: servermon --install-service");
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Resolve paths
|
|
32
|
-
const bunPath = Bun.which("bun");
|
|
33
|
-
if (!bunPath) {
|
|
34
|
-
console.error("❌ bun not found in PATH");
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const servermonPath = Bun.which("servermon");
|
|
39
|
-
if (!servermonPath) {
|
|
40
|
-
console.error("❌ servermon binary not found.");
|
|
41
|
-
console.error(" Install with: bun i -g @irsyadulibad/servermon");
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log(`🔍 bun: ${bunPath}`);
|
|
46
|
-
console.log(`🔍 servermon: ${servermonPath}`);
|
|
47
|
-
console.log(`📁 Config: ${configPath()}`);
|
|
48
|
-
console.log();
|
|
49
|
-
|
|
50
|
-
// Create systemd user directory
|
|
51
|
-
const home = process.env.HOME ?? "~";
|
|
52
|
-
const systemdDir = `${home}/.config/systemd/user`;
|
|
53
|
-
await Bun.write(`${systemdDir}/.gitkeep`, ""); // ensure dir exists
|
|
54
|
-
try { await (Bun as any).mkdir?.(systemdDir, { recursive: true }); } catch { /* Bun.mkdir may not exist; fs fallback */ }
|
|
55
|
-
try { await import("node:fs").then(m => m.mkdirSync(systemdDir, { recursive: true })); } catch {}
|
|
56
|
-
|
|
57
|
-
const serviceFile = `${systemdDir}/servermon.service`;
|
|
58
|
-
const bunDir = bunPath.replace(/\/bun$/, ""); // strip binary name, keep directory
|
|
59
|
-
const serviceContent = `[Unit]
|
|
60
|
-
Description=Server Monitor — Telegram system health reports
|
|
61
|
-
After=network-online.target
|
|
62
|
-
Wants=network-online.target
|
|
63
|
-
|
|
64
|
-
[Service]
|
|
65
|
-
Type=simple
|
|
66
|
-
ExecStart=${servermonPath} start
|
|
67
|
-
Restart=always
|
|
68
|
-
RestartSec=30
|
|
69
|
-
Environment=NODE_ENV=production
|
|
70
|
-
Environment=PATH=${bunDir}:/usr/local/bin:/usr/bin:/bin
|
|
71
|
-
|
|
72
|
-
[Install]
|
|
73
|
-
WantedBy=default.target
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
await Bun.write(serviceFile, serviceContent);
|
|
77
|
-
console.log(`📄 Written: ${serviceFile}`);
|
|
78
|
-
console.log();
|
|
79
|
-
|
|
80
|
-
// systemctl commands
|
|
81
|
-
const cmds = [
|
|
82
|
-
["systemctl", "--user", "daemon-reload"],
|
|
83
|
-
["systemctl", "--user", "enable", "servermon.service"],
|
|
84
|
-
["systemctl", "--user", "start", "servermon.service"],
|
|
85
|
-
["systemctl", "--user", "status", "servermon.service", "--no-pager", "-l"],
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
for (const cmd of cmds) {
|
|
89
|
-
console.log(`🏃 ${cmd.join(" ")}`);
|
|
90
|
-
const proc = Bun.spawnSync({ cmd, stdout: "pipe", stderr: "pipe" });
|
|
91
|
-
const out = new TextDecoder().decode(proc.stdout);
|
|
92
|
-
const err = new TextDecoder().decode(proc.stderr);
|
|
93
|
-
if (out) console.log(out);
|
|
94
|
-
if (err && proc.exitCode !== 0) console.error(err);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log();
|
|
98
|
-
console.log("✅ Systemd service installed!");
|
|
99
|
-
console.log();
|
|
100
|
-
console.log("📌 For auto-start at boot, enable lingering:");
|
|
101
|
-
console.log(` loginctl enable-linger`);
|
|
102
|
-
console.log();
|
|
103
|
-
console.log("📋 Useful commands:");
|
|
104
|
-
console.log(" systemctl --user status servermon # check status");
|
|
105
|
-
console.log(" systemctl --user stop servermon # stop daemon");
|
|
106
|
-
console.log(" systemctl --user restart servermon # restart");
|
|
107
|
-
console.log(" journalctl --user -u servermon -f # watch logs");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function interactiveSetup(): Promise<void> {
|
|
111
|
-
console.log("🖥 Server Monitor — First Time Setup");
|
|
112
|
-
console.log(` Config will be saved to: ${configDir()}\n`);
|
|
113
|
-
|
|
114
|
-
const token = prompt("🔑 Telegram Bot Token: ")?.trim();
|
|
115
|
-
if (!token) {
|
|
116
|
-
console.error("❌ Bot token is required. Get one from @BotFather on Telegram.");
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!token.includes(":")) {
|
|
121
|
-
console.error("❌ Invalid bot token format. Should look like: 123456:ABC-DEF1234gh...");
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log();
|
|
126
|
-
console.log("⏱ Choose report interval:");
|
|
127
|
-
console.log(" 1. Every 5 minutes");
|
|
128
|
-
console.log(" 2. Every 1 hour");
|
|
129
|
-
console.log(" 3. Every 3 hours");
|
|
130
|
-
console.log(" 4. Every 6 hours");
|
|
131
|
-
console.log(" 5. Every 12 hours");
|
|
132
|
-
console.log(" 6. Custom (in seconds)");
|
|
133
|
-
|
|
134
|
-
const choice = prompt(" Pick [1-6] (default: 1): ")?.trim() || "1";
|
|
135
|
-
|
|
136
|
-
const intervals: Record<string, number> = {
|
|
137
|
-
"1": 300,
|
|
138
|
-
"2": 3600,
|
|
139
|
-
"3": 10800,
|
|
140
|
-
"4": 21600,
|
|
141
|
-
"5": 43200,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
let interval: number;
|
|
145
|
-
if (choice === "6") {
|
|
146
|
-
const custom = prompt(" Enter interval in seconds: ")?.trim();
|
|
147
|
-
interval = Math.max(30, parseInt(custom || "300") || 300);
|
|
148
|
-
} else {
|
|
149
|
-
interval = intervals[choice] ?? 300;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const label =
|
|
153
|
-
interval >= 3600
|
|
154
|
-
? `${(interval / 3600).toFixed(0)} hour(s)`
|
|
155
|
-
: `${(interval / 60).toFixed(0)} min`;
|
|
156
|
-
|
|
157
|
-
await saveConfig({ token, interval });
|
|
158
|
-
console.log(`\n✅ Config saved!`);
|
|
159
|
-
console.log(` 📁 ${configPath()}`);
|
|
160
|
-
console.log(` ⏱ Interval: ${interval}s (${label})`);
|
|
161
|
-
console.log(`\n📡 Next step: DM your bot once on Telegram, then run \`servermon\`.`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function main() {
|
|
165
|
-
const cmd = process.argv[2];
|
|
166
|
-
|
|
167
|
-
// --- subcommand routing ---
|
|
168
|
-
if (cmd === "setup") {
|
|
169
|
-
banner();
|
|
170
|
-
await interactiveSetup();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (cmd === "start") {
|
|
175
|
-
banner();
|
|
176
|
-
const config = await loadConfig();
|
|
177
|
-
if (!config) {
|
|
178
|
-
console.error("❌ No config found. Run `servermon setup` first.");
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
process.env["TELEGRAM_BOT_TOKEN"] = config.token;
|
|
182
|
-
process.env["MONITOR_INTERVAL"] = String(config.interval);
|
|
183
|
-
if (config.chatId) process.env["TELEGRAM_CHAT_ID"] = config.chatId;
|
|
184
|
-
|
|
185
|
-
console.log(`📁 Config: ${configPath()}`);
|
|
186
|
-
console.log(`📡 Bot: ...${config.token.slice(-8)}`);
|
|
187
|
-
if (config.chatId) console.log(`💬 Chat: ${config.chatId}`);
|
|
188
|
-
console.log();
|
|
189
|
-
|
|
190
|
-
const { start } = await import("./index.ts");
|
|
191
|
-
await start();
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (cmd === "install-service" || cmd === "--install-service" || cmd === "--setup-systemd") {
|
|
196
|
-
await setupSystemd();
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// --- no/invalid subcommand → help ---
|
|
201
|
-
console.log("╔══════════════════════════════════════╗");
|
|
202
|
-
console.log("║ 🖥 SERVER MONITOR DAEMON 🖥 ║");
|
|
203
|
-
console.log("║ Telegram • Bun • TypeScript ║");
|
|
204
|
-
console.log("╚══════════════════════════════════════╝");
|
|
205
|
-
console.log();
|
|
206
|
-
console.log("Usage: servermon <command>");
|
|
207
|
-
console.log();
|
|
208
|
-
console.log("Commands:");
|
|
209
|
-
console.log(" setup First-time setup (bot token + interval)");
|
|
210
|
-
console.log(" start Start the monitoring daemon");
|
|
211
|
-
console.log(" install-service Install as systemd user service");
|
|
212
|
-
console.log();
|
|
213
|
-
console.log("Examples:");
|
|
214
|
-
console.log(" servermon setup");
|
|
215
|
-
console.log(" servermon start");
|
|
216
|
-
console.log(" servermon install-service");
|
|
217
|
-
}
|
|
6
|
+
import { main } from "./src/cli";
|
|
218
7
|
|
|
219
8
|
main().catch((err) => {
|
|
220
9
|
console.error("❌ Fatal error:", err?.message ?? err);
|
package/index.ts
CHANGED
|
@@ -1,118 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const rawInterval = process.env["MONITOR_INTERVAL"] ?? "300";
|
|
7
|
-
const intervalSec = Math.max(30, parseInt(rawInterval) || 300);
|
|
8
|
-
|
|
9
|
-
// --- Auto-detect chat ID ---
|
|
10
|
-
async function autoDetectChatId(token: string): Promise<string | null> {
|
|
11
|
-
try {
|
|
12
|
-
const resp = await fetch(`https://api.telegram.org/bot${token}/getUpdates?limit=5`);
|
|
13
|
-
const data = (await resp.json()) as {
|
|
14
|
-
ok: boolean;
|
|
15
|
-
result?: Array<{
|
|
16
|
-
message?: { chat?: { id: number; title?: string; first_name?: string } };
|
|
17
|
-
channel_post?: { chat?: { id: number; title?: string } };
|
|
18
|
-
}>;
|
|
19
|
-
};
|
|
20
|
-
if (!data.ok || !data.result?.length) return null;
|
|
21
|
-
|
|
22
|
-
const chatIds = new Set<string>();
|
|
23
|
-
for (const update of data.result.reverse()) {
|
|
24
|
-
const chat = update.message?.chat || update.channel_post?.chat;
|
|
25
|
-
if (chat?.id) chatIds.add(String(chat.id));
|
|
26
|
-
}
|
|
27
|
-
if (chatIds.size === 0) return null;
|
|
28
|
-
|
|
29
|
-
const id = [...chatIds][0]!;
|
|
30
|
-
const chatInfo = data.result.find(
|
|
31
|
-
(u) => String(u.message?.chat?.id || u.channel_post?.chat?.id) === id
|
|
32
|
-
);
|
|
33
|
-
const chatName =
|
|
34
|
-
chatInfo?.message?.chat?.title ||
|
|
35
|
-
chatInfo?.message?.chat?.first_name ||
|
|
36
|
-
chatInfo?.channel_post?.chat?.title ||
|
|
37
|
-
"Unknown";
|
|
38
|
-
console.log(`🔍 Auto-detected chat: ${chatName} (ID: ${id})`);
|
|
39
|
-
return id;
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// --- Daemon ---
|
|
46
|
-
export async function start() {
|
|
47
|
-
if (!botToken) {
|
|
48
|
-
console.error("❌ TELEGRAM_BOT_TOKEN not set. Run `servermon` first to configure.");
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!chatId) {
|
|
53
|
-
console.log("🔍 TELEGRAM_CHAT_ID not set — auto-detecting...");
|
|
54
|
-
let detected = await autoDetectChatId(botToken);
|
|
55
|
-
|
|
56
|
-
if (!detected) {
|
|
57
|
-
console.log("⏳ Waiting for you to DM the bot on Telegram...");
|
|
58
|
-
console.log(" (polling every 10s — no restart needed)");
|
|
59
|
-
console.log();
|
|
60
|
-
|
|
61
|
-
const POLL_INTERVAL_MS = 10_000;
|
|
62
|
-
|
|
63
|
-
while (!detected) {
|
|
64
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
65
|
-
detected = await autoDetectChatId(botToken);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
chatId = detected;
|
|
70
|
-
// Persist to config
|
|
71
|
-
try {
|
|
72
|
-
const { loadConfig } = await import("./src/config");
|
|
73
|
-
const cfg = await loadConfig();
|
|
74
|
-
if (cfg) {
|
|
75
|
-
cfg.chatId = chatId;
|
|
76
|
-
await saveConfig(cfg);
|
|
77
|
-
console.log("💾 Chat ID saved to config");
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
// ok
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log(`⏱ Interval: ${intervalSec}s (${(intervalSec / 60).toFixed(0)} menit)`);
|
|
85
|
-
console.log(`📡 Bot: ...${botToken.slice(-8)}`);
|
|
86
|
-
console.log(`💬 Chat: ${chatId}`);
|
|
87
|
-
console.log();
|
|
88
|
-
|
|
89
|
-
async function tick() {
|
|
90
|
-
const start2 = Date.now();
|
|
91
|
-
const ok = await sendReport(botToken, chatId);
|
|
92
|
-
const elapsed = Date.now() - start2;
|
|
93
|
-
const ts = new Date().toLocaleString("id-ID", { timeZone: "Asia/Jakarta" });
|
|
94
|
-
console.log(`[${ts}] ${ok ? "✅" : "❌"} ${elapsed}ms`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Run once at startup
|
|
98
|
-
await tick();
|
|
99
|
-
|
|
100
|
-
// Loop
|
|
101
|
-
setInterval(tick, intervalSec * 1000);
|
|
102
|
-
|
|
103
|
-
process.on("SIGINT", () => {
|
|
104
|
-
console.log("\n👋 Shutting down...");
|
|
105
|
-
process.exit(0);
|
|
106
|
-
});
|
|
107
|
-
process.on("SIGTERM", () => {
|
|
108
|
-
console.log("\n👋 Shutting down...");
|
|
109
|
-
process.exit(0);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Direct run support (for dev / bun index.ts)
|
|
114
|
-
// When imported by cli.ts, cli.ts calls start() explicitly
|
|
115
|
-
const isDirectlyRun = import.meta.url.endsWith(process.argv[1]?.replace(/^.*\//, "") ?? "");
|
|
116
|
-
if (isDirectlyRun || process.argv[1]?.endsWith("index.ts")) {
|
|
117
|
-
start();
|
|
118
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Server Monitor — module entry point.
|
|
3
|
+
* Re-exports daemon functions for programmatic use.
|
|
4
|
+
*/
|
|
5
|
+
export { start, startAll } from "./src/daemon";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irsyadulibad/servermon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Lightweight server monitoring daemon — collects system metrics and sends structured reports to Telegram. Built with Bun + TypeScript.",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"servermon": "./cli.ts"
|
|
9
9
|
},
|
|
10
10
|
"private": false,
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"start": "bun index.ts",
|
|
13
16
|
"build": "bun build --compile index.ts --outfile server-monitor",
|
|
@@ -27,5 +30,9 @@
|
|
|
27
30
|
},
|
|
28
31
|
"peerDependencies": {
|
|
29
32
|
"typescript": "^5"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@crustjs/core": "^0.0.19",
|
|
36
|
+
"@crustjs/plugins": "^0.1.2"
|
|
30
37
|
}
|
|
31
38
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* ANSI-coloured ASCII banner */
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
|
|
5
|
+
const c = {
|
|
6
|
+
cyan: "\x1b[36m",
|
|
7
|
+
gray: "\x1b[90m",
|
|
8
|
+
gold: "\x1b[33m",
|
|
9
|
+
reset: "\x1b[0m",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function printBanner(): void {
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log(
|
|
15
|
+
`${c.cyan} ███████ ███████ ██████ ██ ██ ███████ ██████ ███ ███ ██████ ███ ██${c.reset}`
|
|
16
|
+
);
|
|
17
|
+
console.log(
|
|
18
|
+
`${c.cyan} ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ████ ██${c.reset}`
|
|
19
|
+
);
|
|
20
|
+
console.log(
|
|
21
|
+
`${c.cyan} ███████ █████ ██████ ██ ██ █████ ██████ ██ ████ ██ ██ ██ ██ ██ ██${c.reset}`
|
|
22
|
+
);
|
|
23
|
+
console.log(
|
|
24
|
+
`${c.cyan} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██${c.reset}`
|
|
25
|
+
);
|
|
26
|
+
console.log(
|
|
27
|
+
`${c.cyan} ███████ ███████ ██ ██ ████ ███████ ██ ██ ██ ██ ██████ ██ ████${c.reset}`
|
|
28
|
+
);
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(
|
|
31
|
+
`${c.gray} ─────────────────────────────────────────────────────────────────────${c.reset}`
|
|
32
|
+
);
|
|
33
|
+
console.log(` 🖥 Server Monitor Daemon • Telegram • Bun • TypeScript`);
|
|
34
|
+
console.log(
|
|
35
|
+
`${c.gray} ─────────────────────────────────────────────────────────────────────${c.reset}`
|
|
36
|
+
);
|
|
37
|
+
console.log(`${c.gold} by irsyadulibad${c.reset}`);
|
|
38
|
+
console.log("");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function printHelp(): void {
|
|
42
|
+
printBanner();
|
|
43
|
+
console.log("Usage: servermon <command> [--name <server>]");
|
|
44
|
+
console.log();
|
|
45
|
+
console.log("Commands:");
|
|
46
|
+
console.log(" setup [--name <srv>] First-time setup (bot token + interval) for a server");
|
|
47
|
+
console.log(" start [--name <srv>] Start the monitoring daemon for a server");
|
|
48
|
+
console.log(" report [--name <srv>] Send a one-time report without starting the daemon");
|
|
49
|
+
console.log(" list List all configured servers");
|
|
50
|
+
console.log(" delete --name <srv> Delete a configured server");
|
|
51
|
+
console.log(
|
|
52
|
+
" service <sub> Manage systemd service (install, status, stop, restart, logs, uninstall)"
|
|
53
|
+
);
|
|
54
|
+
console.log();
|
|
55
|
+
console.log("Examples:");
|
|
56
|
+
console.log(" servermon setup");
|
|
57
|
+
console.log(" servermon setup --name prod");
|
|
58
|
+
console.log(" servermon start --name staging");
|
|
59
|
+
console.log(" servermon report --name prod");
|
|
60
|
+
console.log(" servermon list");
|
|
61
|
+
console.log(" servermon delete --name prod");
|
|
62
|
+
console.log(" servermon service install");
|
|
63
|
+
console.log(" servermon service status");
|
|
64
|
+
console.log(" servermon service logs");
|
|
65
|
+
}
|