607 lines
21 KiB
Python
607 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Cursor自动化服务 - 交互式环境配置脚本
|
||
用于配置和初始化新服务器环境、数据库和Redis
|
||
"""
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
import getpass
|
||
import time
|
||
import yaml
|
||
import socket
|
||
import random
|
||
import string
|
||
from typing import Dict, Any, Optional, Tuple
|
||
|
||
# 设置颜色输出
|
||
class Colors:
|
||
HEADER = '\033[95m'
|
||
BLUE = '\033[94m'
|
||
GREEN = '\033[92m'
|
||
YELLOW = '\033[93m'
|
||
RED = '\033[91m'
|
||
ENDC = '\033[0m'
|
||
BOLD = '\033[1m'
|
||
UNDERLINE = '\033[4m'
|
||
|
||
def print_color(text, color):
|
||
"""输出彩色文本"""
|
||
print(f"{color}{text}{Colors.ENDC}")
|
||
|
||
def print_title(title):
|
||
"""打印标题"""
|
||
width = 60
|
||
print("\n" + "=" * width)
|
||
print(f"{Colors.BOLD}{Colors.HEADER}{title.center(width)}{Colors.ENDC}")
|
||
print("=" * width + "\n")
|
||
|
||
def print_step(step, description):
|
||
"""打印步骤信息"""
|
||
print(f"{Colors.BOLD}{Colors.BLUE}[步骤 {step}]{Colors.ENDC} {description}")
|
||
|
||
def print_success(message):
|
||
"""打印成功信息"""
|
||
print(f"{Colors.GREEN}✓ {message}{Colors.ENDC}")
|
||
|
||
def print_warning(message):
|
||
"""打印警告信息"""
|
||
print(f"{Colors.YELLOW}⚠ {message}{Colors.ENDC}")
|
||
|
||
def print_error(message):
|
||
"""打印错误信息"""
|
||
print(f"{Colors.RED}✗ {message}{Colors.ENDC}")
|
||
|
||
# 检测虚拟环境
|
||
def is_in_virtualenv():
|
||
"""检查是否在虚拟环境中运行"""
|
||
return hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
|
||
|
||
def setup_virtualenv():
|
||
"""设置虚拟环境"""
|
||
print_title("虚拟环境检测")
|
||
|
||
if is_in_virtualenv():
|
||
print_success("已在虚拟环境中运行")
|
||
return True
|
||
|
||
print_warning("当前不在虚拟环境中运行")
|
||
create_venv = input("是否创建并使用虚拟环境? (推荐) (y/n, 默认: y): ").strip().lower()
|
||
|
||
if create_venv == 'n':
|
||
print_warning("跳过虚拟环境创建,将直接在系统Python环境中安装依赖")
|
||
return True
|
||
|
||
venv_path = input("请输入虚拟环境路径 (默认: ./venv): ").strip() or "./venv"
|
||
|
||
try:
|
||
# 检查venv模块
|
||
try:
|
||
import venv
|
||
has_venv = True
|
||
except ImportError:
|
||
has_venv = False
|
||
|
||
if not has_venv:
|
||
print_warning("Python venv模块不可用,尝试安装...")
|
||
if sys.platform.startswith('linux'):
|
||
print("在Linux上安装venv模块...")
|
||
try:
|
||
subprocess.check_call(["sudo", "apt", "install", "-y", "python3-venv", "python3-full"])
|
||
except:
|
||
try:
|
||
subprocess.check_call(["sudo", "yum", "install", "-y", "python3-venv"])
|
||
except:
|
||
print_error("无法自动安装venv模块,请手动安装后重试")
|
||
print_warning("Ubuntu/Debian: sudo apt install python3-venv python3-full")
|
||
print_warning("CentOS/RHEL: sudo yum install python3-venv")
|
||
return False
|
||
else:
|
||
print_error("请安装Python venv模块后重试")
|
||
return False
|
||
|
||
# 创建虚拟环境
|
||
print_step("创建", f"正在创建虚拟环境: {venv_path}")
|
||
if sys.platform.startswith('win'):
|
||
subprocess.check_call([sys.executable, "-m", "venv", venv_path])
|
||
else:
|
||
subprocess.check_call([sys.executable, "-m", "venv", venv_path])
|
||
|
||
# 计算激活脚本路径
|
||
if sys.platform.startswith('win'):
|
||
activate_script = os.path.join(venv_path, "Scripts", "activate.bat")
|
||
python_path = os.path.join(venv_path, "Scripts", "python.exe")
|
||
else:
|
||
activate_script = os.path.join(venv_path, "bin", "activate")
|
||
python_path = os.path.join(venv_path, "bin", "python")
|
||
|
||
print_success(f"虚拟环境创建成功: {venv_path}")
|
||
print_warning("请退出当前程序,然后执行以下命令激活虚拟环境并重新运行:")
|
||
|
||
if sys.platform.startswith('win'):
|
||
print(f"\n{Colors.BOLD}Windows:{Colors.ENDC}")
|
||
print(f" {activate_script}")
|
||
print(f" python setup_environment.py")
|
||
else:
|
||
print(f"\n{Colors.BOLD}Linux/Mac:{Colors.ENDC}")
|
||
print(f" source {activate_script}")
|
||
print(f" python setup_environment.py")
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
print_error(f"创建虚拟环境失败: {str(e)}")
|
||
return False
|
||
|
||
# 尝试导入必要的库,如果不存在则安装
|
||
required_packages = [
|
||
"loguru",
|
||
"pymysql",
|
||
"aiomysql",
|
||
"redis",
|
||
"pyyaml",
|
||
"cryptography" # 添加这一行
|
||
]
|
||
|
||
def check_and_install_packages():
|
||
"""检查并安装必要的Python包"""
|
||
print_step(1, "检查并安装必要的Python包")
|
||
|
||
for package in required_packages:
|
||
try:
|
||
__import__(package.split(">=")[0])
|
||
print_success(f"已安装: {package}")
|
||
except ImportError:
|
||
print_warning(f"未找到 {package},正在安装...")
|
||
try:
|
||
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
||
print_success(f"成功安装 {package}")
|
||
except subprocess.CalledProcessError as e:
|
||
print_error(f"安装 {package} 失败: {str(e)}")
|
||
sys.exit(1)
|
||
|
||
# 安装后重新导入必要的库
|
||
try:
|
||
global yaml
|
||
import yaml
|
||
from loguru import logger
|
||
print_success("所有必要的包已安装")
|
||
except ImportError as e:
|
||
print_error(f"无法导入必要的库: {str(e)}")
|
||
sys.exit(1)
|
||
|
||
def generate_password(length=16):
|
||
"""生成随机密码"""
|
||
chars = string.ascii_letters + string.digits + "!@#$%^&*()_+-="
|
||
return ''.join(random.choice(chars) for _ in range(length))
|
||
|
||
def get_default_hostname():
|
||
"""获取默认主机名"""
|
||
try:
|
||
return socket.gethostname()
|
||
except:
|
||
return "cursor-server"
|
||
|
||
def read_config():
|
||
"""读取现有配置文件"""
|
||
config_path = "config.yaml"
|
||
if os.path.exists(config_path):
|
||
try:
|
||
with open(config_path, 'r', encoding='utf-8') as f:
|
||
return yaml.safe_load(f)
|
||
except Exception as e:
|
||
print_warning(f"读取配置文件失败: {str(e)}")
|
||
return {}
|
||
|
||
def configure_server_settings(existing_config):
|
||
"""配置服务器设置"""
|
||
print_step(2, "配置服务器标识")
|
||
|
||
existing_hostname = None
|
||
if 'server_config' in existing_config and 'hostname' in existing_config['server_config']:
|
||
existing_hostname = existing_config['server_config']['hostname']
|
||
|
||
default_hostname = existing_hostname or get_default_hostname()
|
||
hostname = input(f"请输入服务器唯一标识 (默认: {default_hostname}): ").strip()
|
||
if not hostname:
|
||
hostname = default_hostname
|
||
|
||
if not 'server_config' in existing_config:
|
||
existing_config['server_config'] = {}
|
||
|
||
existing_config['server_config']['hostname'] = hostname
|
||
print_success(f"服务器标识已设置为: {hostname}")
|
||
return existing_config
|
||
|
||
def test_mysql_connection(host, port, user, password, database=None):
|
||
"""测试MySQL连接"""
|
||
import pymysql
|
||
try:
|
||
conn_args = {
|
||
'host': host,
|
||
'port': port,
|
||
'user': user,
|
||
'password': password,
|
||
'connect_timeout': 5
|
||
}
|
||
if database:
|
||
conn_args['database'] = database
|
||
|
||
conn = pymysql.connect(**conn_args)
|
||
conn.close()
|
||
return True, None
|
||
except Exception as e:
|
||
return False, str(e)
|
||
|
||
def configure_mysql_settings(existing_config):
|
||
"""配置MySQL设置"""
|
||
print_step(3, "配置MySQL数据库")
|
||
|
||
db_config = existing_config.get('database', {})
|
||
|
||
print("请输入MySQL配置信息:")
|
||
host = input(f"主机地址 (默认: {db_config.get('host', 'localhost')}): ").strip() or db_config.get('host', 'localhost')
|
||
port = input(f"端口 (默认: {db_config.get('port', 3306)}): ").strip() or db_config.get('port', 3306)
|
||
try:
|
||
port = int(port)
|
||
except ValueError:
|
||
print_warning("端口必须是数字,使用默认值3306")
|
||
port = 3306
|
||
|
||
# 获取root信息以创建数据库和用户
|
||
print("\n需要MySQL root权限来创建数据库和用户:")
|
||
root_user = input("MySQL root用户名 (默认: root): ").strip() or "root"
|
||
root_password = getpass.getpass("MySQL root密码: ")
|
||
|
||
# 测试root连接
|
||
success, error_msg = test_mysql_connection(host, port, root_user, root_password)
|
||
if not success:
|
||
print_error(f"无法连接到MySQL: {error_msg}")
|
||
print_warning("请确认MySQL服务已启动且用户名密码正确")
|
||
retry = input("是否重试MySQL配置? (y/n): ").lower()
|
||
if retry == 'y':
|
||
return configure_mysql_settings(existing_config)
|
||
else:
|
||
sys.exit(1)
|
||
|
||
print_success("MySQL连接测试成功")
|
||
|
||
# 应用数据库和用户设置
|
||
db_name = input(f"数据库名称 (默认: {db_config.get('database', 'auto_cursor_reg')}): ").strip() or db_config.get('database', 'auto_cursor_reg')
|
||
db_user = input(f"应用用户名 (默认: {db_config.get('username', 'auto_cursor_reg')}): ").strip() or db_config.get('username', 'auto_cursor_reg')
|
||
|
||
# 为应用用户生成密码
|
||
default_password = db_config.get('password')
|
||
if default_password:
|
||
use_existing = input(f"使用现有密码? (y/n, 默认: y): ").lower() != 'n'
|
||
if use_existing:
|
||
db_password = default_password
|
||
else:
|
||
suggested_password = generate_password(12)
|
||
db_password = input(f"应用用户密码 (建议: {suggested_password}): ").strip() or suggested_password
|
||
else:
|
||
suggested_password = generate_password(12)
|
||
db_password = input(f"应用用户密码 (建议: {suggested_password}): ").strip() or suggested_password
|
||
|
||
# 更新配置
|
||
existing_config['database'] = {
|
||
'host': host,
|
||
'port': port,
|
||
'username': db_user,
|
||
'password': db_password,
|
||
'database': db_name,
|
||
'pool_size': db_config.get('pool_size', 10),
|
||
'use_redis': db_config.get('use_redis', True)
|
||
}
|
||
|
||
# 保存root信息用于创建数据库和用户
|
||
root_info = {
|
||
'host': host,
|
||
'port': port,
|
||
'user': root_user,
|
||
'password': root_password
|
||
}
|
||
|
||
return existing_config, root_info
|
||
|
||
def test_redis_connection(host, port, password=None, db=0):
|
||
"""测试Redis连接"""
|
||
try:
|
||
import redis
|
||
client = redis.Redis(
|
||
host=host,
|
||
port=port,
|
||
password=password if password else None,
|
||
db=db,
|
||
socket_timeout=5
|
||
)
|
||
client.ping()
|
||
client.close()
|
||
return True, None
|
||
except Exception as e:
|
||
return False, str(e)
|
||
|
||
def configure_redis_settings(existing_config):
|
||
"""配置Redis设置"""
|
||
print_step(4, "配置Redis缓存")
|
||
|
||
db_config = existing_config.get('database', {})
|
||
redis_config = existing_config.get('redis', {})
|
||
|
||
use_redis = input(f"是否使用Redis缓存? (y/n, 默认: {'y' if db_config.get('use_redis', True) else 'n'}): ").lower()
|
||
if use_redis == 'n':
|
||
existing_config['database']['use_redis'] = False
|
||
print_warning("Redis缓存已禁用")
|
||
return existing_config, None
|
||
|
||
existing_config['database']['use_redis'] = True
|
||
|
||
print("请输入Redis配置信息:")
|
||
host = input(f"主机地址 (默认: {redis_config.get('host', '127.0.0.1')}): ").strip() or redis_config.get('host', '127.0.0.1')
|
||
port = input(f"端口 (默认: {redis_config.get('port', 6379)}): ").strip() or redis_config.get('port', 6379)
|
||
try:
|
||
port = int(port)
|
||
except ValueError:
|
||
print_warning("端口必须是数字,使用默认值6379")
|
||
port = 6379
|
||
|
||
password = input(f"密码 (默认: {'保持现有密码' if redis_config.get('password') else '无'}): ")
|
||
if not password and 'password' in redis_config:
|
||
password = redis_config['password']
|
||
|
||
db = input(f"数据库索引 (默认: {redis_config.get('db', 0)}): ").strip() or redis_config.get('db', 0)
|
||
try:
|
||
db = int(db)
|
||
except ValueError:
|
||
print_warning("数据库索引必须是数字,使用默认值0")
|
||
db = 0
|
||
|
||
# 测试Redis连接
|
||
success, error_msg = test_redis_connection(host, port, password, db)
|
||
if not success:
|
||
print_error(f"无法连接到Redis: {error_msg}")
|
||
print_warning("请确认Redis服务已启动且配置正确")
|
||
retry = input("是否重试Redis配置? (y/n): ").lower()
|
||
if retry == 'y':
|
||
return configure_redis_settings(existing_config)
|
||
elif retry == 'n':
|
||
use_anyway = input("是否仍然使用这些设置? (y/n): ").lower()
|
||
if use_anyway != 'y':
|
||
existing_config['database']['use_redis'] = False
|
||
print_warning("Redis缓存已禁用")
|
||
return existing_config, None
|
||
else:
|
||
print_success("Redis连接测试成功")
|
||
|
||
# 更新配置
|
||
existing_config['redis'] = {
|
||
'host': host,
|
||
'port': port,
|
||
'password': password,
|
||
'db': db
|
||
}
|
||
|
||
redis_info = {
|
||
'host': host,
|
||
'port': port,
|
||
'password': password,
|
||
'db': db
|
||
}
|
||
|
||
return existing_config, redis_info
|
||
|
||
def setup_mysql_database(config, root_info):
|
||
"""设置MySQL数据库和用户"""
|
||
print_step(5, "创建MySQL数据库和用户")
|
||
|
||
db_config = config['database']
|
||
db_name = db_config['database']
|
||
db_user = db_config['username']
|
||
db_password = db_config['password']
|
||
|
||
import pymysql
|
||
try:
|
||
# 连接MySQL
|
||
conn = pymysql.connect(
|
||
host=root_info['host'],
|
||
port=root_info['port'],
|
||
user=root_info['user'],
|
||
password=root_info['password']
|
||
)
|
||
|
||
with conn.cursor() as cursor:
|
||
# 创建数据库
|
||
print(f"创建数据库 {db_name}...")
|
||
cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{db_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
|
||
|
||
# 创建用户并授权
|
||
print(f"创建用户 {db_user}...")
|
||
|
||
# 检查用户是否存在
|
||
cursor.execute(f"SELECT 1 FROM mysql.user WHERE user = %s AND host = %s", (db_user, '%'))
|
||
user_exists = cursor.fetchone()
|
||
|
||
if user_exists:
|
||
print_warning(f"用户 {db_user} 已存在,更新密码...")
|
||
cursor.execute(f"ALTER USER '{db_user}'@'%' IDENTIFIED BY %s", (db_password,))
|
||
else:
|
||
cursor.execute(f"CREATE USER '{db_user}'@'%' IDENTIFIED BY %s", (db_password,))
|
||
|
||
# 授权
|
||
cursor.execute(f"GRANT ALL PRIVILEGES ON `{db_name}`.* TO '{db_user}'@'%'")
|
||
cursor.execute("FLUSH PRIVILEGES")
|
||
|
||
conn.close()
|
||
print_success(f"数据库 {db_name} 和用户 {db_user} 创建成功")
|
||
return True
|
||
except Exception as e:
|
||
print_error(f"设置MySQL数据库失败: {str(e)}")
|
||
return False
|
||
|
||
def create_database_tables(config):
|
||
"""创建数据库表结构"""
|
||
print_step(6, "创建数据库表结构")
|
||
|
||
db_config = config['database']
|
||
|
||
import pymysql
|
||
try:
|
||
# 连接MySQL
|
||
conn = pymysql.connect(
|
||
host=db_config['host'],
|
||
port=db_config['port'],
|
||
user=db_config['username'],
|
||
password=db_config['password'],
|
||
database=db_config['database']
|
||
)
|
||
|
||
with conn.cursor() as cursor:
|
||
# 创建email_accounts表
|
||
print("创建email_accounts表...")
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS email_accounts (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
email VARCHAR(255) UNIQUE NOT NULL,
|
||
password VARCHAR(255) NOT NULL,
|
||
client_id VARCHAR(255) NOT NULL,
|
||
refresh_token TEXT NOT NULL,
|
||
in_use BOOLEAN DEFAULT 0,
|
||
cursor_password VARCHAR(255),
|
||
cursor_cookie TEXT,
|
||
cursor_token TEXT,
|
||
sold BOOLEAN DEFAULT 0,
|
||
status VARCHAR(20) DEFAULT 'pending',
|
||
extracted BOOLEAN DEFAULT 0,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
INDEX idx_status_inuse_sold (status, in_use, sold),
|
||
INDEX idx_extracted (extracted, status, sold)
|
||
)
|
||
''')
|
||
|
||
# 创建system_settings表
|
||
print("创建system_settings表...")
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS system_settings (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
key_name VARCHAR(50) UNIQUE NOT NULL,
|
||
value TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
# 初始化system_settings
|
||
cursor.execute(
|
||
"INSERT INTO system_settings (key_name, value) VALUES ('auto_service_enabled', '0') "
|
||
"ON DUPLICATE KEY UPDATE value = value"
|
||
)
|
||
|
||
conn.close()
|
||
print_success("数据库表结构创建成功")
|
||
return True
|
||
except Exception as e:
|
||
print_error(f"创建数据库表失败: {str(e)}")
|
||
return False
|
||
|
||
def update_config_file(config):
|
||
"""更新配置文件"""
|
||
print_step(7, "更新配置文件")
|
||
|
||
config_path = "config.yaml"
|
||
backup_path = f"config.yaml.bak.{int(time.time())}"
|
||
|
||
# 备份现有配置
|
||
if os.path.exists(config_path):
|
||
try:
|
||
with open(config_path, 'r', encoding='utf-8') as src:
|
||
with open(backup_path, 'w', encoding='utf-8') as dst:
|
||
dst.write(src.read())
|
||
print_success(f"现有配置已备份为 {backup_path}")
|
||
except Exception as e:
|
||
print_warning(f"备份配置文件失败: {str(e)}")
|
||
|
||
# 写入新配置
|
||
try:
|
||
with open(config_path, 'w', encoding='utf-8') as f:
|
||
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
||
print_success(f"配置已更新至 {config_path}")
|
||
return True
|
||
except Exception as e:
|
||
print_error(f"更新配置文件失败: {str(e)}")
|
||
return False
|
||
|
||
def finalize_setup():
|
||
"""完成设置并提供指导"""
|
||
print_title("设置完成")
|
||
|
||
print(f"{Colors.GREEN}Cursor自动化服务环境配置已完成!{Colors.ENDC}")
|
||
print("\n接下来您可以:")
|
||
|
||
print(f"{Colors.BOLD}1. 导入邮箱账号:{Colors.ENDC}")
|
||
print(" python import_emails.py")
|
||
|
||
print(f"\n{Colors.BOLD}2. 启动服务:{Colors.ENDC}")
|
||
print(" python start.py")
|
||
print(" 或")
|
||
print(" python auto_cursor_service.py")
|
||
|
||
print(f"\n{Colors.BOLD}3. 设置为系统服务:{Colors.ENDC}")
|
||
print(" 请参考README文件中的系统服务设置说明")
|
||
|
||
print(f"\n{Colors.BOLD}如需帮助:{Colors.ENDC}")
|
||
print(" 查看各README文件获取详细使用说明")
|
||
|
||
print("\n" + "=" * 60)
|
||
|
||
def main():
|
||
"""主函数"""
|
||
print_title("Cursor自动化服务环境配置")
|
||
|
||
# 检查Python版本
|
||
if sys.version_info < (3, 7):
|
||
print_error("需要Python 3.7或更高版本")
|
||
sys.exit(1)
|
||
|
||
# 检查并安装依赖
|
||
check_and_install_packages()
|
||
|
||
# 读取现有配置
|
||
existing_config = read_config()
|
||
|
||
# 配置服务器设置
|
||
config = configure_server_settings(existing_config)
|
||
|
||
# 配置MySQL
|
||
config, root_info = configure_mysql_settings(config)
|
||
|
||
# 配置Redis
|
||
config, redis_info = configure_redis_settings(config)
|
||
|
||
# 设置MySQL数据库和用户
|
||
if not setup_mysql_database(config, root_info):
|
||
retry = input("MySQL设置失败,是否继续? (y/n): ").lower()
|
||
if retry != 'y':
|
||
sys.exit(1)
|
||
|
||
# 创建数据库表
|
||
if not create_database_tables(config):
|
||
retry = input("创建表失败,是否继续? (y/n): ").lower()
|
||
if retry != 'y':
|
||
sys.exit(1)
|
||
|
||
# 更新配置文件
|
||
if not update_config_file(config):
|
||
retry = input("更新配置文件失败,是否继续? (y/n): ").lower()
|
||
if retry != 'y':
|
||
sys.exit(1)
|
||
|
||
# 完成设置
|
||
finalize_setup()
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
main()
|
||
except KeyboardInterrupt:
|
||
print("\n\n设置已取消")
|
||
sys.exit(1) |