diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a8a2aec --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# 复制本文件为 .env 并按实际环境填写(.env 已被 .gitignore 忽略,切勿提交真实凭据) + +# 服务器列表(JSON 数组),多个服务器用逗号分隔 +SERVERS_JSON=[{"name":"web1","ip":"192.168.1.10","username":"root","password":"change-me"}] + +# 是否开启 Flask 调试模式(生产环境务必保持 0) +FLASK_DEBUG=0 + +# 以下变量仅供 test.py 使用 +# SSH_HOST=192.168.1.10 +# SSH_PORT=22 +# SSH_USER=root +# SSH_PASSWORD=change-me diff --git a/.gitignore b/.gitignore index 7e36f92..4fe8643 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,10 @@ cython_debug/ /.idea/ /.vscode/ + +# 项目本地数据:运行日志与截图(可能含内网信息,禁止提交) +logs/ +*.png + +# 历史清理临时文件 +replacements.txt diff --git a/app.py b/app.py index 7c26b6b..5ed7ae2 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,6 @@ from flask import Flask, render_template, request, redirect, url_for,jsonify +import json +import os import threading import time import logging @@ -46,12 +48,18 @@ def setup_logging(): setup_logging() -# 模拟的服务器信息,可以根据实际需求进行动态配置 -servers = [ - {"name": "现场端侧070", "ip": "***REMOVED***", "username": "root", "password": "***REMOVED***"}, - {"name": "测试服务器", "ip": "***REMOVED***", "username": "root", "password": "***REMOVED***"}, - {"name": "Server2", "ip": "192.168.1.2", "username": "user2", "password": "password2"}, -] +# 服务器列表通过环境变量 SERVERS_JSON 注入(JSON 数组),避免在代码中硬编码 IP / 账号 / 密码 +# 格式: [{"name": "web1", "ip": "192.168.1.10", "username": "root", "password": "xxx"}] +# 配置方式参考 .env.example +_servers_env = os.environ.get("SERVERS_JSON", "").strip() +if _servers_env: + try: + servers = json.loads(_servers_env) + except json.JSONDecodeError as e: + raise SystemExit(f"SERVERS_JSON 环境变量不是合法 JSON: {e}") +else: + servers = [] + logging.warning("未设置 SERVERS_JSON 环境变量,服务器列表为空,请参考 .env.example 进行配置") # 全局变量存储服务器状态 server_status = {} @@ -249,4 +257,6 @@ def control(): if __name__ == '__main__': # 启动定时任务 start_scheduler() - app.run(host='0.0.0.0',port=8090,debug=True) + # debug 模式默认关闭:开启后 Werkzeug 调试器会暴露在网络上,存在远程代码执行风险 + debug = os.environ.get("FLASK_DEBUG", "0").lower() in ("1", "true", "yes") + app.run(host='0.0.0.0', port=8090, debug=debug) diff --git a/docker-compose.yml b/docker-compose.yml index 4157032..ab2b33a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,10 @@ services: context: . dockerfile: Dockerfile container_name: linux_controller + environment: + # 服务器列表从环境变量注入(写入项目根目录 .env 文件即可,compose 会自动读取) + - SERVERS_JSON=${SERVERS_JSON:-} + - FLASK_DEBUG=${FLASK_DEBUG:-0} ports: - "8090:8090" volumes: diff --git a/readme.md b/readme.md index 3d02f8e..23c8a60 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,53 @@ -linux服务器控制 +# linux_controller -PC端: -http://***REMOVED***:8090/m -移动端: -http://***REMOVED***:8090/m +基于 Flask + Paramiko 的 Linux 服务器批量监控与开关机控制面板(PC / 移动端自适应)。 +## 功能 + +- 定时(60 秒)通过 SSH 采集各服务器 `top` 指标:CPU / 内存 / 负载 / 任务状态等 +- Web 页面展示服务器状态,支持远程关机 / 重启(带二次确认弹窗) + +## 快速开始 + +### 1. 配置服务器列表 + +复制 `.env.example` 为 `.env`,填写 `SERVERS_JSON`(JSON 数组,包含 name / ip / username / password)。 +`.env` 已被 `.gitignore` 忽略,**切勿提交真实凭据**。 + +### 2. 本地运行 + +```bash +pip install -r requirements.txt +``` + +Linux / macOS: + +```bash +export SERVERS_JSON='[{"name":"demo","ip":"192.168.1.10","username":"root","password":"your-password"}]' +python app.py +``` + +Windows PowerShell: + +```powershell +$env:SERVERS_JSON='[{"name":"demo","ip":"192.168.1.10","username":"root","password":"your-password"}]' +python app.py +``` + +### 3. Docker 运行 + +在项目根目录 `.env` 文件中配置 `SERVERS_JSON` 后: + +```bash +docker compose up -d --build +``` + +访问 `http://<主机IP>:8090/`(PC 端)、`http://<主机IP>:8090/m`(移动端)。 + +## 安全建议(部署前必读) + +- **切勿将服务直接暴露在公网**:本应用未内置登录认证与 CSRF 防护,任何能访问到它的人都可以远程关机 / 重启受控服务器。请仅部署在内网,并通过防火墙、VPN 或反向代理(BasicAuth 等)做访问控制。 +- 服务器凭据一律通过环境变量注入,不要写回代码或提交到仓库。 +- 代码中 `AutoAddPolicy()` 会自动接受未知主机指纹,存在中间人风险;对安全要求较高的环境请改为 `RejectPolicy` 并预置 known_hosts。 +- 建议 SSH 使用密钥认证代替密码认证。 +- 生产环境保持 `FLASK_DEBUG` 关闭(默认已关闭)。 diff --git a/send.sh b/send.sh index 173b36b..6625e91 100644 --- a/send.sh +++ b/send.sh @@ -1 +1,7 @@ -scp -r ./* root@***REMOVED***:/data/linux_controller/ \ No newline at end of file +#!/bin/bash +# 用法: ./send.sh [目标目录] +# 示例: ./send.sh root@192.168.1.100 +TARGET="${1:?用法: ./send.sh [目标目录]}" +DEST_DIR="${2:-/data/linux_controller}" + +scp -r ./* "$TARGET:$DEST_DIR/" diff --git a/test.py b/test.py index 227c54d..cbe5288 100644 --- a/test.py +++ b/test.py @@ -1,13 +1,21 @@ +import os +import sys + import paramiko -# 配置连接参数 -hostname = "***REMOVED***" # 远程服务器的 IP 地址 -port = 22 # SSH 端口,通常是 22 -username = "root" # SSH 用户名 -password = "***REMOVED***" # SSH 密码,或使用密钥认证 +# 连接参数从环境变量读取,避免在代码中硬编码任何真实凭据 +# 可参考 .env.example 配置;生产环境建议改用 SSH 密钥认证 +hostname = os.environ.get("SSH_HOST", "") +port = int(os.environ.get("SSH_PORT", "22")) +username = os.environ.get("SSH_USER", "") +password = os.environ.get("SSH_PASSWORD", "") + # SSH 连接并执行关机命令 def shutdown_server(): + if not all([hostname, username, password]): + print("请先通过环境变量配置 SSH_HOST / SSH_USER / SSH_PASSWORD(可参考 .env.example)") + sys.exit(1) try: # 创建 SSH 客户端对象 client = paramiko.SSHClient() @@ -40,4 +48,4 @@ def shutdown_server(): print(f"An error occurred: {e}") if __name__ == "__main__": - shutdown_server() \ No newline at end of file + shutdown_server()