#!/bin/bash # 知识库系统部署脚本 # 使用Laravel Octane + Swoole set -e # 遇到错误立即退出 # 配置变量 SERVER_HOST="192.168.1.33" SERVER_USER="root" SERVER_PASSWORD="Sipai@123" SERVER_PATH="/opt/KnowledgeBase" IMAGE_NAME="knowledge-base-app" IMAGE_TAG="latest" COMPOSE_VERSION="1.25.5" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 检查必要工具 check_requirements() { log_info "检查部署环境..." if ! command -v docker &> /dev/null; then log_error "Docker 未安装" exit 1 fi if ! command -v sshpass &> /dev/null; then log_error "sshpass 未安装,请先安装: brew install sshpass" exit 1 fi log_info "环境检查完成" } # 设置网络代理(如果需要) setup_proxy() { if [ "$USE_PROXY" = "true" ]; then log_info "设置网络代理..." export https_proxy=http://127.0.0.1:7890 export http_proxy=http://127.0.0.1:7890 export all_proxy=socks5://127.0.0.1:7890 log_info "代理设置完成" fi } # 清理本地构建文件 clean_local() { log_info "清理本地构建文件..." # 清理不需要的文件 rm -rf node_modules/.cache rm -rf storage/logs/*.log rm -rf storage/framework/cache/data/* rm -rf storage/framework/sessions/* rm -rf storage/framework/views/* log_info "本地清理完成" } # 构建Docker镜像 build_image() { log_info "开始构建Docker镜像..." # 构建镜像 docker build --platform linux/amd64 -t ${IMAGE_NAME}:${IMAGE_TAG} . if [ $? -eq 0 ]; then log_info "Docker镜像构建成功" else log_error "Docker镜像构建失败" exit 1 fi } # 导出镜像为tar包 export_image() { log_info "导出Docker镜像..." docker save ${IMAGE_NAME}:${IMAGE_TAG} | gzip > ${IMAGE_NAME}-${IMAGE_TAG}.tar.gz if [ $? -eq 0 ]; then log_info "镜像导出成功: ${IMAGE_NAME}-${IMAGE_TAG}.tar.gz" else log_error "镜像导出失败" exit 1 fi } # 同步代码到服务器 sync_code() { log_info "同步代码到服务器..." # 创建临时目录用于同步 TEMP_DIR=$(mktemp -d) # 复制需要的文件 cp -r . "$TEMP_DIR/" # 删除不需要的文件 cd "$TEMP_DIR" rm -rf node_modules # 保留vendor目录,因为服务器上缺少Octane包 rm -rf storage/logs/*.log rm -rf storage/framework/cache/data/* rm -rf storage/framework/sessions/* rm -rf storage/framework/views/* rm -rf .git rm -rf tests rm -rf docs rm -rf .DS_Store rm -rf *.tar.gz # 同步到服务器 sshpass -p "${SERVER_PASSWORD}" rsync -avz --delete \ --exclude='storage/mysql/' \ --exclude='storage/redis/' \ --exclude='storage/meilisearch/' \ --exclude='storage/app/documents/' \ --exclude='storage/app/markdown/' \ "$TEMP_DIR/" "${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/" # 清理临时目录 rm -rf "$TEMP_DIR" log_info "代码同步完成" } # 上传Docker镜像 upload_image() { log_info "上传Docker镜像到服务器..." sshpass -p "${SERVER_PASSWORD}" scp ${IMAGE_NAME}-${IMAGE_TAG}.tar.gz \ ${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/ log_info "镜像上传完成" } # 在服务器上部署 deploy_on_server() { log_info "在服务器上执行部署..." sshpass -p "${SERVER_PASSWORD}" ssh ${SERVER_USER}@${SERVER_HOST} << EOF cd ${SERVER_PATH} # 停止现有服务 echo "停止现有服务..." docker-compose -f docker-compose-simple.yml down || true # 删除旧镜像 echo "清理旧镜像..." docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true docker system prune -f # 加载新镜像 echo "加载新镜像..." docker load < ${IMAGE_NAME}-${IMAGE_TAG}.tar.gz # 删除镜像文件 rm -f ${IMAGE_NAME}-${IMAGE_TAG}.tar.gz # 复制生产环境配置 echo "设置生产环境配置..." cp .env.production .env # 生成新的APP_KEY(如果需要) if grep -q "your-app-key-here" .env; then echo "生成新的APP_KEY..." APP_KEY=\$(openssl rand -base64 32) sed -i "s|your-app-key-here-change-this-in-production|base64:\$APP_KEY|g" .env fi # 生成新的MEILISEARCH_KEY(如果需要) if grep -q "your-master-key-change-this-in-production" .env; then echo "生成新的MEILISEARCH_KEY..." MEILI_KEY=\$(openssl rand -hex 32) sed -i "s|your-master-key-change-this-in-production|\$MEILI_KEY|g" .env fi # 启动服务 echo "启动服务..." docker-compose -f docker-compose-simple.yml up -d # 等待服务启动 echo "等待服务启动..." sleep 30 # 运行数据库迁移 echo "运行数据库迁移..." docker-compose -f docker-compose-simple.yml exec -T app php artisan migrate --force # 清理缓存 echo "清理应用缓存..." docker-compose -f docker-compose-simple.yml exec -T app php artisan config:clear docker-compose -f docker-compose-simple.yml exec -T app php artisan cache:clear docker-compose -f docker-compose-simple.yml exec -T app php artisan route:clear docker-compose -f docker-compose-simple.yml exec -T app php artisan view:clear # 重新生成缓存 echo "重新生成缓存..." docker-compose -f docker-compose-simple.yml exec -T app php artisan config:cache docker-compose -f docker-compose-simple.yml exec -T app php artisan route:cache docker-compose -f docker-compose-simple.yml exec -T app php artisan view:cache # 创建搜索索引 echo "创建搜索索引..." docker-compose -f docker-compose-simple.yml exec -T app php artisan scout:import || true echo "部署完成!" EOF log_info "服务器部署完成" } # 验证部署 verify_deployment() { log_info "验证部署状态..." sshpass -p "${SERVER_PASSWORD}" ssh ${SERVER_USER}@${SERVER_HOST} << EOF cd ${SERVER_PATH} echo "=== 服务状态 ===" docker-compose -f docker-compose-simple.yml ps echo "=== 应用健康检查 ===" curl -f http://localhost:8000/health || echo "健康检查失败" echo "=== 队列状态 ===" docker-compose -f docker-compose-simple.yml exec -T app php artisan queue:work --once --timeout=10 || echo "队列测试失败" echo "=== Meilisearch状态 ===" curl -f http://localhost:7700/health || echo "Meilisearch连接失败" echo "=== 数据库连接 ===" docker-compose -f docker-compose-simple.yml exec -T app php artisan tinker --execute="DB::connection()->getPdo(); echo 'Database connected successfully';" || echo "数据库连接失败" EOF log_info "部署验证完成" } # 清理本地文件 cleanup() { log_info "清理本地文件..." rm -f ${IMAGE_NAME}-${IMAGE_TAG}.tar.gz log_info "清理完成" } # 主函数 main() { log_info "开始部署知识库系统..." check_requirements setup_proxy clean_local build_image export_image sync_code upload_image deploy_on_server verify_deployment cleanup log_info "部署流程全部完成!" log_info "访问地址: http://${SERVER_HOST}:8000" } # 执行主函数 main "$@"