第一次提交
This commit is contained in:
186
scripts/generate_ssl.py
Normal file
186
scripts/generate_ssl.py
Normal file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SSL证书生成脚本
|
||||
用于生成自签名证书(开发和测试)或为Let's Encrypt准备
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def check_openssl():
|
||||
"""检查OpenSSL是否已安装"""
|
||||
try:
|
||||
result = subprocess.run(['openssl', 'version'], capture_output=True, text=True)
|
||||
print(f"✅ OpenSSL 已安装: {result.stdout.strip()}")
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print("❌ OpenSSL 未安装")
|
||||
print("\n请安装 OpenSSL:")
|
||||
print(" Ubuntu/Debian: sudo apt-get install openssl")
|
||||
print(" CentOS/RHEL: sudo yum install openssl")
|
||||
print(" macOS: brew install openssl")
|
||||
return False
|
||||
|
||||
|
||||
def generate_self_signed_cert(domain="localhost", days=365):
|
||||
"""生成自签名SSL证书"""
|
||||
print(f"\n🔒 生成自签名SSL证书...")
|
||||
print(f" 域名: {domain}")
|
||||
print(f" 有效期: {days} 天")
|
||||
|
||||
# 创建证书目录
|
||||
cert_dir = Path('certs')
|
||||
cert_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 证书文件路径
|
||||
key_file = cert_dir / f"{domain}.key"
|
||||
cert_file = cert_dir / f"{domain}.crt"
|
||||
csr_file = cert_dir / f"{domain}.csr"
|
||||
|
||||
# 生成私钥
|
||||
print("\n1. 生成私钥...")
|
||||
cmd_key = [
|
||||
'openssl', 'genrsa',
|
||||
'-out', str(key_file),
|
||||
'2048'
|
||||
]
|
||||
try:
|
||||
subprocess.run(cmd_key, check=True, capture_output=True)
|
||||
print(f" ✅ 私钥已生成: {key_file}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" ❌ 生成私钥失败: {e.stderr.decode()}")
|
||||
return False
|
||||
|
||||
# 生成证书签名请求
|
||||
print("\n2. 生成证书签名请求...")
|
||||
cmd_csr = [
|
||||
'openssl', 'req',
|
||||
'-new',
|
||||
'-key', str(key_file),
|
||||
'-out', str(csr_file),
|
||||
'-subj', f'/C=CN/ST=Beijing/L=Beijing/O=KaMiXiTong/CN={domain}'
|
||||
]
|
||||
try:
|
||||
subprocess.run(cmd_csr, check=True, capture_output=True)
|
||||
print(f" ✅ 证书签名请求已生成: {csr_file}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" ❌ 生成证书签名请求失败: {e.stderr.decode()}")
|
||||
return False
|
||||
|
||||
# 生成自签名证书
|
||||
print("\n3. 生成自签名证书...")
|
||||
cmd_cert = [
|
||||
'openssl', 'x509',
|
||||
'-req',
|
||||
'-in', str(csr_file),
|
||||
'-signkey', str(key_file),
|
||||
'-out', str(cert_file),
|
||||
'-days', str(days)
|
||||
]
|
||||
try:
|
||||
subprocess.run(cmd_cert, check=True, capture_output=True)
|
||||
print(f" ✅ 自签名证书已生成: {cert_file}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" ❌ 生成自签名证书失败: {e.stderr.decode()}")
|
||||
return False
|
||||
|
||||
# 清理临时文件
|
||||
csr_file.unlink()
|
||||
|
||||
# 设置文件权限
|
||||
key_file.chmod(0o600)
|
||||
cert_file.chmod(0o644)
|
||||
|
||||
print("\n✅ SSL证书生成完成!")
|
||||
print(f"\n📁 文件位置:")
|
||||
print(f" 私钥: {key_file.absolute()}")
|
||||
print(f" 证书: {cert_file.absolute()}")
|
||||
print(f"\n⚠️ 安全提醒:")
|
||||
print(f" - 私钥文件权限已设置为600(仅所有者可读写)")
|
||||
print(f" - 自签名证书仅用于开发和测试")
|
||||
print(f" - 生产环境请使用 Let's Encrypt 或其他CA签发的证书")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def generate_certbot_config(domain, email):
|
||||
"""生成 Certbot 配置"""
|
||||
print(f"\n🔒 生成 Certbot 配置...")
|
||||
|
||||
cert_dir = Path('certs')
|
||||
cert_dir.mkdir(exist_ok=True)
|
||||
|
||||
config_content = f"""# Certbot 配置文件
|
||||
|
||||
# 申请 Let's Encrypt 证书
|
||||
certbot certonly \\
|
||||
--webroot \\
|
||||
-w /var/www/html \\
|
||||
-d {domain} \\
|
||||
--email {email} \\
|
||||
--agree-tos \\
|
||||
--non-interactive \\
|
||||
--keep-until-expiring
|
||||
|
||||
# 证书自动续期(添加到 crontab)
|
||||
0 12 * * * /usr/bin/certbot renew --quiet
|
||||
"""
|
||||
|
||||
config_file = cert_dir / 'certbot.conf'
|
||||
with open(config_file, 'w') as f:
|
||||
f.write(config_content)
|
||||
|
||||
print(f"✅ Certbot 配置已生成: {config_file}")
|
||||
print(f"\n📋 使用说明:")
|
||||
print(f" 1. 安装 Certbot: sudo apt-get install certbot python3-certbot-nginx")
|
||||
print(f" 2. 运行配置: sudo bash certbot.conf")
|
||||
print(f" 3. 证书自动续期: 每天12点检查")
|
||||
print(f" 4. 手动续期: sudo certbot renew")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("🔒 SSL证书生成工具")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查OpenSSL
|
||||
if not check_openssl():
|
||||
sys.exit(1)
|
||||
|
||||
# 获取用户输入
|
||||
print("\n请选择证书类型:")
|
||||
print("1. 自签名证书(开发和测试)")
|
||||
print("2. Let's Encrypt 配置(生产环境推荐)")
|
||||
choice = input("\n请选择 (1/2): ").strip()
|
||||
|
||||
if choice == '1':
|
||||
domain = input("\n请输入域名或IP地址 (默认: localhost): ").strip() or 'localhost'
|
||||
days = input("请输入有效期天数 (默认: 365): ").strip()
|
||||
days = int(days) if days.isdigit() else 365
|
||||
|
||||
generate_self_signed_cert(domain, days)
|
||||
|
||||
elif choice == '2':
|
||||
domain = input("\n请输入域名: ").strip()
|
||||
email = input("请输入邮箱地址: ").strip()
|
||||
|
||||
if not domain or not email:
|
||||
print("❌ 域名和邮箱不能为空")
|
||||
sys.exit(1)
|
||||
|
||||
generate_certbot_config(domain, email)
|
||||
|
||||
else:
|
||||
print("❌ 无效选择")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 完成!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
222
scripts/health_check.py
Normal file
222
scripts/health_check.py
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
简单监控脚本
|
||||
用于快速检查系统健康状态,不依赖外部工具
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import requests
|
||||
import psutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class HealthChecker:
|
||||
"""健康检查器"""
|
||||
|
||||
def __init__(self, base_url='http://localhost:5000'):
|
||||
self.base_url = base_url
|
||||
self.checks = []
|
||||
|
||||
def check_service_health(self):
|
||||
"""检查服务健康状态"""
|
||||
print("\n🔍 检查服务健康状态...")
|
||||
try:
|
||||
response = requests.get(f'{self.base_url}/api/v1/health', timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get('success'):
|
||||
print("✅ 服务健康检查通过")
|
||||
return True
|
||||
print("❌ 服务健康检查失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 无法连接到服务: {str(e)}")
|
||||
return False
|
||||
|
||||
def check_system_resources(self):
|
||||
"""检查系统资源"""
|
||||
print("\n🔍 检查系统资源...")
|
||||
|
||||
# CPU 使用率
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
print(f" CPU 使用率: {cpu_percent:.1f}%")
|
||||
cpu_status = "✅" if cpu_percent < 80 else "⚠️" if cpu_percent < 90 else "❌"
|
||||
|
||||
# 内存使用率
|
||||
memory = psutil.virtual_memory()
|
||||
print(f" 内存使用率: {memory.percent:.1f}%")
|
||||
memory_status = "✅" if memory.percent < 85 else "⚠️" if memory.percent < 95 else "❌"
|
||||
|
||||
# 磁盘使用率
|
||||
disk = psutil.disk_usage('/')
|
||||
disk_percent = (disk.used / disk.total) * 100
|
||||
print(f" 磁盘使用率: {disk_percent:.1f}%")
|
||||
disk_status = "✅" if disk_percent < 90 else "⚠️" if disk_percent < 95 else "❌"
|
||||
|
||||
# 负载均值
|
||||
load_avg = psutil.getloadavg()
|
||||
print(f" 系统负载: {load_avg[0]:.2f}, {load_avg[1]:.2f}, {load_avg[2]:.2f}")
|
||||
load_status = "✅" if load_avg[0] < 2 else "⚠️" if load_avg[0] < 4 else "❌"
|
||||
|
||||
return all([
|
||||
cpu_status == "✅",
|
||||
memory_status == "✅",
|
||||
disk_status == "✅",
|
||||
load_status == "✅"
|
||||
])
|
||||
|
||||
def check_database_connection(self):
|
||||
"""检查数据库连接"""
|
||||
print("\n🔍 检查数据库连接...")
|
||||
try:
|
||||
# 这里可以添加数据库连接检查
|
||||
# 由于需要导入Flask应用,暂时跳过
|
||||
print("✅ 数据库连接检查(跳过,需要应用上下文)")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 数据库连接检查失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def check_disk_space(self):
|
||||
"""检查磁盘空间"""
|
||||
print("\n🔍 检查磁盘空间...")
|
||||
disk_usage = psutil.disk_usage('/')
|
||||
free_gb = disk_usage.free / (1024**3)
|
||||
total_gb = disk_usage.total / (1024**3)
|
||||
print(f" 可用空间: {free_gb:.2f} GB / {total_gb:.2f} GB")
|
||||
return free_gb > 1 # 至少需要1GB可用空间
|
||||
|
||||
def check_process_status(self):
|
||||
"""检查关键进程"""
|
||||
print("\n🔍 检查关键进程...")
|
||||
processes = ['python', 'flask', 'nginx', 'mysql', 'redis']
|
||||
found_processes = []
|
||||
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
try:
|
||||
if any(proc.info['name'] and proc.info['name'].lower().startswith(p.lower()) for p in processes):
|
||||
found_processes.append(proc.info['name'])
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
|
||||
print(f" 发现进程: {', '.join(set(found_processes))}")
|
||||
return len(found_processes) > 0
|
||||
|
||||
def check_log_files(self):
|
||||
"""检查日志文件"""
|
||||
print("\n🔍 检查日志文件...")
|
||||
log_files = [
|
||||
'logs/kamaxitong.log',
|
||||
'/var/log/nginx/access.log',
|
||||
'/var/log/nginx/error.log'
|
||||
]
|
||||
|
||||
for log_file in log_files:
|
||||
path = Path(log_file)
|
||||
if path.exists():
|
||||
size_mb = path.stat().st_size / (1024 * 1024)
|
||||
print(f" {log_file}: {size_mb:.2f} MB")
|
||||
else:
|
||||
print(f" {log_file}: 文件不存在")
|
||||
|
||||
return True
|
||||
|
||||
def generate_report(self):
|
||||
"""生成健康报告"""
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 KaMiXiTong 系统健康报告")
|
||||
print("=" * 60)
|
||||
print(f"检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# 运行所有检查
|
||||
checks = [
|
||||
('服务健康', self.check_service_health),
|
||||
('系统资源', self.check_system_resources),
|
||||
('数据库连接', self.check_database_connection),
|
||||
('磁盘空间', self.check_disk_space),
|
||||
('进程状态', self.check_process_status),
|
||||
('日志文件', self.check_log_files)
|
||||
]
|
||||
|
||||
results = []
|
||||
for name, check_func in checks:
|
||||
try:
|
||||
result = check_func()
|
||||
results.append((name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {name}检查失败: {str(e)}")
|
||||
results.append((name, False))
|
||||
|
||||
# 生成报告
|
||||
print("\n" + "=" * 60)
|
||||
print("📋 检查结果汇总")
|
||||
print("=" * 60)
|
||||
|
||||
all_passed = True
|
||||
for name, passed in results:
|
||||
status = "✅ 通过" if passed else "❌ 失败"
|
||||
print(f"{name:20s}: {status}")
|
||||
if not passed:
|
||||
all_passed = False
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if all_passed:
|
||||
print("✅ 系统健康状态良好")
|
||||
else:
|
||||
print("⚠️ 发现问题,请查看上述详细信息")
|
||||
print("=" * 60)
|
||||
|
||||
return all_passed
|
||||
|
||||
def save_report(self, filename='health_report.json'):
|
||||
"""保存健康报告到文件"""
|
||||
report = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'checks': [
|
||||
{
|
||||
'name': name,
|
||||
'passed': passed,
|
||||
'time': datetime.now().isoformat()
|
||||
}
|
||||
for name, passed in self.results
|
||||
],
|
||||
'system_info': {
|
||||
'cpu_count': psutil.cpu_count(),
|
||||
'memory_total': psutil.virtual_memory().total,
|
||||
'disk_total': psutil.disk_usage('/').total
|
||||
}
|
||||
}
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
print(f"\n📄 健康报告已保存到: {filename}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("🔍 KaMiXiTong 系统健康检查")
|
||||
print("=" * 60)
|
||||
|
||||
# 获取基础URL
|
||||
base_url = input("请输入服务地址 (默认: http://localhost:5000): ").strip()
|
||||
if not base_url:
|
||||
base_url = 'http://localhost:5000'
|
||||
|
||||
checker = HealthChecker(base_url)
|
||||
passed = checker.generate_report()
|
||||
|
||||
# 保存报告
|
||||
save = input("\n是否保存健康报告? (y/N): ").strip().lower()
|
||||
if save == 'y':
|
||||
checker.save_report()
|
||||
|
||||
sys.exit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
215
scripts/setup_env.py
Normal file
215
scripts/setup_env.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
环境变量配置和验证脚本
|
||||
自动生成安全的随机密钥并验证配置
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import secrets
|
||||
import getpass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def generate_secret_key(length=32):
|
||||
"""生成安全的随机密钥"""
|
||||
return secrets.token_urlsafe(length)
|
||||
|
||||
|
||||
def generate_hex_key(length=32):
|
||||
"""生成十六进制随机密钥"""
|
||||
return secrets.token_hex(length)
|
||||
|
||||
|
||||
def check_env_vars():
|
||||
"""检查必需的环境变量"""
|
||||
print("🔍 检查环境变量配置...")
|
||||
print("-" * 60)
|
||||
|
||||
required_vars = {
|
||||
'SECRET_KEY': '应用密钥(用于会话加密)',
|
||||
'AUTH_SECRET_KEY': '认证密钥(用于API签名)',
|
||||
'DATABASE_URL': '数据库连接URL'
|
||||
}
|
||||
|
||||
missing_vars = []
|
||||
weak_keys = []
|
||||
|
||||
for var_name, description in required_vars.items():
|
||||
value = os.environ.get(var_name)
|
||||
if not value:
|
||||
print(f"❌ {var_name:20s} - 未设置 ({description})")
|
||||
missing_vars.append(var_name)
|
||||
else:
|
||||
print(f"✅ {var_name:20s} - 已设置")
|
||||
# 检查密钥强度
|
||||
if 'KEY' in var_name:
|
||||
if len(value) < 32:
|
||||
print(f"⚠️ {var_name:20s} - 密钥长度不足32字符 (当前: {len(value)})")
|
||||
weak_keys.append(var_name)
|
||||
else:
|
||||
print(f"✅ {var_name:20s} - 密钥长度符合要求")
|
||||
|
||||
print("-" * 60)
|
||||
|
||||
if missing_vars:
|
||||
print(f"\n❌ 发现 {len(missing_vars)} 个未设置的环境变量")
|
||||
return False
|
||||
elif weak_keys:
|
||||
print(f"\n⚠️ 发现 {len(weak_keys)} 个强度不足的密钥")
|
||||
return False
|
||||
else:
|
||||
print("\n✅ 所有必需的环境变量已正确配置")
|
||||
return True
|
||||
|
||||
|
||||
def generate_env_file():
|
||||
"""生成 .env 文件"""
|
||||
print("\n🚀 生成 .env 文件...")
|
||||
print("-" * 60)
|
||||
|
||||
# 收集用户输入
|
||||
database_url = input("数据库URL (mysql://user:pass@localhost:3306/dbname): ").strip()
|
||||
if not database_url:
|
||||
print("❌ 数据库URL不能为空")
|
||||
return False
|
||||
|
||||
frontend_domain = input("前端域名 (your-domain.com): ").strip()
|
||||
admin_email = input("管理员邮箱 (admin@yourcompany.com): ").strip()
|
||||
|
||||
# 生成随机密钥
|
||||
secret_key = generate_secret_key(32)
|
||||
auth_secret_key = generate_secret_key(32)
|
||||
|
||||
# 生成 .env 文件内容
|
||||
env_content = f"""# KaMiXiTong 生产环境配置
|
||||
# 生成时间: {__import__('datetime').datetime.now().isoformat()}
|
||||
|
||||
# ==========================================
|
||||
# 🔐 安全配置
|
||||
# ==========================================
|
||||
SECRET_KEY={secret_key}
|
||||
AUTH_SECRET_KEY={auth_secret_key}
|
||||
|
||||
# ==========================================
|
||||
# 🗄️ 数据库配置
|
||||
# ==========================================
|
||||
DATABASE_URL={database_url}
|
||||
DB_POOL_SIZE=20
|
||||
DB_MAX_OVERFLOW=30
|
||||
DB_POOL_RECYCLE=3600
|
||||
DB_POOL_TIMEOUT=30
|
||||
|
||||
# ==========================================
|
||||
# 🌐 系统配置
|
||||
# ==========================================
|
||||
SITE_NAME=KaMiXiTong软件授权管理系统
|
||||
ADMIN_EMAIL={admin_email}
|
||||
FRONTEND_DOMAIN={frontend_domain}
|
||||
API_VERSION=v1
|
||||
|
||||
# ==========================================
|
||||
# 🔒 安全策略
|
||||
# ==========================================
|
||||
SESSION_COOKIE_SECURE=true
|
||||
SESSION_COOKIE_HTTPONLY=true
|
||||
SESSION_COOKIE_SAMESITE=Lax
|
||||
SESSION_LIFETIME_HOURS=24
|
||||
REMEMBER_COOKIE_DURATION=30
|
||||
REMEMBER_COOKIE_SECURE=true
|
||||
MAX_FAILED_ATTEMPTS=5
|
||||
LOCKOUT_MINUTES=10
|
||||
MAX_UNBIND_TIMES=3
|
||||
|
||||
# ==========================================
|
||||
# 💾 缓存配置
|
||||
# ==========================================
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# ==========================================
|
||||
# 📝 日志配置
|
||||
# ==========================================
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=logs/kamaxitong.log
|
||||
|
||||
# ==========================================
|
||||
# 📦 文件上传
|
||||
# ==========================================
|
||||
MAX_CONTENT_LENGTH=52428800
|
||||
UPLOAD_FOLDER=static/uploads
|
||||
|
||||
# ==========================================
|
||||
# 🏷️ 卡密配置
|
||||
# ==========================================
|
||||
LICENSE_KEY_LENGTH=32
|
||||
TRIAL_PREFIX=TRIAL_
|
||||
OFFLINE_CACHE_DAYS=7
|
||||
|
||||
# ==========================================
|
||||
# 🌍 环境标识
|
||||
# ==========================================
|
||||
FLASK_ENV=production
|
||||
DEBUG=false
|
||||
|
||||
# ==========================================
|
||||
# 💳 支付配置(可选)
|
||||
# ==========================================
|
||||
PAYMENT_ENABLED=false
|
||||
"""
|
||||
|
||||
# 写入文件
|
||||
env_file = Path('.env')
|
||||
try:
|
||||
with open(env_file, 'w', encoding='utf-8') as f:
|
||||
f.write(env_content)
|
||||
print(f"✅ .env 文件已生成: {env_file.absolute()}")
|
||||
print(f"\n⚠️ 请妥善保管此文件,不要提交到版本控制系统")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 生成 .env 文件失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("🔧 KaMiXiTong 环境变量配置工具")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查是否已存在 .env 文件
|
||||
env_file = Path('.env')
|
||||
if env_file.exists():
|
||||
print(f"\n⚠️ 发现已存在的 .env 文件")
|
||||
overwrite = input("是否覆盖?(y/N): ").strip().lower()
|
||||
if overwrite != 'y':
|
||||
print("操作已取消")
|
||||
return
|
||||
|
||||
# 生成 .env 文件
|
||||
if not generate_env_file():
|
||||
sys.exit(1)
|
||||
|
||||
# 重新加载环境变量
|
||||
print("\n🔄 重新加载环境变量...")
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
# 验证配置
|
||||
if not check_env_vars():
|
||||
print("\n❌ 环境变量配置不完整,请检查后重新运行")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 环境变量配置完成!")
|
||||
print("=" * 60)
|
||||
print("\n📋 下一步操作:")
|
||||
print("1. 运行数据库迁移: flask db upgrade")
|
||||
print("2. 启动应用: flask run")
|
||||
print("3. 访问健康检查: curl http://localhost:5000/api/v1/health")
|
||||
print("\n🔐 安全提醒:")
|
||||
print("- 妥善保管 .env 文件,不要提交到版本控制")
|
||||
print("- 定期更换密钥")
|
||||
print("- 确保生产环境启用HTTPS")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
385
scripts/setup_monitoring.py
Normal file
385
scripts/setup_monitoring.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
监控告警配置脚本
|
||||
自动安装和配置 Prometheus + Grafana 监控栈
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class MonitoringSetup:
|
||||
"""监控配置类"""
|
||||
|
||||
def __init__(self):
|
||||
self.monitoring_dir = Path('monitoring')
|
||||
self.docker_compose_file = self.monitoring_dir / 'docker-compose.yml'
|
||||
|
||||
def check_docker(self):
|
||||
"""检查 Docker 和 Docker Compose"""
|
||||
print("🔍 检查 Docker 环境...")
|
||||
|
||||
try:
|
||||
result = subprocess.run(['docker', '--version'], capture_output=True, text=True)
|
||||
print(f"✅ Docker: {result.stdout.strip()}")
|
||||
except FileNotFoundError:
|
||||
print("❌ Docker 未安装")
|
||||
print("\n请安装 Docker: https://docs.docker.com/get-docker/")
|
||||
return False
|
||||
|
||||
try:
|
||||
result = subprocess.run(['docker-compose', '--version'], capture_output=True, text=True)
|
||||
print(f"✅ Docker Compose: {result.stdout.strip()}")
|
||||
except FileNotFoundError:
|
||||
try:
|
||||
result = subprocess.run(['docker', 'compose', 'version'], capture_output=True, text=True)
|
||||
print(f"✅ Docker Compose: {result.stdout.strip()}")
|
||||
except FileNotFoundError:
|
||||
print("❌ Docker Compose 未安装")
|
||||
print("\n请安装 Docker Compose")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def create_docker_compose(self):
|
||||
"""创建 Docker Compose 配置"""
|
||||
print("\n📝 创建 Docker Compose 配置...")
|
||||
|
||||
self.monitoring_dir.mkdir(exist_ok=True)
|
||||
|
||||
compose_content = """version: '3.8'
|
||||
|
||||
services:
|
||||
# Prometheus 监控
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: kamaxitong-prometheus
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- ./alert_rules.yml:/etc/prometheus/alert_rules.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
||||
- '--web.console.templates=/etc/prometheus/consoles'
|
||||
- '--storage.tsdb.retention.time=200h'
|
||||
- '--web.enable-lifecycle'
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
# Grafana 仪表板
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: kamaxitong-grafana
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./grafana/provisioning:/etc/grafana/provisioning
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_USER=admin
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin123
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
# Node Exporter 系统监控
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
container_name: kamaxitong-node-exporter
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9100:9100"
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.rootfs=/rootfs'
|
||||
- '--path.sysfs=/host/sys'
|
||||
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
# AlertManager 告警管理
|
||||
alertmanager:
|
||||
image: prom/alertmanager:latest
|
||||
container_name: kamaxitong-alertmanager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9093:9093"
|
||||
volumes:
|
||||
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
|
||||
- alertmanager_data:/alertmanager
|
||||
command:
|
||||
- '--config.file=/etc/alertmanager/alertmanager.yml'
|
||||
- '--storage.path=/alertmanager'
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
# Redis Exporter
|
||||
redis-exporter:
|
||||
image: oliver006/redis_exporter:latest
|
||||
container_name: kamaxitong-redis-exporter
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9121:9121"
|
||||
environment:
|
||||
- REDIS_ADDR=redis://redis:6379
|
||||
networks:
|
||||
- monitoring
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
# Redis 数据库
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: kamaxitong-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
alertmanager_data:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
monitoring:
|
||||
driver: bridge
|
||||
"""
|
||||
|
||||
with open(self.docker_compose_file, 'w') as f:
|
||||
f.write(compose_content)
|
||||
|
||||
print(f"✅ Docker Compose 配置已创建: {self.docker_compose_file}")
|
||||
|
||||
def create_alertmanager_config(self):
|
||||
"""创建 AlertManager 配置"""
|
||||
print("\n📝 创建 AlertManager 配置...")
|
||||
|
||||
alertmanager_config = self.monitoring_dir / 'alertmanager.yml'
|
||||
|
||||
config_content = """global:
|
||||
smtp_smarthost: 'localhost:587'
|
||||
smtp_from: 'alerts@yourcompany.com'
|
||||
smtp_auth_username: 'alerts@yourcompany.com'
|
||||
smtp_auth_password: 'your-password'
|
||||
|
||||
route:
|
||||
group_by: ['alertname']
|
||||
group_wait: 10s
|
||||
group_interval: 10s
|
||||
repeat_interval: 1h
|
||||
receiver: 'web.hook'
|
||||
routes:
|
||||
- match:
|
||||
severity: critical
|
||||
receiver: 'critical-alerts'
|
||||
- match:
|
||||
severity: warning
|
||||
receiver: 'warning-alerts'
|
||||
|
||||
receivers:
|
||||
- name: 'web.hook'
|
||||
webhook_configs:
|
||||
- url: 'http://localhost:5001/alert'
|
||||
send_resolved: true
|
||||
|
||||
- name: 'critical-alerts'
|
||||
email_configs:
|
||||
- to: 'admin@yourcompany.com'
|
||||
subject: '【严重告警】KaMiXiTong 系统告警'
|
||||
body: |
|
||||
{{ range .Alerts }}
|
||||
告警: {{ .Annotations.summary }}
|
||||
描述: {{ .Annotations.description }}
|
||||
时间: {{ .StartsAt }}
|
||||
级别: {{ .Labels.severity }}
|
||||
{{ end }}
|
||||
|
||||
- name: 'warning-alerts'
|
||||
email_configs:
|
||||
- to: 'admin@yourcompany.com'
|
||||
subject: '【警告】KaMiXiTong 系统告警'
|
||||
body: |
|
||||
{{ range .Alerts }}
|
||||
告警: {{ .Annotations.summary }}
|
||||
描述: {{ .Annotations.description }}
|
||||
时间: {{ .StartsAt }}
|
||||
级别: {{ .Labels.severity }}
|
||||
{{ end }}
|
||||
|
||||
inhibit_rules:
|
||||
- source_match:
|
||||
severity: 'critical'
|
||||
target_match:
|
||||
severity: 'warning'
|
||||
equal: ['alertname', 'dev', 'instance']
|
||||
"""
|
||||
|
||||
with open(alertmanager_config, 'w') as f:
|
||||
f.write(config_content)
|
||||
|
||||
print(f"✅ AlertManager 配置已创建: {alertmanager_config}")
|
||||
|
||||
def create_grafana_provisioning(self):
|
||||
"""创建 Grafana 配置"""
|
||||
print("\n📝 创建 Grafana 配置...")
|
||||
|
||||
provisioning_dir = self.monitoring_dir / 'grafana' / 'provisioning'
|
||||
provisioning_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 数据源配置
|
||||
datasource_config = provisioning_dir / 'datasources.yml'
|
||||
datasource_content = """apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
"""
|
||||
|
||||
with open(datasource_config, 'w') as f:
|
||||
f.write(datasource_content)
|
||||
|
||||
# 仪表板配置
|
||||
dashboard_config = provisioning_dir / 'dashboards.yml'
|
||||
dashboard_content = """apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'KaMiXiTong'
|
||||
orgId: 1
|
||||
folder: 'KaMiXiTong'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
||||
"""
|
||||
|
||||
with open(dashboard_config, 'w') as f:
|
||||
f.write(dashboard_content)
|
||||
|
||||
print("✅ Grafana 配置已创建")
|
||||
|
||||
def copy_monitoring_files(self):
|
||||
"""复制监控配置文件"""
|
||||
print("\n📁 复制监控配置文件...")
|
||||
|
||||
# 复制 Prometheus 配置
|
||||
prometheus_src = Path('monitoring/prometheus.yml')
|
||||
prometheus_dst = self.monitoring_dir / 'prometheus.yml'
|
||||
if prometheus_src.exists():
|
||||
prometheus_dst.write_text(prometheus_src.read_text())
|
||||
|
||||
# 复制告警规则
|
||||
alert_rules_src = Path('monitoring/alert_rules.yml')
|
||||
alert_rules_dst = self.monitoring_dir / 'alert_rules.yml'
|
||||
if alert_rules_src.exists():
|
||||
alert_rules_dst.write_text(alert_rules_src.read_text())
|
||||
|
||||
# 复制 Grafana 仪表板
|
||||
grafana_src = Path('monitoring/grafana_dashboard.json')
|
||||
grafana_dashboard_dir = self.monitoring_dir / 'grafana' / 'provisioning' / 'dashboards'
|
||||
grafana_dashboard_dir.mkdir(parents=True, exist_ok=True)
|
||||
grafana_dst = grafana_dashboard_dir / 'kamaxitong_dashboard.json'
|
||||
if grafana_src.exists():
|
||||
grafana_dst.write_text(grafana_src.read_text())
|
||||
|
||||
print("✅ 配置文件已复制")
|
||||
|
||||
def start_monitoring(self):
|
||||
"""启动监控服务"""
|
||||
print("\n🚀 启动监控服务...")
|
||||
|
||||
os.chdir(self.monitoring_dir)
|
||||
|
||||
# 启动服务
|
||||
print("启动 Docker Compose...")
|
||||
result = subprocess.run(['docker-compose', 'up', '-d'], capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"❌ 启动失败: {result.stderr}")
|
||||
return False
|
||||
|
||||
print("✅ 监控服务已启动")
|
||||
return True
|
||||
|
||||
def show_access_info(self):
|
||||
"""显示访问信息"""
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 监控服务配置完成!")
|
||||
print("=" * 60)
|
||||
print("\n📊 访问地址:")
|
||||
print(" Grafana 仪表板: http://localhost:3000")
|
||||
print(" 用户名: admin")
|
||||
print(" 密码: admin123")
|
||||
print("\n Prometheus: http://localhost:9090")
|
||||
print(" AlertManager: http://localhost:9093")
|
||||
print(" Node Exporter: http://localhost:9100")
|
||||
print("\n📋 常用命令:")
|
||||
print(" 查看服务状态: docker-compose ps")
|
||||
print(" 查看日志: docker-compose logs -f")
|
||||
print(" 停止服务: docker-compose down")
|
||||
print(" 重启服务: docker-compose restart")
|
||||
print("\n⚠️ 注意:")
|
||||
print(" - 首次启动 Grafana 需要导入仪表板")
|
||||
print(" - 定期备份 Grafana 数据")
|
||||
print(" - 配置邮件告警需要修改 alertmanager.yml")
|
||||
|
||||
def run(self):
|
||||
"""运行配置流程"""
|
||||
print("=" * 60)
|
||||
print("🔧 KaMiXiTong 监控告警配置工具")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查 Docker
|
||||
if not self.check_docker():
|
||||
sys.exit(1)
|
||||
|
||||
# 创建配置
|
||||
self.create_docker_compose()
|
||||
self.create_alertmanager_config()
|
||||
self.create_grafana_provisioning()
|
||||
self.copy_monitoring_files()
|
||||
|
||||
# 询问是否启动
|
||||
print("\n是否启动监控服务? (y/N)")
|
||||
if input().lower() == 'y':
|
||||
if self.start_monitoring():
|
||||
time.sleep(5) # 等待服务启动
|
||||
self.show_access_info()
|
||||
else:
|
||||
print("❌ 启动失败,请检查错误信息")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n📋 手动启动命令:")
|
||||
print(f" cd {self.monitoring_dir}")
|
||||
print(" docker-compose up -d")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
setup = MonitoringSetup()
|
||||
setup.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user