Browse Source
- 支持自动检测包管理器(npm/yarn) - 包含Node.js版本检查 - 自动安装依赖和TypeScript - 构建生产版本并创建发布目录 - 提供详细的部署说明 - 支持命令行参数控制构建过程feature/x1-web-request
8 changed files with 589 additions and 10 deletions
@ -0,0 +1,19 @@ |
|||||
|
node_modules |
||||
|
npm-debug.log |
||||
|
.git |
||||
|
.gitignore |
||||
|
README.md |
||||
|
.env |
||||
|
.env.local |
||||
|
.env.development |
||||
|
.env.test |
||||
|
.nyc_output |
||||
|
coverage |
||||
|
.vscode |
||||
|
.idea |
||||
|
*.log |
||||
|
dist |
||||
|
.DS_Store |
||||
|
Thumbs.db |
||||
|
build.bat |
||||
|
deploy.sh |
||||
@ -0,0 +1,32 @@ |
|||||
|
# 使用官方Node.js 18 Alpine镜像作为构建环境 |
||||
|
FROM node:18-alpine AS builder |
||||
|
|
||||
|
# 设置工作目录 |
||||
|
WORKDIR /app |
||||
|
|
||||
|
# 复制package.json和package-lock.json |
||||
|
COPY package*.json ./ |
||||
|
|
||||
|
# 安装依赖 |
||||
|
RUN npm ci --only=production |
||||
|
|
||||
|
# 复制源代码 |
||||
|
COPY . . |
||||
|
|
||||
|
# 构建生产版本 |
||||
|
RUN npm run build |
||||
|
|
||||
|
# 使用nginx Alpine镜像作为运行环境 |
||||
|
FROM nginx:alpine |
||||
|
|
||||
|
# 复制构建产物到nginx目录 |
||||
|
COPY --from=builder /app/dist /usr/share/nginx/html |
||||
|
|
||||
|
# 复制nginx配置文件 |
||||
|
COPY nginx.conf /etc/nginx/nginx.conf |
||||
|
|
||||
|
# 暴露端口 |
||||
|
EXPOSE 80 |
||||
|
|
||||
|
# 启动nginx |
||||
|
CMD ["nginx", "-g", "daemon off;"] |
||||
@ -0,0 +1,248 @@ |
|||||
|
@echo off |
||||
|
setlocal enabledelayedexpansion |
||||
|
chcp 65001 >nul |
||||
|
|
||||
|
REM 设置错误时退出 |
||||
|
set "EXIT_CODE=0" |
||||
|
|
||||
|
REM 解析命令行参数 |
||||
|
call :parse_arguments %* |
||||
|
|
||||
|
REM 主函数 |
||||
|
call :main |
||||
|
goto :end |
||||
|
|
||||
|
REM 主函数 |
||||
|
:main |
||||
|
call :log_info "Starting X1.WebUI build process..." |
||||
|
|
||||
|
call :check_node_version |
||||
|
if !EXIT_CODE! neq 0 goto :end |
||||
|
|
||||
|
call :detect_package_manager |
||||
|
if !EXIT_CODE! neq 0 goto :end |
||||
|
|
||||
|
call :install_dependencies |
||||
|
if !EXIT_CODE! neq 0 goto :end |
||||
|
|
||||
|
call :build_project |
||||
|
if !EXIT_CODE! neq 0 goto :end |
||||
|
|
||||
|
call :create_publish_dir |
||||
|
call :show_publish_info |
||||
|
call :show_deployment_instructions |
||||
|
|
||||
|
call :log_success "Build completed!" |
||||
|
call :log_info "Please upload the publish directory contents to server /home/hyh/x1_web/" |
||||
|
goto :eof |
||||
|
|
||||
|
REM 检查Node.js版本 |
||||
|
:check_node_version |
||||
|
call :log_info "Checking Node.js version..." |
||||
|
node --version >nul 2>&1 |
||||
|
if errorlevel 1 ( |
||||
|
call :log_error "Node.js not installed, please install Node.js 18+" |
||||
|
set "EXIT_CODE=1" |
||||
|
goto :end |
||||
|
) |
||||
|
|
||||
|
for /f "tokens=*" %%i in ('node --version') do set "NODE_VERSION=%%i" |
||||
|
call :log_success "Node.js version check passed: !NODE_VERSION!" |
||||
|
goto :eof |
||||
|
|
||||
|
REM 解析命令行参数 |
||||
|
:parse_arguments |
||||
|
set "PACKAGE_MANAGER=" |
||||
|
set "SHOW_HELP=false" |
||||
|
|
||||
|
:parse_loop |
||||
|
if "%~1"=="" goto :parse_done |
||||
|
if /i "%~1"=="--npm" ( |
||||
|
set "PACKAGE_MANAGER=npm" |
||||
|
call :log_info "Package manager set to npm via command line" |
||||
|
) else if /i "%~1"=="--yarn" ( |
||||
|
set "PACKAGE_MANAGER=yarn" |
||||
|
call :log_info "Package manager set to yarn via command line" |
||||
|
) else if /i "%~1"=="--help" ( |
||||
|
set "SHOW_HELP=true" |
||||
|
) else ( |
||||
|
call :log_error "Unknown parameter: %~1" |
||||
|
call :show_help |
||||
|
set "EXIT_CODE=1" |
||||
|
goto :end |
||||
|
) |
||||
|
shift |
||||
|
goto :parse_loop |
||||
|
|
||||
|
:parse_done |
||||
|
if "!SHOW_HELP!"=="true" ( |
||||
|
call :show_help |
||||
|
set "EXIT_CODE=0" |
||||
|
goto :end |
||||
|
) |
||||
|
goto :eof |
||||
|
|
||||
|
REM 显示帮助信息 |
||||
|
:show_help |
||||
|
echo. |
||||
|
echo Usage: build.bat [options] |
||||
|
echo. |
||||
|
echo Options: |
||||
|
echo --npm Force using npm package manager |
||||
|
echo --yarn Force using yarn package manager |
||||
|
echo --help Show this help message |
||||
|
echo. |
||||
|
echo Examples: |
||||
|
echo build.bat - Auto detect package manager |
||||
|
echo build.bat --npm - Force using npm |
||||
|
echo build.bat --yarn - Force using yarn |
||||
|
echo build.bat --help - Show help |
||||
|
echo. |
||||
|
goto :eof |
||||
|
|
||||
|
REM 检测包管理器 |
||||
|
:detect_package_manager |
||||
|
call :log_info "Detecting package manager..." |
||||
|
if not "!PACKAGE_MANAGER!"=="" ( |
||||
|
call :log_info "Package manager already set to: !PACKAGE_MANAGER!" |
||||
|
goto :eof |
||||
|
) |
||||
|
|
||||
|
if exist "yarn.lock" ( |
||||
|
set "PACKAGE_MANAGER=yarn" |
||||
|
call :log_info "Found yarn.lock, using yarn" |
||||
|
) else if exist "package-lock.json" ( |
||||
|
set "PACKAGE_MANAGER=npm" |
||||
|
call :log_info "Found package-lock.json, using npm" |
||||
|
) else ( |
||||
|
set "PACKAGE_MANAGER=npm" |
||||
|
call :log_info "No lock file found, defaulting to npm" |
||||
|
) |
||||
|
goto :eof |
||||
|
|
||||
|
REM 安装依赖 |
||||
|
:install_dependencies |
||||
|
call :log_info "Installing project dependencies..." |
||||
|
if not exist "node_modules" ( |
||||
|
if "!PACKAGE_MANAGER!"=="yarn" ( |
||||
|
call yarn install --frozen-lockfile |
||||
|
) else ( |
||||
|
call npm ci |
||||
|
) |
||||
|
) else ( |
||||
|
if "!PACKAGE_MANAGER!"=="yarn" ( |
||||
|
call yarn install --frozen-lockfile --production |
||||
|
) else ( |
||||
|
call npm ci --only=production |
||||
|
) |
||||
|
) |
||||
|
if errorlevel 1 ( |
||||
|
call :log_error "Dependency installation failed" |
||||
|
set "EXIT_CODE=1" |
||||
|
goto :end |
||||
|
) |
||||
|
call :log_success "Dependencies installed successfully" |
||||
|
goto :eof |
||||
|
|
||||
|
REM 构建项目 |
||||
|
:build_project |
||||
|
call :log_info "Building production version..." |
||||
|
|
||||
|
REM 直接尝试构建,如果失败则安装TypeScript |
||||
|
call :log_info "Attempting to build project..." |
||||
|
if "!PACKAGE_MANAGER!"=="yarn" ( |
||||
|
call yarn build |
||||
|
) else ( |
||||
|
call npm run build |
||||
|
) |
||||
|
if errorlevel 1 ( |
||||
|
call :log_warning "Build failed, checking if TypeScript is needed..." |
||||
|
call :log_info "Installing TypeScript as dev dependency..." |
||||
|
if "!PACKAGE_MANAGER!"=="yarn" ( |
||||
|
call yarn add -D typescript |
||||
|
) else ( |
||||
|
call npm install --save-dev typescript |
||||
|
) |
||||
|
|
||||
|
call :log_info "Retrying build after TypeScript installation..." |
||||
|
if "!PACKAGE_MANAGER!"=="yarn" ( |
||||
|
call yarn build |
||||
|
) else ( |
||||
|
call npm run build |
||||
|
) |
||||
|
if errorlevel 1 ( |
||||
|
call :log_error "Project build failed even after TypeScript installation" |
||||
|
set "EXIT_CODE=1" |
||||
|
goto :end |
||||
|
) |
||||
|
) |
||||
|
call :log_success "Project build completed" |
||||
|
goto :eof |
||||
|
|
||||
|
REM 创建发布目录 |
||||
|
:create_publish_dir |
||||
|
call :log_info "Creating publish directory..." |
||||
|
if exist "publish" rmdir /s /q "publish" |
||||
|
mkdir "publish" |
||||
|
|
||||
|
REM 复制构建产物到发布目录根目录 |
||||
|
xcopy "dist\*" "publish\" /E /Y /Q |
||||
|
|
||||
|
REM 复制Dockerfile和nginx配置到发布目录根目录 |
||||
|
copy "Dockerfile" "publish\" >nul |
||||
|
copy "nginx.conf" "publish\" >nul |
||||
|
|
||||
|
REM 复制部署脚本到发布目录根目录 |
||||
|
copy "deploy.sh" "publish\" >nul |
||||
|
|
||||
|
REM 注意:.dockerignore 文件不需要复制到发布目录,只在本地构建时使用 |
||||
|
|
||||
|
call :log_success "Publish directory created: publish/" |
||||
|
goto :eof |
||||
|
|
||||
|
REM 显示文件大小 |
||||
|
:show_publish_info |
||||
|
call :log_info "Publish directory info:" |
||||
|
for /f "tokens=1" %%i in ('dir publish /s ^| find "个文件"') do echo File count: %%i |
||||
|
echo. |
||||
|
call :log_info "Publish directory contents:" |
||||
|
dir publish /B |
||||
|
echo. |
||||
|
call :log_info "Publish directory structure:" |
||||
|
tree publish /F |
||||
|
goto :eof |
||||
|
|
||||
|
REM 显示部署说明 |
||||
|
:show_deployment_instructions |
||||
|
call :log_info "Deployment instructions:" |
||||
|
echo. |
||||
|
echo 1. Upload all contents of publish directory to server /home/hyh/x1_web/ |
||||
|
echo 2. Ensure the following files are in /home/hyh/x1_web/ root directory: |
||||
|
echo - Dockerfile |
||||
|
echo - nginx.conf |
||||
|
echo - deploy.sh |
||||
|
echo - index.html |
||||
|
echo - assets/ directory |
||||
|
echo 3. On server run: chmod +x deploy.sh ^&^& ./deploy.sh |
||||
|
echo. |
||||
|
goto :eof |
||||
|
|
||||
|
REM 日志函数(简化版本,不使用颜色) |
||||
|
:log_info |
||||
|
echo [INFO] %~1 |
||||
|
goto :eof |
||||
|
|
||||
|
:log_success |
||||
|
echo [SUCCESS] %~1 |
||||
|
goto :eof |
||||
|
|
||||
|
:log_warning |
||||
|
echo [WARNING] %~1 |
||||
|
goto :eof |
||||
|
|
||||
|
:log_error |
||||
|
echo [ERROR] %~1 |
||||
|
goto :eof |
||||
|
|
||||
|
:end |
||||
|
exit /b %EXIT_CODE% |
||||
@ -0,0 +1,207 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# 设置错误时退出 |
||||
|
set -e |
||||
|
|
||||
|
# 颜色定义 |
||||
|
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 "${BLUE}[INFO]${NC} $1" |
||||
|
} |
||||
|
|
||||
|
log_success() { |
||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1" |
||||
|
} |
||||
|
|
||||
|
log_warning() { |
||||
|
echo -e "${YELLOW}[WARNING]${NC} $1" |
||||
|
} |
||||
|
|
||||
|
log_error() { |
||||
|
echo -e "${RED}[ERROR]${NC} $1" |
||||
|
} |
||||
|
|
||||
|
# 默认配置 |
||||
|
IMAGE_NAME="x1-webui" |
||||
|
CONTAINER_NAME="x1-webui-container" |
||||
|
PORT="9905" |
||||
|
REMOVE_MODE=false |
||||
|
|
||||
|
# 解析命令行参数 |
||||
|
parse_arguments() { |
||||
|
while [[ $# -gt 0 ]]; do |
||||
|
case $1 in |
||||
|
-p|--port) |
||||
|
PORT="$2" |
||||
|
shift 2 |
||||
|
;; |
||||
|
-i|--image) |
||||
|
IMAGE_NAME="$2" |
||||
|
shift 2 |
||||
|
;; |
||||
|
-c|--container) |
||||
|
CONTAINER_NAME="$2" |
||||
|
shift 2 |
||||
|
;; |
||||
|
-r|--remove) |
||||
|
REMOVE_MODE=true |
||||
|
shift |
||||
|
;; |
||||
|
-h|--help) |
||||
|
echo "用法: $0 [选项]" |
||||
|
echo "选项:" |
||||
|
echo " -p, --port PORT 指定端口 (默认: 9905)" |
||||
|
echo " -i, --image NAME 指定镜像名称 (默认: x1-webui)" |
||||
|
echo " -c, --container NAME 指定容器名称 (默认: x1-webui-container)" |
||||
|
echo " -r, --remove 删除旧容器和镜像" |
||||
|
echo " -h, --help 显示帮助信息" |
||||
|
exit 0 |
||||
|
;; |
||||
|
*) |
||||
|
log_error "未知选项: $1" |
||||
|
exit 1 |
||||
|
;; |
||||
|
esac |
||||
|
done |
||||
|
} |
||||
|
|
||||
|
# 检查Docker |
||||
|
check_docker() { |
||||
|
log_info "检查Docker安装状态..." |
||||
|
if ! command -v docker &> /dev/null; then |
||||
|
log_error "Docker未安装,请先安装Docker" |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
if ! docker info &> /dev/null; then |
||||
|
log_error "Docker服务未运行,请启动Docker服务" |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
log_success "Docker检查通过" |
||||
|
} |
||||
|
|
||||
|
# 检查必要文件 |
||||
|
check_files() { |
||||
|
log_info "检查必要文件..." |
||||
|
|
||||
|
local required_files=("Dockerfile" "nginx.conf" "index.html") |
||||
|
for file in "${required_files[@]}"; do |
||||
|
if [ ! -f "$file" ]; then |
||||
|
log_error "缺少必要文件: $file" |
||||
|
exit 1 |
||||
|
fi |
||||
|
done |
||||
|
|
||||
|
log_success "文件检查通过" |
||||
|
} |
||||
|
|
||||
|
# 停止并删除旧容器 |
||||
|
stop_old_container() { |
||||
|
log_info "停止旧容器..." |
||||
|
if docker ps -a --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then |
||||
|
docker stop "$CONTAINER_NAME" || true |
||||
|
docker rm "$CONTAINER_NAME" || true |
||||
|
log_success "旧容器已停止并删除" |
||||
|
else |
||||
|
log_info "没有找到旧容器" |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# 删除旧镜像 |
||||
|
remove_old_image() { |
||||
|
if [ "$REMOVE_MODE" = true ]; then |
||||
|
log_info "删除旧镜像..." |
||||
|
if docker images --format "table {{.Repository}}" | grep -q "^${IMAGE_NAME}$"; then |
||||
|
docker rmi "$IMAGE_NAME" || true |
||||
|
log_success "旧镜像已删除" |
||||
|
else |
||||
|
log_info "没有找到旧镜像" |
||||
|
fi |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# 构建Docker镜像 |
||||
|
build_image() { |
||||
|
log_info "构建Docker镜像: $IMAGE_NAME" |
||||
|
docker build -t "$IMAGE_NAME" . |
||||
|
log_success "镜像构建完成" |
||||
|
} |
||||
|
|
||||
|
# 运行容器 |
||||
|
run_container() { |
||||
|
log_info "启动容器: $CONTAINER_NAME" |
||||
|
docker run -d \ |
||||
|
--name "$CONTAINER_NAME" \ |
||||
|
-p "$PORT:80" \ |
||||
|
--restart unless-stopped \ |
||||
|
"$IMAGE_NAME" |
||||
|
|
||||
|
log_success "容器启动完成" |
||||
|
} |
||||
|
|
||||
|
# 健康检查 |
||||
|
health_check() { |
||||
|
log_info "等待应用启动..." |
||||
|
local max_attempts=30 |
||||
|
local attempt=1 |
||||
|
|
||||
|
while [ $attempt -le $max_attempts ]; do |
||||
|
if curl -f -s "http://localhost:$PORT/health" > /dev/null 2>&1; then |
||||
|
log_success "应用启动成功!" |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
log_info "等待应用启动... ($attempt/$max_attempts)" |
||||
|
sleep 2 |
||||
|
((attempt++)) |
||||
|
done |
||||
|
|
||||
|
log_warning "应用启动超时,但容器可能仍在运行" |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# 显示部署信息 |
||||
|
show_deployment_info() { |
||||
|
log_success "部署完成!" |
||||
|
echo "" |
||||
|
echo "部署信息:" |
||||
|
echo " 镜像名称: $IMAGE_NAME" |
||||
|
echo " 容器名称: $CONTAINER_NAME" |
||||
|
echo " 访问端口: $PORT" |
||||
|
echo " 访问地址: http://localhost:$PORT" |
||||
|
echo "" |
||||
|
|
||||
|
log_info "容器状态:" |
||||
|
docker ps --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" |
||||
|
|
||||
|
echo "" |
||||
|
log_info "容器日志:" |
||||
|
docker logs --tail 10 "$CONTAINER_NAME" |
||||
|
} |
||||
|
|
||||
|
# 主函数 |
||||
|
main() { |
||||
|
log_info "开始部署X1.WebUI..." |
||||
|
|
||||
|
parse_arguments "$@" |
||||
|
check_docker |
||||
|
check_files |
||||
|
stop_old_container |
||||
|
remove_old_image |
||||
|
build_image |
||||
|
run_container |
||||
|
health_check |
||||
|
show_deployment_info |
||||
|
|
||||
|
log_success "部署完成!" |
||||
|
} |
||||
|
|
||||
|
# 执行主函数 |
||||
|
main "$@" |
||||
@ -0,0 +1,74 @@ |
|||||
|
events { |
||||
|
worker_connections 1024; |
||||
|
} |
||||
|
|
||||
|
http { |
||||
|
include /etc/nginx/mime.types; |
||||
|
default_type application/octet-stream; |
||||
|
|
||||
|
# 日志格式 |
||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' |
||||
|
'$status $body_bytes_sent "$http_referer" ' |
||||
|
'"$http_user_agent" "$http_x_forwarded_for"'; |
||||
|
|
||||
|
access_log /var/log/nginx/access.log main; |
||||
|
error_log /var/log/nginx/error.log; |
||||
|
|
||||
|
# 基本设置 |
||||
|
sendfile on; |
||||
|
tcp_nopush on; |
||||
|
tcp_nodelay on; |
||||
|
keepalive_timeout 65; |
||||
|
types_hash_max_size 2048; |
||||
|
|
||||
|
# Gzip压缩 |
||||
|
gzip on; |
||||
|
gzip_vary on; |
||||
|
gzip_min_length 1024; |
||||
|
gzip_proxied any; |
||||
|
gzip_comp_level 6; |
||||
|
gzip_types |
||||
|
text/plain |
||||
|
text/css |
||||
|
text/xml |
||||
|
text/javascript |
||||
|
application/json |
||||
|
application/javascript |
||||
|
application/xml+rss |
||||
|
application/atom+xml |
||||
|
image/svg+xml; |
||||
|
|
||||
|
server { |
||||
|
listen 80; |
||||
|
server_name localhost; |
||||
|
root /usr/share/nginx/html; |
||||
|
index index.html; |
||||
|
|
||||
|
# 处理React Router的客户端路由 |
||||
|
location / { |
||||
|
try_files $uri $uri/ /index.html; |
||||
|
} |
||||
|
|
||||
|
# 静态资源缓存 |
||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { |
||||
|
expires 1y; |
||||
|
add_header Cache-Control "public, immutable"; |
||||
|
} |
||||
|
|
||||
|
# API代理到后端9905端口 |
||||
|
location /api/ { |
||||
|
proxy_pass http://host.docker.internal:9905; |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
# 健康检查 |
||||
|
location /health { |
||||
|
access_log off; |
||||
|
return 200 "healthy\n"; |
||||
|
add_header Content-Type text/plain; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue