kpinquan/serv00_process
0
1import paramiko2import schedule3import time4import os5import sys6from flask import Flask, jsonify, render_template_string, request, redirect, session7from threading import Thread8import logging9from functools import wraps10 11app = Flask(__name__)12app.secret_key = 'your_secret_key' # 请替换为一个随机的安全密钥13 14vps_status = {}15start_time = time.time()16 17logging.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)25logger = logging.getLogger()26 27# 设置访问密码28ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'your_default_password') 29 30def get_vps_configs():31 configs = []32 index = 133 while True:34 hostname = os.environ.get(f'HOSTNAME_{index}')35 if not hostname:36 break37 38 username = os.environ.get(f'USERNAME_{index}')39 password = os.environ.get(f'PASSWORD_{index}')40 41 script_paths = []42 script_index = 143 while True:44 script_path = os.environ.get(f'SCRIPT_PATHS_{index}_{script_index}')45 if not script_path:46 break47 port = os.environ.get(f'PORTS_{index}_{script_index}')48 script_paths.append((script_path.strip(), port))49 script_index += 150 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': port59 })60 61 index += 162 return configs63 64def check_and_run_script(config):65 logger.info(f"Checking VPS {config['index']}: {config['hostname']} - {config['script_path']}")66 client = None67 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': pid104 }105 logger.info(f"Port {port} is in use on VPS {config['index']} ({config['hostname']}), process is running.")106 # 服务正在运行,无需执行后续脚本检查107 return108 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_name129 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': pid138 }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_pid147 user = config['username']148 command = script_name149 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': pid162 }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 180def 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# 登录功能201def 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_function208 209@app.route('/login', methods=['GET', 'POST'])210def login():211 if request.method == 'POST':212 password = request.form.get('password', '')213 if password == ADMIN_PASSWORD:214 session['logged_in'] = True215 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_required299def 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_required404def execute_tasks():405 # 异步执行任务,避免阻塞请求406 Thread(target=check_all_vps).start()407 return jsonify({"status": "所有任务正在执行,页面将在5秒后刷新。"})408 409@app.route('/status/<path:key>')410@login_required411def 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')415def health_check():416 return jsonify({"status": "healthy", "uptime": time.time() - start_time}), 200417 418def run_flask():419 app.run(host='0.0.0.0', port=7860)420 421def main():422 global start_time423 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 = 0437 while True:438 schedule.run_pending()439 time.sleep(60)440 heartbeat_count += 1441 if heartbeat_count % 60 == 0:442 uptime_hours = heartbeat_count // 60443 logger.info(f"Heartbeat: Script is still running. Uptime: {uptime_hours} hours")444 445if __name__ == "__main__":446 main()