204 lines
6.6 KiB
Plaintext
204 lines
6.6 KiB
Plaintext
# -*- coding: utf-8 -*-
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
import os
|
||
|
||
# 服务器配置
|
||
SERVER_HOST = "45.249.208.118"
|
||
SERVER_USER = "root"
|
||
SERVER_PASSWORD = "lxvhNYFZ4160"
|
||
SERVER_PROJECT_DIR = "/root/youyidian-3d"
|
||
LOCAL_PACKAGE = "project-deploy.tar.gz"
|
||
|
||
def print_step(step, message):
|
||
print(f"\n{'='*60}")
|
||
print(f"步骤 {step}: {message}")
|
||
print('='*60)
|
||
|
||
def run_ssh_command(command):
|
||
"""执行SSH命令"""
|
||
full_cmd = f'ssh {SERVER_USER}@{SERVER_HOST} "{command}"'
|
||
print(f"\n执行命令: {command}\n")
|
||
|
||
# 使用 plink 或 ssh (需要手动输入密码)
|
||
result = subprocess.run(full_cmd, shell=True, capture_output=False, text=True)
|
||
return result.returncode == 0
|
||
|
||
def main():
|
||
print("\n" + "="*60)
|
||
print(" FLNXI素材库系统 - 自动部署脚本")
|
||
print(f" 目标服务器: {SERVER_HOST}")
|
||
print("="*60)
|
||
|
||
# 检查部署包是否存在
|
||
if not os.path.exists(LOCAL_PACKAGE):
|
||
print(f"\n[ERROR] Cannot find: {LOCAL_PACKAGE}")
|
||
print("Please create package first")
|
||
return False
|
||
|
||
file_size = os.path.getsize(LOCAL_PACKAGE) / 1024 / 1024
|
||
print(f"\n[OK] Found package: {LOCAL_PACKAGE} ({file_size:.2f} MB)")
|
||
|
||
# 步骤1: 上传文件
|
||
print_step(1, "上传部署包到服务器")
|
||
print(f"正在上传 {LOCAL_PACKAGE} 到 {SERVER_HOST}:/root/")
|
||
print(f"密码: {SERVER_PASSWORD}")
|
||
print("\n请在提示时输入密码...")
|
||
time.sleep(2)
|
||
|
||
upload_cmd = f"scp {LOCAL_PACKAGE} {SERVER_USER}@{SERVER_HOST}:/root/"
|
||
result = subprocess.run(upload_cmd, shell=True)
|
||
|
||
if result.returncode != 0:
|
||
print("\n[ERROR] Upload failed!")
|
||
return False
|
||
|
||
print("\n[OK] Upload success!")
|
||
|
||
# 步骤2: 创建项目目录
|
||
print_step(2, "创建项目目录")
|
||
commands = [
|
||
f"mkdir -p {SERVER_PROJECT_DIR}",
|
||
f"cd {SERVER_PROJECT_DIR} && pwd"
|
||
]
|
||
|
||
for cmd in commands:
|
||
print(f"执行: {cmd}")
|
||
|
||
result = subprocess.run(
|
||
f'ssh {SERVER_USER}@{SERVER_HOST} "{"; ".join(commands)}"',
|
||
shell=True
|
||
)
|
||
|
||
# 步骤3: 备份现有文件
|
||
print_step(3, "备份现有上传文件")
|
||
backup_cmd = f"""
|
||
cd {SERVER_PROJECT_DIR} && \
|
||
if [ -d uploads ]; then \
|
||
mkdir -p backups && \
|
||
echo '正在备份uploads目录...' && \
|
||
cp -r uploads backups/uploads-$(date +%Y%m%d-%H%M%S) 2>/dev/null || true && \
|
||
echo '[OK] Backup complete'; \
|
||
else \
|
||
echo '[WARN] No uploads directory found, skip backup'; \
|
||
fi
|
||
"""
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{backup_cmd}"', shell=True)
|
||
|
||
# 步骤4: 解压文件
|
||
print_step(4, "解压新版本")
|
||
extract_cmd = f"""
|
||
cd {SERVER_PROJECT_DIR} && \
|
||
echo '正在解压项目文件...' && \
|
||
tar -xzf /root/{LOCAL_PACKAGE} && \
|
||
rm /root/{LOCAL_PACKAGE} && \
|
||
echo '[OK] Extract complete' && \
|
||
ls -lh
|
||
"""
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{extract_cmd}"', shell=True)
|
||
|
||
# 步骤5: 恢复上传目录
|
||
print_step(5, "恢复上传目录")
|
||
restore_cmd = f"""
|
||
cd {SERVER_PROJECT_DIR} && \
|
||
if [ -d backups ]; then \
|
||
latest=$(ls -td backups/uploads-* 2>/dev/null | head -1); \
|
||
if [ -n "$latest" ]; then \
|
||
echo "从 $latest 恢复上传文件..."; \
|
||
mkdir -p uploads; \
|
||
cp -r "$latest"/* uploads/ 2>/dev/null || true; \
|
||
echo '[OK] Restore complete'; \
|
||
fi; \
|
||
fi && \
|
||
mkdir -p uploads && \
|
||
chmod -R 777 uploads && \
|
||
echo '[OK] Permissions set'
|
||
"""
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{restore_cmd}"', shell=True)
|
||
|
||
# 步骤6: 查看当前Docker状态
|
||
print_step(6, "检查Docker状态")
|
||
subprocess.run(
|
||
f'ssh {SERVER_USER}@{SERVER_HOST} "docker ps -a"',
|
||
shell=True
|
||
)
|
||
|
||
# 步骤7: 停止旧容器
|
||
print_step(7, "停止旧容器")
|
||
stop_cmd = f"""
|
||
cd {SERVER_PROJECT_DIR} && \
|
||
echo '正在停止旧容器...' && \
|
||
docker compose down 2>/dev/null || docker-compose down 2>/dev/null || true && \
|
||
echo '[OK] Old containers stopped'
|
||
"""
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{stop_cmd}"', shell=True)
|
||
|
||
# 步骤8: 构建并启动新容器
|
||
print_step(8, "构建并启动新容器")
|
||
print("[WARN] This step may take several minutes, please wait...")
|
||
build_cmd = f"""
|
||
cd {SERVER_PROJECT_DIR} && \
|
||
echo '正在构建镜像...' && \
|
||
docker compose up -d --build --force-recreate || docker-compose up -d --build --force-recreate && \
|
||
echo '[OK] Containers started'
|
||
"""
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{build_cmd}"', shell=True)
|
||
|
||
# 步骤9: 等待容器启动
|
||
print_step(9, "等待容器完全启动")
|
||
print("等待20秒...")
|
||
for i in range(20, 0, -1):
|
||
print(f"\r剩余 {i} 秒...", end="", flush=True)
|
||
time.sleep(1)
|
||
print("\n")
|
||
|
||
# 步骤10: 数据库迁移
|
||
print_step(10, "执行数据库迁移")
|
||
migrate_cmd = f"""
|
||
cd {SERVER_PROJECT_DIR} && \
|
||
echo '正在执行数据库迁移...' && \
|
||
docker compose exec -T app npx prisma db push --accept-data-loss --schema=/app/prisma/schema.prisma 2>/dev/null || \
|
||
docker-compose exec -T app npx prisma db push --accept-data-loss --schema=/app/prisma/schema.prisma 2>/dev/null || \
|
||
echo '[WARN] Database migration failed, may need manual execution'
|
||
"""
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{migrate_cmd}"', shell=True)
|
||
|
||
# 步骤11: 查看最终状态
|
||
print_step(11, "查看部署结果")
|
||
print("\n容器运行状态:")
|
||
subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "docker ps"', shell=True)
|
||
|
||
print("\n\n应用日志(最后30行):")
|
||
subprocess.run(
|
||
f'ssh {SERVER_USER}@{SERVER_HOST} "cd {SERVER_PROJECT_DIR} && (docker compose logs --tail=30 app 2>/dev/null || docker-compose logs --tail=30 app)"',
|
||
shell=True
|
||
)
|
||
|
||
# 完成
|
||
print("\n" + "="*60)
|
||
print(" [OK] Deployment Complete!")
|
||
print("="*60)
|
||
print(f"\n访问地址: http://{SERVER_HOST}:端口号")
|
||
print("(请根据docker-compose.yml中的端口配置访问)")
|
||
print("\n常用命令:")
|
||
print(f" 查看日志: ssh {SERVER_USER}@{SERVER_HOST} 'cd {SERVER_PROJECT_DIR} && docker compose logs -f app'")
|
||
print(f" 重启服务: ssh {SERVER_USER}@{SERVER_HOST} 'cd {SERVER_PROJECT_DIR} && docker compose restart'")
|
||
print(f" 停止服务: ssh {SERVER_USER}@{SERVER_HOST} 'cd {SERVER_PROJECT_DIR} && docker compose down'")
|
||
print("="*60)
|
||
|
||
return True
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
success = main()
|
||
sys.exit(0 if success else 1)
|
||
except KeyboardInterrupt:
|
||
print("\n\n[INFO] User interrupted")
|
||
sys.exit(1)
|
||
except Exception as e:
|
||
print(f"\n\n[ERROR] Error occurred: {e}")
|
||
sys.exit(1)
|
||
|
||
|