Files
auto_cursor/setup_environment.py
2025-04-02 10:45:19 +08:00

607 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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)