第一次提交
This commit is contained in:
340
deploy.py
Normal file
340
deploy.py
Normal file
@@ -0,0 +1,340 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user