#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ KaMiXiTong 部署脚本 用于生产环境的一键部署 """ import os import sys import subprocess import argparse from pathlib import Path def check_python_version(): """检查Python版本""" if sys.version_info < (3, 6): print("❌ 错误: 需要Python 3.6或更高版本") print(f"当前版本: {sys.version}") sys.exit(1) print(f"✅ Python版本检查通过: {sys.version}") def create_directories(): """创建必要的目录""" directories = [ 'logs', 'static/uploads', 'static/css', 'static/js' ] for directory in directories: Path(directory).mkdir(parents=True, exist_ok=True) print("✅ 目录结构创建完成") def check_env_file(): """检查环境配置文件""" env_file = Path('.env') if not env_file.exists(): print("⚠️ 未找到 .env 文件,正在创建默认配置...") try: with open('.env.example', 'r', encoding='utf-8') as src: with open('.env', 'w', encoding='utf-8') as dst: dst.write(src.read()) print("✅ 已创建默认 .env 文件") except FileNotFoundError: print("❌ 未找到 .env.example 文件") return False else: print("✅ 环境配置文件已存在") return True def install_dependencies(): """安装依赖""" print("📦 正在安装依赖包...") try: subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt']) # 检查并安装生产环境依赖 try: subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'gunicorn']) print("✅ Gunicorn安装完成") except subprocess.CalledProcessError: print("⚠️ Gunicorn安装失败") print("✅ 依赖安装完成") return True except subprocess.CalledProcessError: print("❌ 依赖安装失败") return False def init_database(): """初始化数据库""" print("🗄️ 正在初始化数据库...") # 检查使用哪种数据库 env_file = Path('.env') if env_file.exists(): with open(env_file, 'r', encoding='utf-8') as f: content = f.read() if 'DATABASE_URL=sqlite' in content: init_script = 'init_db_sqlite.py' else: init_script = 'setup_mysql.py' else: # 默认使用SQLite init_script = 'init_db_sqlite.py' try: if os.path.exists(init_script): subprocess.check_call([sys.executable, init_script]) print("✅ 数据库初始化完成") return True else: print(f"⚠️ 数据库初始化脚本 {init_script} 不存在") return False except subprocess.CalledProcessError as e: print(f"❌ 数据库初始化失败: {e}") return False def setup_systemd_service(): """配置Systemd服务""" print("⚙️ 正在配置Systemd服务...") # 获取当前路径 app_path = os.path.abspath('.') venv_path = os.path.join(app_path, 'venv', 'bin', 'gunicorn') # 如果没有虚拟环境,使用系统gunicorn if not os.path.exists(venv_path): venv_path = 'gunicorn' # 从环境变量获取域名 frontend_domain = os.environ.get('FRONTEND_DOMAIN') or os.environ.get('DOMAIN_NAME') or os.environ.get('SERVER_NAME') or 'your-domain.com' # 确保域名不包含协议部分 if frontend_domain.startswith(('http://', 'https://')): frontend_domain = frontend_domain.split('://', 1)[1] service_content = f"""[Unit] Description=KaMiXiTong Service After=network.target [Service] User=www-data Group=www-data WorkingDirectory={app_path} Environment="FLASK_ENV=production" Environment="DOMAIN_NAME={frontend_domain}" ExecStart={venv_path} -w 4 -b 0.0.0.0:5088 run:app Restart=always RestartSec=10 [Install] WantedBy=multi-user.target """ try: # 写入服务文件 with open('/tmp/kamaxitong.service', 'w') as f: f.write(service_content) print("✅ Systemd服务配置文件已生成") print("💡 请使用以下命令安装服务:") print(" sudo cp /tmp/kamaxitong.service /etc/systemd/system/") print(" sudo systemctl daemon-reload") print(" sudo systemctl enable kamaxitong.service") print(" sudo systemctl start kamaxitong.service") return True except Exception as e: print(f"❌ Systemd服务配置失败: {e}") return False def setup_nginx_config(): """配置Nginx""" print("⚙️ 正在生成Nginx配置...") # 从环境变量获取域名 frontend_domain = os.environ.get('FRONTEND_DOMAIN') or os.environ.get('DOMAIN_NAME') or os.environ.get('SERVER_NAME') or '' if frontend_domain: # 确保域名不包含协议部分 if frontend_domain.startswith(('http://', 'https://')): server_name = frontend_domain.split('://', 1)[1] else: server_name = frontend_domain else: server_name = 'your-domain.com' nginx_config = f"""server {{ listen 80; server_name {server_name}; server_tokens off; # ACME挑战目录(用于Let's Encrypt证书) location /.well-known/acme-challenge/ {{ root /var/www/certbot; }} # 重定向到HTTPS location / {{ return 301 https://$server_name$request_uri; }} }} server {{ listen 443 ssl http2; server_name {server_name}; server_tokens off; # SSL证书配置(请替换为实际路径) # ssl_certificate /etc/letsencrypt/live/{server_name}/fullchain.pem; # ssl_certificate_key /etc/letsencrypt/live/{server_name}/privkey.pem; # SSL安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 安全头配置 add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Gzip压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied expired no-cache no-store private; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss; # 代理到Flask应用 location / {{ proxy_pass http://127.0.0.1:5088; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $server_name; proxy_redirect off; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; }} # 静态文件直接由Nginx处理 location /static/ {{ alias /var/www/kamaxitong/static/; expires 1y; add_header Cache-Control "public, immutable"; add_header Access-Control-Allow-Origin "*"; }} # 上传文件处理 location /static/uploads/ {{ alias /var/www/kamaxitong/static/uploads/; expires 1d; add_header Cache-Control "public"; }} # ACME挑战目录(用于Let's Encrypt证书) location /.well-known/acme-challenge/ {{ root /var/www/certbot; }} # 日志配置 access_log /var/log/nginx/kamaxitong.access.log; error_log /var/log/nginx/kamaxitong.error.log; # 错误页面 error_page 500 502 503 504 /50x.html; location = /50x.html {{ root /usr/share/nginx/html; }} }}""" try: # 写入配置文件 with open('/tmp/kamaxitong_nginx.conf', 'w') as f: f.write(nginx_config) print("✅ Nginx配置文件已生成") print("💡 请使用以下命令安装配置:") print(" sudo cp /tmp/kamaxitong_nginx.conf /etc/nginx/sites-available/kamaxitong") print(" sudo ln -s /etc/nginx/sites-available/kamaxitong /etc/nginx/sites-enabled/") print(" sudo nginx -t") print(" sudo systemctl restart nginx") return True except Exception as e: print(f"❌ Nginx配置生成失败: {e}") return False def main(): """主函数""" parser = argparse.ArgumentParser(description='KaMiXiTong 部署脚本') parser.add_argument('--skip-install', action='store_true', help='跳过依赖安装') parser.add_argument('--skip-db', action='store_true', help='跳过数据库初始化') parser.add_argument('--setup-service', action='store_true', help='配置Systemd服务') parser.add_argument('--setup-nginx', action='store_true', help='生成Nginx配置') args = parser.parse_args() print("=" * 50) print(" KaMiXiTong 生产环境部署脚本") print("=" * 50) # 检查Python版本 check_python_version() # 创建目录 create_directories() # 检查环境配置 if not check_env_file(): sys.exit(1) # 安装依赖 if not args.skip_install: if not install_dependencies(): sys.exit(1) else: print("⏭️ 跳过依赖安装") # 初始化数据库 if not args.skip_db: if not init_database(): sys.exit(1) else: print("⏭️ 跳过数据库初始化") # 配置Systemd服务 if args.setup_service: setup_systemd_service() # 配置Nginx if args.setup_nginx: setup_nginx_config() print("\n" + "=" * 50) print("✅ 部署准备完成!") print("=" * 50) if args.setup_service: print("💡 已生成Systemd服务配置文件,请按提示安装") if args.setup_nginx: print("💡 已生成Nginx配置文件,请按提示安装") print("\n📌 下一步操作:") print("1. 编辑 .env 文件,配置数据库和其他参数") print("2. 配置SSL证书(推荐)") print("3. 启动服务") print("4. 访问你的域名") if __name__ == '__main__': main()