kpinquan commited on
Commit
459e884
·
verified ·
1 Parent(s): 7eca5ab

Create vps_monitor.py

Browse files
Files changed (1) hide show
  1. vps_monitor.py +446 -0
vps_monitor.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import paramiko
2
+ import schedule
3
+ import time
4
+ import os
5
+ import sys
6
+ from flask import Flask, jsonify, render_template_string, request, redirect, session
7
+ from threading import Thread
8
+ import logging
9
+ from functools import wraps
10
+
11
+ app = Flask(__name__)
12
+ app.secret_key = 'your_secret_key' # 请替换为一个随机的安全密钥
13
+
14
+ vps_status = {}
15
+ start_time = time.time()
16
+
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s',
20
+ handlers=[
21
+ logging.StreamHandler(sys.stdout),
22
+ logging.StreamHandler(sys.stderr)
23
+ ]
24
+ )
25
+ logger = logging.getLogger()
26
+
27
+ # 设置访问密码
28
+ ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'your_default_password')
29
+
30
+ def get_vps_configs():
31
+ configs = []
32
+ index = 1
33
+ while True:
34
+ hostname = os.environ.get(f'HOSTNAME_{index}')
35
+ if not hostname:
36
+ break
37
+
38
+ username = os.environ.get(f'USERNAME_{index}')
39
+ password = os.environ.get(f'PASSWORD_{index}')
40
+
41
+ script_paths = []
42
+ script_index = 1
43
+ while True:
44
+ script_path = os.environ.get(f'SCRIPT_PATHS_{index}_{script_index}')
45
+ if not script_path:
46
+ break
47
+ port = os.environ.get(f'PORTS_{index}_{script_index}')
48
+ script_paths.append((script_path.strip(), port))
49
+ script_index += 1
50
+
51
+ for script_path, port in script_paths:
52
+ configs.append({
53
+ 'index': index,
54
+ 'hostname': hostname,
55
+ 'username': username,
56
+ 'password': password,
57
+ 'script_path': script_path,
58
+ 'port': port
59
+ })
60
+
61
+ index += 1
62
+ return configs
63
+
64
+ def check_and_run_script(config):
65
+ logger.info(f"Checking VPS {config['index']}: {config['hostname']} - {config['script_path']}")
66
+ client = None
67
+ try:
68
+ client = paramiko.SSHClient()
69
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
70
+ client.connect(hostname=config['hostname'], username=config['username'], password=config['password'], port=22)
71
+
72
+ port = config.get('port')
73
+ script_path = config['script_path']
74
+ script_name = os.path.basename(script_path)
75
+ key = f"{config['index']}:{config['hostname']}:{script_name}"
76
+
77
+ if port:
78
+ check_command = f"sockstat -4 -l | grep ':{port}'"
79
+ stdin, stdout, stderr = client.exec_command(check_command)
80
+ output = stdout.read().decode('utf-8').strip()
81
+
82
+ if output:
83
+ # 解析输出获取 'user', 'command', 'pid'
84
+ lines = output.strip().split('\n')
85
+ line = lines[0]
86
+ parts = line.split()
87
+ if len(parts) >= 3:
88
+ user = parts[0]
89
+ command = parts[1]
90
+ pid = parts[2]
91
+ else:
92
+ user = command = pid = 'N/A'
93
+
94
+ status = "Running"
95
+ vps_status[key] = {
96
+ 'index': config['index'],
97
+ 'status': status,
98
+ 'last_check': time.strftime('%Y-%m-%d %H:%M:%S'),
99
+ 'username': config['username'],
100
+ 'script_name': script_name,
101
+ 'user': user,
102
+ 'command': command,
103
+ 'pid': pid
104
+ }
105
+ logger.info(f"Port {port} is in use on VPS {config['index']} ({config['hostname']}), process is running.")
106
+ # 服务正在运行,无需执行后续脚本检查
107
+ return
108
+ else:
109
+ # 端口未被占用,继续检查脚本进程或启动脚本
110
+ logger.info(f"Port {port} is not in use on VPS {config['index']} ({config['hostname']}), proceeding to check script process.")
111
+
112
+ # 现在继续检查脚本是否正在运行
113
+ check_command = f"ps aux | grep '{script_path}' | grep -v grep"
114
+ stdin, stdout, stderr = client.exec_command(check_command)
115
+ output = stdout.read().decode('utf-8').strip()
116
+
117
+ if output and script_path in output:
118
+ status = "Running"
119
+ # 解析输出获取 PID 等信息
120
+ lines = output.strip().split('\n')
121
+ line = lines[0]
122
+ parts = line.split()
123
+ if len(parts) >= 2:
124
+ user = parts[0]
125
+ pid = parts[1]
126
+ else:
127
+ user = pid = 'N/A'
128
+ command = script_name
129
+ vps_status[key] = {
130
+ 'index': config['index'],
131
+ 'status': status,
132
+ 'last_check': time.strftime('%Y-%m-%d %H:%M:%S'),
133
+ 'username': config['username'],
134
+ 'script_name': script_name,
135
+ 'user': user,
136
+ 'command': command,
137
+ 'pid': pid
138
+ }
139
+ else:
140
+ logger.info(f"Script {script_name} not running. Attempting to restart.")
141
+ stdin, stdout, stderr = client.exec_command(f"nohup /bin/sh {script_path} > /dev/null 2>&1 & echo $!")
142
+ new_pid = stdout.read().decode('utf-8').strip()
143
+
144
+ if new_pid.isdigit():
145
+ status = "Restarted"
146
+ pid = new_pid
147
+ user = config['username']
148
+ command = script_name
149
+ else:
150
+ status = "Restart Failed"
151
+ pid = user = command = 'N/A'
152
+
153
+ vps_status[key] = {
154
+ 'index': config['index'],
155
+ 'status': status,
156
+ 'last_check': time.strftime('%Y-%m-%d %H:%M:%S'),
157
+ 'username': config['username'],
158
+ 'script_name': script_name,
159
+ 'user': user,
160
+ 'command': command,
161
+ 'pid': pid
162
+ }
163
+
164
+ except Exception as e:
165
+ logger.error(f"Error occurred while checking VPS {config['index']} - {config['hostname']} - {script_name}: {str(e)}")
166
+ vps_status[f"{config['index']}:{config['hostname']}:{script_name}"] = {
167
+ 'index': config['index'],
168
+ 'status': f"Error: {str(e)}",
169
+ 'last_check': time.strftime('%Y-%m-%d %H:%M:%S'),
170
+ 'username': config['username'],
171
+ 'script_name': script_name,
172
+ 'user': 'N/A',
173
+ 'command': 'N/A',
174
+ 'pid': 'N/A'
175
+ }
176
+ finally:
177
+ if client:
178
+ client.close()
179
+
180
+ def check_all_vps():
181
+ logger.info("Starting VPS check")
182
+ for config in get_vps_configs():
183
+ check_and_run_script(config)
184
+
185
+ table = "+---------+-----------------------+------------------+----------+-------------------------+----------+----------+----------+----------+\n"
186
+ table += "| Index | Hostname | Script Name | Status | Last Check | Username | User | Command | PID |\n"
187
+ table += "+---------+-----------------------+------------------+----------+-------------------------+----------+----------+----------+----------+\n"
188
+
189
+ for key, status in vps_status.items():
190
+ index, hostname, script_name = key.split(':')
191
+ table += "| {:<7} | {:<21} | {:<16} | {:<8} | {:<23} | {:<8} | {:<8} | {:<8} | {:<8} |\n".format(
192
+ status['index'], hostname[:21], script_name[:16], status['status'][:8],
193
+ status['last_check'], status['username'][:8],
194
+ status.get('user', 'N/A')[:8], status.get('command', 'N/A')[:8], status.get('pid', 'N/A')[:8]
195
+ )
196
+ table += "+---------+-----------------------+------------------+----------+-------------------------+----------+----------+----------+----------+\n"
197
+
198
+ logger.info("\n" + table)
199
+
200
+ # 登录功能
201
+ def login_required(f):
202
+ @wraps(f)
203
+ def decorated_function(*args, **kwargs):
204
+ if not session.get('logged_in'):
205
+ return redirect('/login')
206
+ return f(*args, **kwargs)
207
+ return decorated_function
208
+
209
+ @app.route('/login', methods=['GET', 'POST'])
210
+ def login():
211
+ if request.method == 'POST':
212
+ password = request.form.get('password', '')
213
+ if password == ADMIN_PASSWORD:
214
+ session['logged_in'] = True
215
+ return redirect('/')
216
+ else:
217
+ return render_template_string('''
218
+ <!DOCTYPE html>
219
+ <html lang="zh-CN">
220
+ <head>
221
+ <meta charset="UTF-8">
222
+ <title>登录</title>
223
+ <style>
224
+ body {
225
+ display: flex;
226
+ justify-content: center;
227
+ align-items: center;
228
+ height: 100vh;
229
+ margin: 0;
230
+ background-color: #f2f2f2;
231
+ }
232
+ .login-container {
233
+ text-align: center;
234
+ background-color: #ffffff;
235
+ padding: 30px;
236
+ border-radius: 8px;
237
+ box-shadow: 0px 0px 10px 0px #aaaaaa;
238
+ }
239
+ p.error {
240
+ color: red;
241
+ }
242
+ </style>
243
+ </head>
244
+ <body>
245
+ <div class="login-container">
246
+ <h2>密码错误</h2>
247
+ <a href="/login">返回</a>
248
+ </div>
249
+ </body>
250
+ </html>
251
+ ''')
252
+ return render_template_string('''
253
+ <!DOCTYPE html>
254
+ <html lang="zh-CN">
255
+ <head>
256
+ <meta charset="UTF-8">
257
+ <title>登录</title>
258
+ <style>
259
+ body {
260
+ display: flex;
261
+ justify-content: center;
262
+ align-items: center;
263
+ height: 100vh;
264
+ margin: 0;
265
+ background-color: #f2f2f2;
266
+ }
267
+ .login-container {
268
+ text-align: center;
269
+ background-color: #ffffff;
270
+ padding: 30px;
271
+ border-radius: 8px;
272
+ box-shadow: 0px 0px 10px 0px #aaaaaa;
273
+ }
274
+ input[type=password], input[type=submit] {
275
+ padding: 10px;
276
+ margin: 10px;
277
+ font-size: 16px;
278
+ width: 100%;
279
+ }
280
+ input[type=submit] {
281
+ width: 50%;
282
+ }
283
+ </style>
284
+ </head>
285
+ <body>
286
+ <div class="login-container">
287
+ <h2>请输入密码</h2>
288
+ <form method="post">
289
+ <p><input type="password" name="password" placeholder="密码"></p>
290
+ <p><input type="submit" value="登录"></p>
291
+ </form>
292
+ </div>
293
+ </body>
294
+ </html>
295
+ ''')
296
+
297
+ @app.route('/')
298
+ @login_required
299
+ def index():
300
+ html = '''
301
+ <!DOCTYPE html>
302
+ <html lang="zh-CN">
303
+ <head>
304
+ <meta charset="UTF-8">
305
+ <title>Serv00 状态概览</title>
306
+ <style>
307
+ body {
308
+ font-family: Arial, sans-serif;
309
+ }
310
+ .container {
311
+ width: 90%;
312
+ margin: 0 auto;
313
+ }
314
+ h1 {
315
+ text-align: center;
316
+ }
317
+ #executeButton {
318
+ display: block;
319
+ margin: 20px auto;
320
+ padding: 10px 20px;
321
+ font-size: 16px;
322
+ }
323
+ #result {
324
+ text-align: center;
325
+ color: green;
326
+ font-weight: bold;
327
+ }
328
+ table {
329
+ width: 100%;
330
+ border-collapse: collapse;
331
+ margin-top: 20px;
332
+ }
333
+ table, th, td {
334
+ border: 1px solid #dddddd;
335
+ }
336
+ th, td {
337
+ padding: 8px;
338
+ text-align: center;
339
+ }
340
+ th {
341
+ background-color: #f2f2f2;
342
+ }
343
+ tr:nth-child(even){
344
+ background-color: #f9f9f9;
345
+ }
346
+ </style>
347
+ </head>
348
+ <body>
349
+ <div class="container">
350
+ <h1>Serv00 状态概览</h1>
351
+ <button id="executeButton" onclick="executeTasks()">立即执行所有任务</button>
352
+ <p id="result"></p>
353
+ <table>
354
+ <tr>
355
+ <th>Index</th>
356
+ <th>Hostname</th>
357
+ <th>Script Name</th>
358
+ <th>Status</th>
359
+ <th>Last Check</th>
360
+ <th>Username</th>
361
+ <th>User</th>
362
+ <th>Command</th>
363
+ <th>PID</th>
364
+ </tr>
365
+ {% for key, data in vps_status.items() %}
366
+ <tr>
367
+ <td>{{ data.index }}</td>
368
+ <td><a href="/status/{{ key }}">{{ key.split(':')[1] }}</a></td>
369
+ <td>{{ data.script_name }}</td>
370
+ <td>{{ data.status }}</td>
371
+ <td>{{ data.last_check }}</td>
372
+ <td>{{ data.username }}</td>
373
+ <td>{{ data.user }}</td>
374
+ <td>{{ data.command }}</td>
375
+ <td>{{ data.pid }}</td>
376
+ </tr>
377
+ {% endfor %}
378
+ </table>
379
+ </div>
380
+ <script>
381
+ function executeTasks() {
382
+ fetch('/execute', {
383
+ method: 'POST',
384
+ credentials: 'include'
385
+ })
386
+ .then(response => response.json())
387
+ .then(data => {
388
+ document.getElementById('result').innerText = data.status;
389
+ setTimeout(function(){ location.reload(); }, 5000); // 5秒后刷新页面
390
+ })
391
+ .catch(error => {
392
+ console.error('Error:', error);
393
+ document.getElementById('result').innerText = '执行任务时出错。';
394
+ });
395
+ }
396
+ </script>
397
+ </body>
398
+ </html>
399
+ '''
400
+ return render_template_string(html, vps_status=vps_status)
401
+
402
+ @app.route('/execute', methods=['POST'])
403
+ @login_required
404
+ def execute_tasks():
405
+ # 异步执行任务,避免阻塞请求
406
+ Thread(target=check_all_vps).start()
407
+ return jsonify({"status": "所有任务正在执行,页面将在5秒后刷新。"})
408
+
409
+ @app.route('/status/<path:key>')
410
+ @login_required
411
+ def vps_status_detail(key):
412
+ return jsonify(vps_status[key]) if key in vps_status else (jsonify({"error": "VPS or script not found"}), 404)
413
+
414
+ @app.route('/health')
415
+ def health_check():
416
+ return jsonify({"status": "healthy", "uptime": time.time() - start_time}), 200
417
+
418
+ def run_flask():
419
+ app.run(host='0.0.0.0', port=7860)
420
+
421
+ def main():
422
+ global start_time
423
+ start_time = time.time()
424
+
425
+ logger.info("===== VPS monitoring script is starting =====")
426
+
427
+ Thread(target=run_flask).start()
428
+ logger.info("Flask server started in background")
429
+
430
+ check_all_vps()
431
+ schedule.every(4).hours.do(check_all_vps)
432
+ logger.info("Scheduled VPS check every 4 hours")
433
+
434
+ logger.info("===== VPS monitoring script is running =====")
435
+
436
+ heartbeat_count = 0
437
+ while True:
438
+ schedule.run_pending()
439
+ time.sleep(60)
440
+ heartbeat_count += 1
441
+ if heartbeat_count % 60 == 0:
442
+ uptime_hours = heartbeat_count // 60
443
+ logger.info(f"Heartbeat: Script is still running. Uptime: {uptime_hours} hours")
444
+
445
+ if __name__ == "__main__":
446
+ main()