Files
host_message/deploy.sh
2026-02-13 12:33:43 +08:00

649 lines
18 KiB
Bash
Raw 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.
#!/bin/bash
# ==========================================================================
# 自动部署脚本 - host_message 项目
# 功能:将本地代码打包上传到远程服务器并自动部署
# ==========================================================================
# 配置信息
REMOTE_HOST="6.6.6.86"
REMOTE_USER="ubuntu"
REMOTE_PASS="qweasdzxc1"
REMOTE_DIR="/home/ubuntu/host_message"
LOCAL_DIR="/Users/jeremygan/Desktop/TangledupAI/host_message-main"
APP_PORT=8888
ZIP_NAME="host_message_$(date +%Y%m%d_%H%M%S).zip"
TEMP_ZIP="/tmp/$ZIP_NAME"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查依赖
check_dependencies() {
log_step "检查系统依赖..."
# 检查 sshpass
if ! command -v sshpass &> /dev/null; then
log_error "sshpass 未安装,正在安装..."
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
if command -v brew &> /dev/null; then
brew install sshpass
else
log_error "请先安装 Homebrew然后运行: brew install sshpass"
exit 1
fi
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
sudo apt-get update && sudo apt-get install -y sshpass
else
log_error "不支持的操作系统,请手动安装 sshpass"
exit 1
fi
fi
# 检查 zip
if ! command -v zip &> /dev/null; then
log_error "zip 命令未找到,请安装 zip 工具"
exit 1
fi
log_info "依赖检查完成"
}
# 创建代码包
create_package() {
log_step "创建代码包..."
# 切换到项目目录
cd "$LOCAL_DIR" || {
log_error "无法进入项目目录: $LOCAL_DIR"
exit 1
}
# 删除旧的临时文件
rm -f "$TEMP_ZIP"
# 创建排除文件列表
EXCLUDE_PATTERNS=(
"*.pyc"
"__pycache__/*"
".git/*"
".DS_Store"
"logs/*"
"*.log"
".env"
"venv/*"
".venv/*"
"node_modules/*"
"uploads/*"
"chat_history/*"
)
# 构建排除参数
EXCLUDE_ARGS=""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
EXCLUDE_ARGS="$EXCLUDE_ARGS -x $pattern"
done
# 创建zip包
log_info "正在打包文件..."
eval "zip -r \"$TEMP_ZIP\" . $EXCLUDE_ARGS"
if [ $? -eq 0 ]; then
log_info "代码包创建成功: $TEMP_ZIP"
log_info "包大小: $(du -h "$TEMP_ZIP" | cut -f1)"
else
log_error "代码包创建失败"
exit 1
fi
}
# 测试SSH连接
test_ssh_connection() {
log_step "测试SSH连接..."
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "echo 'SSH连接测试成功'" 2>/dev/null
if [ $? -eq 0 ]; then
log_info "SSH连接测试成功"
else
log_error "SSH连接失败请检查服务器地址、用户名和密码"
exit 1
fi
}
# 上传代码包
upload_package() {
log_step "上传代码包到服务器..."
# 使用scp上传文件
sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no "$TEMP_ZIP" "$REMOTE_USER@$REMOTE_HOST:/tmp/"
if [ $? -eq 0 ]; then
log_info "代码包上传成功"
else
log_error "代码包上传失败"
exit 1
fi
}
# 远程部署
remote_deploy() {
log_step "在远程服务器上执行部署..."
# 创建远程部署脚本
REMOTE_SCRIPT=$(cat <<EOF
#!/bin/bash
# 设置颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() {
echo -e "\${GREEN}[远程INFO]\${NC} \$(date '+%Y-%m-%d %H:%M:%S') - \$1"
}
log_error() {
echo -e "\${RED}[远程ERROR]\${NC} \$(date '+%Y-%m-%d %H:%M:%S') - \$1"
}
log_warn() {
echo -e "\${YELLOW}[远程WARN]\${NC} \$(date '+%Y-%m-%d %H:%M:%S') - \$1"
}
# 检查端口占用并杀死进程
check_and_kill_port() {
log_info \"检查端口 $APP_PORT 是否被占用...\"
# 查找占用端口的进程
PID=\$(lsof -ti:$APP_PORT 2>/dev/null)
if [ ! -z \"\$PID\" ]; then
log_warn \"发现端口 $APP_PORT 被进程 \$PID 占用,正在终止...\"
kill -TERM \$PID
sleep 3
# 检查进程是否还存在
if kill -0 \$PID 2>/dev/null; then
log_warn \"进程仍然存在,强制终止...\"
kill -KILL \$PID
sleep 2
fi
# 再次检查
NEW_PID=\$(lsof -ti:$APP_PORT 2>/dev/null)
if [ ! -z \"\$NEW_PID\" ]; then
log_error \"无法终止占用端口 $APP_PORT 的进程\"
exit 1
else
log_info \"端口 $APP_PORT 已释放\"
fi
else
log_info \"端口 $APP_PORT 未被占用\"
fi
}
# 检查环境是否已安装
check_environment() {
log_info \"检查服务器环境...\"
# 检查Python3
if ! python3 --version &> /dev/null; then
log_info \"Python3 未安装,需要安装...\"
return 1
fi
# 检查是否存在虚拟环境
if [ -d \"/home/ubuntu/host_message_venv\" ]; then
log_info \"发现已存在的虚拟环境\"
return 0
fi
# 检查supervisor
if ! command -v supervisorctl &> /dev/null; then
log_info \"Supervisor 未安装,需要安装...\"
return 1
fi
log_info \"基础环境检查通过,但需要创建虚拟环境\"
return 2
}
# 安装系统依赖
install_dependencies() {
log_info \"安装系统依赖...\"
# 更新包列表
sudo apt-get update -qq
# 安装Python3、pip和相关开发工具
log_info \"安装 Python3 和相关工具...\"
sudo apt-get install -y python3 python3-pip python3-venv python3-dev build-essential python3-full
# 安装supervisor用于进程保活
if ! command -v supervisorctl &> /dev/null; then
log_info \"安装 supervisor...\"
sudo apt-get install -y supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor
fi
# 安装其他必要工具
sudo apt-get install -y curl wget unzip lsof net-tools
log_info \"系统依赖安装完成\"
}
# 创建和管理虚拟环境
setup_virtual_environment() {
local venv_path=\"/home/ubuntu/host_message_venv\"
log_info \"设置Python虚拟环境...\"
# 如果虚拟环境已存在,询问是否重新创建
if [ -d \"\$venv_path\" ]; then
log_info \"虚拟环境已存在,删除旧环境并重新创建...\"
rm -rf \"\$venv_path\"
fi
# 创建虚拟环境
log_info \"创建新的虚拟环境...\"
python3 -m venv \"\$venv_path\"
if [ \$? -eq 0 ]; then
log_info \"虚拟环境创建成功: \$venv_path\"
else
log_error \"虚拟环境创建失败\"
exit 1
fi
# 激活虚拟环境并升级pip
log_info \"激活虚拟环境并升级pip...\"
source \"\$venv_path/bin/activate\"
pip install --upgrade pip setuptools wheel
log_info \"虚拟环境设置完成\"
}
# 主要部署逻辑
main_deploy() {
# 检查并终止端口进程
check_and_kill_port
# 检查环境状态
check_environment
env_status=\$?
case \$env_status in
0)
log_info "环境已完整安装,跳过依赖安装"
;;
1)
log_info "需要安装基础环境"
install_dependencies
setup_virtual_environment
;;
2)
log_info "基础环境已安装,只需创建虚拟环境"
setup_virtual_environment
;;
esac
# 备份旧代码(如果存在)
if [ -d \"$REMOTE_DIR\" ]; then
log_info \"备份现有代码...\"
sudo mv \"$REMOTE_DIR\" \"${REMOTE_DIR}_backup_\$(date +%Y%m%d_%H%M%S)\" 2>/dev/null || true
fi
# 创建部署目录
log_info \"创建部署目录...\"
sudo mkdir -p \"$REMOTE_DIR\"
sudo chown \$USER:\$USER \"$REMOTE_DIR\"
# 解压代码包
log_info \"解压代码包...\"
cd \"$REMOTE_DIR\"
unzip -q \"/tmp/$ZIP_NAME\"
if [ \$? -eq 0 ]; then
log_info \"代码解压成功\"
else
log_error \"代码解压失败\"
exit 1
fi
# 创建必要的目录
mkdir -p logs uploads chat_history database
# 激活虚拟环境并安装Python依赖
log_info \"激活虚拟环境并安装Python依赖...\"
source \"/home/ubuntu/host_message_venv/bin/activate\"
if [ -f \"requirements.txt\" ]; then
log_info \"发现 requirements.txt安装依赖包...\"
pip install -r requirements.txt
if [ \$? -eq 0 ]; then
log_info \"Python依赖安装成功\"
else
log_error \"Python依赖安装失败\"
# 尝试单独安装每个包
log_info \"尝试单独安装依赖包...\"
while IFS= read -r package; do
if [[ ! \$package =~ ^[[:space:]]*# ]] && [[ ! -z \$package ]]; then
log_info \"安装: \$package\"
pip install \$package || log_warn \"安装 \$package 失败\"
fi
done < requirements.txt
fi
else
log_warn \"未找到 requirements.txt 文件\"
fi
# 验证关键依赖
log_info \"验证Python依赖...\"
python -c \"import fastapi, uvicorn; print('关键依赖验证成功')\" || {
log_error \"关键依赖验证失败,手动安装...\"
pip install fastapi uvicorn
}
# 检查main.py文件
if [ ! -f \"main.py\" ]; then
log_error \"main.py 文件不存在!\"
exit 1
fi
# 测试Python应用是否可以启动
log_info \"测试Python应用...\"
source \"/home/ubuntu/host_message_venv/bin/activate\"
timeout 10 python -c \"
import sys
sys.path.insert(0, '.')
try:
import main
print('应用模块导入成功')
except Exception as e:
print(f'应用模块导入失败: {e}')
sys.exit(1)
\" || log_warn \"应用模块测试失败,但继续部署...\""
# 创建启动脚本
log_info \"创建应用启动脚本...\"
cat > start_app.sh <<SCRIPT_EOF
#!/bin/bash
set -e
# 进入应用目录
cd \"$REMOTE_DIR\"
# 激活虚拟环境
source \"/home/ubuntu/host_message_venv/bin/activate\"
# 设置环境变量
export PYTHONPATH=\"$REMOTE_DIR:\\\$PYTHONPATH\"
# 记录启动信息
echo \"[\$(date)] 应用启动开始...\"
echo \"当前目录: \$(pwd)\"
echo \"Python版本: \$(python --version)\"
echo \"虚拟环境: \$VIRTUAL_ENV\"
# 检查必要文件
if [ ! -f \"main.py\" ]; then
echo \"错误: main.py 文件不存在\"
exit 1
fi
# 启动应用
echo \"[\$(date)] 启动Python应用...\"
python main.py
SCRIPT_EOF
chmod +x start_app.sh
# 创建调试脚本
log_info \"创建调试脚本...\"
cat > debug_app.sh <<DEBUG_EOF
#!/bin/bash
cd \"$REMOTE_DIR\"
echo \"=== 调试信息 ===\"
echo \"当前目录: \$(pwd)\"
# 激活虚拟环境
source \"/home/ubuntu/host_message_venv/bin/activate\" 2>/dev/null || echo \"虚拟环境激活失败\"
echo \"Python版本: \$(python --version 2>/dev/null || python3 --version)\"
echo \"pip版本: \$(pip --version 2>/dev/null || echo '无pip')\"
echo \"虚拟环境: \$VIRTUAL_ENV\"
echo \"文件列表:\"
ls -la
echo \"=== 尝试导入测试 ===\"
python -c \"
import sys
print('Python路径:', sys.path)
try:
import fastapi
print('fastapi版本:', fastapi.__version__)
except ImportError as e:
print('fastapi导入失败:', e)
try:
import uvicorn
print('uvicorn版本:', uvicorn.__version__)
except ImportError as e:
print('uvicorn导入失败:', e)
\" 2>/dev/null || python3 -c \"
import sys
print('Python路径:', sys.path)
try:
import fastapi
print('fastapi版本:', fastapi.__version__)
except ImportError as e:
print('fastapi导入失败:', e)
try:
import uvicorn
print('uvicorn版本:', uvicorn.__version__)
except ImportError as e:
print('uvicorn导入失败:', e)
\"
echo \"=== 检查端口占用 ===\"
netstat -tlnp | grep 8888 || echo '端口8888未被占用'
echo \"=== 尝试启动应用5秒后停止 ===\"
timeout 5 python main.py 2>/dev/null || timeout 5 python3 main.py || echo '应用启动测试完成'
DEBUG_EOF
chmod +x debug_app.sh
# 创建supervisor配置
log_info \"配置进程保活监控...\"
sudo tee /etc/supervisor/conf.d/host_message.conf > /dev/null <<EOF
[program:host_message]
command=$REMOTE_DIR/start_app.sh
directory=$REMOTE_DIR
user=ubuntu
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=$REMOTE_DIR/logs/supervisor.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
environment=PATH=\"/home/ubuntu/host_message_venv/bin:/usr/local/bin:/usr/bin:/bin\",PYTHONPATH=\"$REMOTE_DIR\"
startsecs=10
startretries=3
EOF
# 停止可能存在的旧进程
sudo supervisorctl stop host_message 2>/dev/null || true
# 重新加载supervisor配置
sudo supervisorctl reread
sudo supervisorctl update
# 启动应用
log_info \"启动应用...\"
sudo supervisorctl start host_message
# 等待应用启动并检查状态
log_info \"等待应用启动...\"
for i in {1..30}; do
sleep 2
STATUS=\$(sudo supervisorctl status host_message 2>/dev/null || echo "ERROR")
log_info \"\$i 次检查: \$STATUS\"
if echo \"\$STATUS\" | grep -q \"RUNNING\"; then
log_info \"应用启动成功!\"
break
elif echo \"\$STATUS\" | grep -q \"FATAL\\|BACKOFF\"; then
log_error \"应用启动失败: \$STATUS\"
log_error \"查看详细日志:\"
tail -20 \"$REMOTE_DIR/logs/supervisor.log\" 2>/dev/null || echo \"无法读取日志文件\"
# 运行调试脚本
log_info \"运行调试脚本获取详细信息:\"
cd \"$REMOTE_DIR\"
./debug_app.sh 2>&1 || true
# 检查supervisor错误日志
log_info \"检查supervisor错误日志:\"
sudo tail -20 /var/log/supervisor/supervisord.log 2>/dev/null || echo \"无法读取supervisor日志\"
exit 1
fi
if [ \$i -eq 30 ]; then
log_error \"应用启动超时\"
log_error \"最终状态: \$STATUS\"
exit 1
fi
done
# 检查端口监听
log_info \"检查端口监听状态...\"
for i in {1..10}; do
if netstat -tlnp 2>/dev/null | grep -q \":$APP_PORT\" || ss -tlnp 2>/dev/null | grep -q \":$APP_PORT\"; then
log_info \"端口 $APP_PORT 监听正常\"
log_info \"部署完成!您可以通过 http://$REMOTE_HOST:$APP_PORT 访问应用\"
break
else
log_warn \"等待端口 $APP_PORT 开始监听... (\$i/10)\"
sleep 2
fi
if [ \$i -eq 10 ]; then
log_warn \"端口 $APP_PORT 未在监听,请检查应用日志\"
log_info \"当前监听的端口:\"
netstat -tlnp 2>/dev/null | grep LISTEN || ss -tlnp 2>/dev/null | grep LISTEN || echo \"无法获取监听端口信息\"
fi
done
# 清理临时文件
rm -f \"/tmp/$ZIP_NAME\"
log_info \"部署脚本执行完成\"
}
# 执行主要部署逻辑
main_deploy
EOF
)
# 执行远程部署脚本
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "$REMOTE_SCRIPT"
if [ $? -eq 0 ]; then
log_info "远程部署完成"
else
log_error "远程部署失败"
exit 1
fi
}
# 清理本地临时文件
cleanup() {
log_step "清理临时文件..."
rm -f "$TEMP_ZIP"
log_info "临时文件清理完成"
}
# 显示部署信息
show_deploy_info() {
echo ""
echo "=========================================="
echo " 部署完成信息"
echo "=========================================="
echo "服务器地址: $REMOTE_HOST"
echo "部署目录: $REMOTE_DIR"
echo "应用端口: $APP_PORT"
echo "访问地址: http://$REMOTE_HOST:$APP_PORT"
echo "=========================================="
echo ""
echo "常用管理命令:"
echo "查看应用状态: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl status host_message'"
echo "重启应用: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl restart host_message'"
echo "停止应用: ssh $REMOTE_USER@$REMOTE_HOST 'sudo supervisorctl stop host_message'"
echo "查看日志: ssh $REMOTE_USER@$REMOTE_HOST 'tail -f $REMOTE_DIR/logs/supervisor.log'"
echo "调试应用: ssh $REMOTE_USER@$REMOTE_HOST 'cd $REMOTE_DIR && ./debug_app.sh'"
echo "=========================================="
# 最后验证部署
log_info "正在验证部署结果..."
sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "
echo '=== 最终验证 ==='
sudo supervisorctl status host_message
echo '=== 端口检查 ==='
netstat -tlnp | grep 8888 || ss -tlnp | grep 8888 || echo '端口8888未监听'
echo '=== 进程检查 ==='
ps aux | grep 'python3 main.py' | grep -v grep || echo '未找到Python进程'
" || log_warn "最终验证失败,请手动检查"
}
# 主函数
main() {
echo "=========================================="
echo " host_message 项目自动部署脚本"
echo "=========================================="
echo "目标服务器: $REMOTE_HOST"
echo "部署目录: $REMOTE_DIR"
echo "开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
echo ""
# 执行部署步骤
check_dependencies
create_package
test_ssh_connection
upload_package
remote_deploy
cleanup
show_deploy_info
log_info "🎉 全部部署任务完成!"
}
# 错误处理
set -e
trap 'log_error "脚本执行过程中发生错误,退出码: $?"' ERR
# 执行主函数
main "$@"