refactor(deploy): 统一发布脚本为单一入口 scripts/deploy.sh

- 新增 scripts/deploy.sh,支持 build/deploy/rollback/status 子命令
- 删除 deploy.sh、deploy-dist.sh、scripts/deploy-static.sh、scripts/deployment/deploy-production.sh
- Jenkinsfile 部署/回滚改为调用统一脚本,移除内联部署逻辑与 STATIC_DIR
- 同步 README、DEPLOYMENT、docs、CLAUDE.md、setup-cicd 与 package.json 脚本
This commit is contained in:
2026-08-17 18:40:51 +08:00
parent 315d664b9c
commit af57504e8e
19 changed files with 511 additions and 657 deletions
+7
View File
@@ -14,6 +14,13 @@ npm run build # Build production files to dist/
npm run build:clean # Clean then build npm run build:clean # Clean then build
npm run preview # Serve dist/ on port 3000 (npx serve) npm run preview # Serve dist/ on port 3000 (npx serve)
# Deploy (统一发布脚本)
./scripts/deploy.sh build # 构建静态产物
./scripts/deploy.sh deploy # 构建并发布到生产服务器
./scripts/deploy.sh deploy --skip-build # 使用现有 dist/ 直接发布
./scripts/deploy.sh rollback # 回滚到最近一次远程备份
./scripts/deploy.sh status # 查看生产环境发布状态
# Linting & Type Checking # Linting & Type Checking
npm run lint # ESLint (configured in config/lint/.eslintrc.json) npm run lint # ESLint (configured in config/lint/.eslintrc.json)
npm run type-check # tsc --noEmit npm run type-check # tsc --noEmit
+12 -10
View File
@@ -1,5 +1,7 @@
# Novalon网站部署文档 # Novalon网站部署文档
> ⚠️ **本文档部分内容为历史 Docker Compose 部署记录。当前唯一发布入口为 `scripts/deploy.sh`(远程静态发布 dist/ 到生产 Nginx),详细说明见 [docs/deployment.md](docs/deployment.md)。**
## 项目信息 ## 项目信息
- **项目名称**: Novalon官网 - **项目名称**: Novalon官网
@@ -94,13 +96,13 @@
- 提供Let's Encrypt配置指导 - 提供Let's Encrypt配置指导
- 设置证书文件权限 - 设置证书文件权限
### 6. deploy.sh ### 6. scripts/deploy.sh
- **作用**: 自动化部署脚本 - **作用**: 统一发布脚本(当前唯一发布入口)
- **功能**: - **功能**:
- 上传部署文件到服务器 - `build`:构建静态产物
- 配置SSL证书 - `deploy`:发布 `dist/` 到生产服务器(备份 + rsync + 权限 + Nginx 重载 + 验证)
- 启动Docker容器 - `rollback`:回滚到最近一次远程备份
- 检查容器状态和日志 - `status`:查看生产环境发布状态
## 部署步骤 ## 部署步骤
@@ -155,10 +157,10 @@ git push origin feat-dynamic
- nginx.conf - nginx.conf
- .env.example - .env.example
- setup-ssl.sh - setup-ssl.sh
- deploy.sh - scripts/deploy.sh
# 设置脚本执行权限 # 设置脚本执行权限
chmod +x deploy.sh setup-ssl.sh chmod +x scripts/deploy.sh setup-ssl.sh
``` ```
**配置详情**: **配置详情**:
@@ -247,8 +249,8 @@ certbot renew --dry-run
**自动化部署**: **自动化部署**:
```bash ```bash
# 执行部署脚本 # 执行统一发布脚本(构建 + 发布)
./deploy.sh ./scripts/deploy.sh deploy
``` ```
**手动部署**: **手动部署**:
Vendored
+8 -146
View File
@@ -8,7 +8,6 @@ pipeline {
SERVER_IP = '139.155.109.62' SERVER_IP = '139.155.109.62'
SERVER_USER = 'root' SERVER_USER = 'root'
DEPLOY_ROOT = '/home/novalon/docker-app' DEPLOY_ROOT = '/home/novalon/docker-app'
STATIC_DIR = 'novalon-static'
NGINX_CONTAINER = 'novalon-nginx-secure' NGINX_CONTAINER = 'novalon-nginx-secure'
DOMAIN = 'https://novalon.cn' DOMAIN = 'https://novalon.cn'
BACKUP_RETENTION_COUNT = 3 BACKUP_RETENTION_COUNT = 3
@@ -53,7 +52,7 @@ pipeline {
steps { steps {
echo "==========================================" echo "=========================================="
echo "🚀 Novalon Website CI/CD Pipeline" echo "🚀 Novalon Website CI/CD Pipeline"
echo "🐳 Docker Shell Mode (参考 deploy-dist.sh)" echo "🐳 Docker Shell Mode (参考 scripts/deploy.sh)"
echo "==========================================" echo "=========================================="
sh ''' sh '''
@@ -286,156 +285,19 @@ pipeline {
echo "⚠️ 准备部署到生产环境: ${DOMAIN}" echo "⚠️ 准备部署到生产环境: ${DOMAIN}"
sleep(time: 3, unit: 'SECONDS') sleep(time: 3, unit: 'SECONDS')
sh """ sh '''
set -e # 统一发布脚本(单一事实源: scripts/deploy.sh
./scripts/deploy.sh deploy --skip-build
DIST_DIR='dist' '''
echo "=========================================="
echo "📋 步骤 1: 检查 dist 目录"
echo "=========================================="
if [ ! -d "\$DIST_DIR" ]; then
echo "❌ dist 目录不存在,请先构建"
exit 1
fi
DIST_SIZE=\$(du -sh "\$DIST_DIR" | cut -f1)
echo "✅ dist 目录大小: \$DIST_SIZE"
echo ""
echo "=========================================="
echo "📋 步骤 2: 验证 SSH 连接"
echo "=========================================="
if ! ssh -o ConnectTimeout=5 ${SERVER_USER}@${SERVER_IP} exit; then
echo "❌ 无法连接到服务器 ${SERVER_IP}"
exit 1
fi
echo "✅ SSH 连接验证成功"
echo ""
echo "=========================================="
echo "📋 步骤 3: 备份旧版本"
echo "=========================================="
ssh ${SERVER_USER}@${SERVER_IP} "
set -e
STATIC_PATH='${DEPLOY_ROOT}/${STATIC_DIR}'
TIMESTAMP=\$(date +%Y%m%d_%H%M%S)
BACKUP_PATH='${DEPLOY_ROOT}/${STATIC_DIR}_backup_\${TIMESTAMP}'
if [ -d \"\$STATIC_PATH\" ]; then
echo \"📦 备份旧版本到: \$BACKUP_PATH\"
cp -r \"\$STATIC_PATH\" \"\$BACKUP_PATH\"
echo \"✅ 备份完成\"
echo \"🧹 清理旧备份(保留最近 ${BACKUP_RETENTION_COUNT} 个)...\"
cd '${DEPLOY_ROOT}'
ls -dt ${STATIC_DIR}_backup_* 2>/dev/null | tail -n +$((BACKUP_RETENTION_COUNT + 1)) | xargs rm -rf 2>/dev/null || true
echo \"✅ 清理完成\"
else
echo \"ℹ️ 无需备份(目录不存在),首次部署\"
fi
"
echo ""
echo "=========================================="
echo "📋 步骤 4: 上传 dist 目录"
echo "=========================================="
ssh ${SERVER_USER}@${SERVER_IP} "mkdir -p '${DEPLOY_ROOT}/${STATIC_DIR}'"
echo "📤 使用 rsync 同步文件..."
rsync -avz --delete "\$DIST_DIR/" ${SERVER_USER}@${SERVER_IP}:${DEPLOY_ROOT}/${STATIC_DIR}/
FILE_COUNT=\$(find \$DIST_DIR -type f | wc -l)
echo "✅ 上传完成: \$FILE_COUNT 个文件, 总大小 \$DIST_SIZE"
echo ""
echo "=========================================="
echo "📋 步骤 5: 设置权限"
echo "=========================================="
ssh ${SERVER_USER}@${SERVER_IP} "
set -e
STATIC_PATH='${DEPLOY_ROOT}/${STATIC_DIR}'
chown -R www-data:www-data \"\$STATIC_PATH\" 2>/dev/null || true
chmod -R 755 \"\$STATIC_PATH\"
echo \"✅ 权限设置完成\"
"
echo ""
echo "=========================================="
echo "📋 步骤 6: 重载 Nginx"
echo "=========================================="
ssh ${SERVER_USER}@${SERVER_IP} "
set -e
if docker exec ${NGINX_CONTAINER} nginx -t; then
docker exec ${NGINX_CONTAINER} nginx -s reload
echo \"✅ Nginx 重载成功\"
else
echo \"⚠️ Nginx 配置检测失败,尝试重启容器...\"
docker restart ${NGINX_CONTAINER}
sleep 3
echo \"✅ Nginx 容器已重启\"
fi
"
echo ""
echo "=========================================="
echo "📋 步骤 7: 验证部署"
echo "=========================================="
sleep 3
HTTP_STATUS=\$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 ${DOMAIN})
if [ "\$HTTP_STATUS" = "200" ]; then
echo "✅ 主页访问正常 (HTTP \$HTTP_STATUS)"
else
echo "❌ 主页返回异常状态码: \$HTTP_STATUS"
exit 1
fi
echo ""
echo "🔍 检查关键页面..."
for PAGE in "/contact" "/about" "/products" "/services"; do
STATUS=\$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 \${DOMAIN}\$PAGE)
if [ "\$STATUS" = "200" ]; then
echo " ✅ \$PAGE (\$STATUS)"
else
echo " ⚠️ \$PAGE (\$STATUS) - 需要关注"
fi
done
echo ""
echo "🎉 部署成功!"
echo "📋 访问地址:"
echo " HTTP: http://${SERVER_IP}"
echo " HTTPS: https://novalon.cn"
"""
} }
post { post {
failure { failure {
echo "❌ 部署失败!正在执行自动回滚..." echo "❌ 部署失败!正在执行自动回滚..."
script { script {
try { try {
sh """ sh '''
ssh ${SERVER_USER}@${SERVER_IP} ' ./scripts/deploy.sh rollback
set -e '''
LATEST_BACKUP=\$(ls -dt ${DEPLOY_ROOT}/${STATIC_DIR}_backup_* 2>/dev/null | head -1)
if [ -n "\$LATEST_BACKUP" ]; then
echo "🔄 回滚到: \$LATEST_BACKUP"
rm -rf "${DEPLOY_ROOT}/${STATIC_DIR}"
cp -r "\$LATEST_BACKUP" "${DEPLOY_ROOT}/${STATIC_DIR}"
if docker exec ${NGINX_CONTAINER} nginx -t; then
docker exec ${NGINX_CONTAINER} nginx -s reload
else
docker restart ${NGINX_CONTAINER}
fi
echo "✅ 回滚完成"
else
echo "❌ 未找到可用备份!需要手动介入"
exit 1
fi
'
"""
} catch (Exception e) { } catch (Exception e) {
echo "❌ 自动回滚失败: ${e.getMessage()}" echo "❌ 自动回滚失败: ${e.getMessage()}"
echo "🚨 需要立即手动介入!" echo "🚨 需要立即手动介入!"
+41 -8
View File
@@ -345,27 +345,60 @@ npm run test:all # type-check + lint + coverage + fast E2E
## 部署 ## 部署
### 静态部署 ### 统一发布脚本(推荐)
项目构建后生成纯静态文件,可部署到任何静态托管服务 项目发布统一通过 `scripts/deploy.sh` 完成,包含构建、发布、回滚、状态查看
```bash
# 构建静态产物
./scripts/deploy.sh build
# 构建并发布到生产服务器(默认命令,等价于 ./scripts/deploy.sh deploy
./scripts/deploy.sh deploy
# 使用现有 dist/ 直接发布(跳过本地构建)
./scripts/deploy.sh deploy --skip-build
# 回滚到最近一次远程备份
./scripts/deploy.sh rollback
# 查看生产环境发布状态
./scripts/deploy.sh status
# 查看完整帮助
./scripts/deploy.sh help
```
常用选项:
```bash
./scripts/deploy.sh deploy --skip-build --auto-rollback
./scripts/deploy.sh deploy --server-ip 139.155.109.62 --project-name novalon-website
```
也可通过环境变量覆盖默认配置:`SERVER_IP``SERVER_USER``DEPLOY_ROOT``PROJECT_NAME``NGINX_CONTAINER``DOMAIN``BACKUP_RETENTION_COUNT``DIST_DIR`
npm 快捷命令:`npm run deploy``npm run deploy:skip-build``npm run deploy:rollback``npm run deploy:status`
### 纯静态构建
项目构建后生成纯静态文件,产物位于 `dist/` 目录:
```bash ```bash
npm run build npm run build
``` ```
构建产物位于 `dist/` 目录,可直接部署到: `dist/` 可直接部署到:
- Nginx - Nginx
- CDN - CDN
- Vercel - Vercel
- Netlify - Netlify
- GitHub Pages - GitHub Pages
### Docker 部署 ### CI/CD
Jenkins Pipeline`Jenkinsfile`)在部署阶段调用 `./scripts/deploy.sh deploy --skip-build`,部署失败时自动调用 `./scripts/deploy.sh rollback` 回滚。
```bash
docker build -t novalon-website .
docker run -p 3000:3000 novalon-website
```
## 文档 ## 文档
-142
View File
@@ -1,142 +0,0 @@
#!/bin/bash
set -e
SERVER_IP="139.155.109.62"
SERVER_USER="root"
DEPLOY_ROOT="/home/novalon/docker-app"
PROJECT_NAME="novalon-website"
DIST_DIR="dist"
STATIC_DIR="novalon-static"
NGINX_CONTAINER="novalon-nginx-secure"
while getopts "i:u:p:h" opt; do
case $opt in
i) SERVER_IP="$OPTARG" ;;
u) SERVER_USER="$OPTARG" ;;
p) PROJECT_NAME="$OPTARG" ;;
h)
echo "用法: $0 [选项]"
echo "选项:"
echo " -i IP地址 服务器IP地址 (默认: 139.155.109.62)"
echo " -u 用户名 SSH用户名 (默认: root)"
echo " -p 项目名 项目名称 (默认: novalon-website)"
echo " -h 显示帮助信息"
exit 0
;;
\?)
echo "无效选项: -$OPTARG" >&2
exit 1
;;
esac
done
LOG_DIR="./logs"
LOG_FILE="$LOG_DIR/deploy_dist_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "🚀 开始部署 dist 到服务器 $SERVER_IP"
echo "📁 部署目录: $DEPLOY_ROOT/$STATIC_DIR"
echo "📋 部署日志: $LOG_FILE"
echo ""
echo "📋 步骤1: 检查 dist 目录..."
if [ ! -d "$DIST_DIR" ]; then
echo "❌ dist 目录不存在,请先运行 pnpm build"
exit 1
fi
DIST_SIZE=$(du -sh "$DIST_DIR" | cut -f1)
echo "✅ dist 目录大小: $DIST_SIZE"
echo ""
echo "📋 步骤2: 验证SSH连接..."
if ! ssh -o ConnectTimeout=5 "$SERVER_USER@$SERVER_IP" exit; then
echo "❌ 无法连接到服务器 $SERVER_IP"
exit 1
fi
echo "✅ SSH连接验证成功"
echo ""
echo "📋 步骤3: 备份旧版本..."
ssh "$SERVER_USER@$SERVER_IP" "
set -e
STATIC_PATH='$DEPLOY_ROOT/$STATIC_DIR'
BACKUP_PATH='$DEPLOY_ROOT/${STATIC_DIR}_backup_$(date +%Y%m%d_%H%M%S)'
if [ -d \"\$STATIC_PATH\" ]; then
echo \"📦 备份到 \$BACKUP_PATH\"
cp -r \"\$STATIC_PATH\" \"\$BACKUP_PATH\"
echo \"✅ 备份完成\"
else
echo \"️ 无需备份,目录不存在\"
fi
"
echo ""
echo "📋 步骤4: 上传 dist 目录..."
ssh "$SERVER_USER@$SERVER_IP" "mkdir -p '$DEPLOY_ROOT/$STATIC_DIR'"
rsync -avz --delete "$DIST_DIR/" "$SERVER_USER@$SERVER_IP:$DEPLOY_ROOT/$STATIC_DIR/"
echo "✅ dist 目录已上传"
echo ""
echo "📋 步骤5: 设置文件权限..."
ssh "$SERVER_USER@$SERVER_IP" "
set -e
STATIC_PATH='$DEPLOY_ROOT/$STATIC_DIR'
chown -R www-data:www-data \"\$STATIC_PATH\" 2>/dev/null || true
chmod -R 755 \"\$STATIC_PATH\"
echo \"✅ 权限设置完成\"
"
echo ""
echo "📋 步骤6: 清除 Nginx 缓存..."
ssh "$SERVER_USER@$SERVER_IP" "
set -e
docker exec $NGINX_CONTAINER nginx -s reload 2>/dev/null || echo \"⚠️ Nginx 重载失败,尝试重启容器\"
echo \"✅ Nginx 缓存已清除\"
"
echo ""
echo "📋 步骤7: 验证部署..."
if ! ssh "$SERVER_USER@$SERVER_IP" "[ -d '$DEPLOY_ROOT/$STATIC_DIR' ]"; then
echo "❌ 部署目录不存在"
exit 1
fi
FILE_COUNT=$(ssh "$SERVER_USER@$SERVER_IP" "find '$DEPLOY_ROOT/$STATIC_DIR' -type f | wc -l")
echo "✅ 部署完成,共 $FILE_COUNT 个文件"
echo ""
echo "📋 步骤8: 清理旧备份(保留最近3个)..."
ssh "$SERVER_USER@$SERVER_IP" "
set -e
BACKUP_DIR='$DEPLOY_ROOT'
cd \"\$BACKUP_DIR\"
ls -dt ${STATIC_DIR}_backup_* 2>/dev/null | tail -n +4 | xargs rm -rf 2>/dev/null || true
echo \"✅ 清理完成\"
"
echo ""
echo "📋 步骤9: 验证生产环境..."
LOGO_FONT=$(curl -s "https://novalon.cn/logo.svg" | grep -o "font-family: [^;]*" | head -1)
if echo "$LOGO_FONT" | grep -q "aoyagiReisho"; then
echo "✅ 生产环境已更新: $LOGO_FONT"
else
echo "⚠️ 生产环境可能未更新,请手动验证"
fi
echo ""
echo "🎉 部署成功!"
echo ""
echo "📋 访问地址:"
echo " HTTP: http://$SERVER_IP"
echo " HTTPS: https://$SERVER_IP"
echo " 域名: https://novalon.cn"
echo ""
echo "📋 后续步骤:"
echo " 1. 验证网站可访问性"
echo " 2. 检查静态资源加载"
echo " 3. 测试网站主要功能"
-201
View File
@@ -1,201 +0,0 @@
#!/bin/bash
set -e
SERVER_IP="139.155.109.62"
SERVER_USER="root"
DEPLOY_ROOT="/home/novalon/docker-app"
PROJECT_NAME="novalon-website"
PROJECT_DIR="$DEPLOY_ROOT/$PROJECT_NAME"
CONTAINER_NAME="novalon-website"
NGINX_CONTAINER_NAME="novalon-nginx"
VERSION="1.0.0"
while getopts "i:u:p:c:v:h" opt; do
case $opt in
i) SERVER_IP="$OPTARG" ;;
u) SERVER_USER="$OPTARG" ;;
p) PROJECT_NAME="$OPTARG" ;;
c) CONTAINER_NAME="$OPTARG" ;;
v) VERSION="$OPTARG" ;;
h)
echo "用法: $0 [选项]"
echo "选项:"
echo " -i IP地址 服务器IP地址 (默认: 139.155.109.62)"
echo " -u 用户名 SSH用户名 (默认: root)"
echo " -p 项目名 项目名称 (默认: novalon-website)"
echo " -c 容器名 容器名称 (默认: novalon-website)"
echo " -v 版本号 版本号 (默认: 1.0.0)"
echo " -h 显示帮助信息"
exit 0
;;
\?)
echo "无效选项: -$OPTARG" >&2
exit 1
;;
esac
done
PROJECT_DIR="$DEPLOY_ROOT/$PROJECT_NAME"
LOG_DIR="./logs"
LOG_FILE="$LOG_DIR/deploy_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "🚀 开始部署Novalon网站到服务器 $SERVER_IP"
echo "📁 部署根目录: $DEPLOY_ROOT"
echo "📁 项目目录: $PROJECT_DIR"
echo "🐳 容器名称: $CONTAINER_NAME"
echo "📦 版本号: $VERSION"
echo "📋 部署日志: $LOG_FILE"
echo ""
echo "📋 步骤0: 部署前检查..."
for file in docker-compose.yml Dockerfile nginx-static.conf .env.example setup-ssl.sh; do
if [ ! -f "$file" ]; then
echo "❌ 缺少必要文件: $file"
exit 1
fi
done
if ! command -v docker-compose &> /dev/null; then
echo "❌ 本地docker-compose不可用"
exit 1
fi
echo "✅ 部署前检查通过"
echo ""
echo "📋 步骤1: 验证SSH连接..."
if ! ssh -o ConnectTimeout=5 "$SERVER_USER@$SERVER_IP" exit; then
echo "❌ 无法连接到服务器 $SERVER_IP"
exit 1
fi
if ! ssh "$SERVER_USER@$SERVER_IP" "docker --version"; then
echo "❌ 服务器上Docker不可用"
exit 1
fi
echo "✅ SSH连接验证成功"
echo ""
echo "📋 步骤2: 上传部署文件..."
ssh "$SERVER_USER@$SERVER_IP" "mkdir -p '$PROJECT_DIR'"
scp -r docker-compose.yml Dockerfile nginx-static.conf .env.example setup-ssl.sh "$SERVER_USER@$SERVER_IP:$PROJECT_DIR/"
echo "✅ 部署文件已上传"
echo ""
echo "📋 步骤3: 在服务器上执行部署..."
ssh "$SERVER_USER@$SERVER_IP" << ENDSSH
cd '$PROJECT_DIR'
echo "🔒 配置SSL证书..."
chmod +x setup-ssl.sh
./setup-ssl.sh
echo "📋 检查环境变量文件..."
if [ ! -f .env ]; then
echo "📝 创建.env文件..."
cp .env.example .env
echo "⚠️ 请编辑.env文件,填入正确的环境变量"
echo "⚠️ 可选配置: NEXT_PUBLIC_GA_ID"
fi
echo "🐳 启动Docker容器..."
docker-compose down
docker-compose build --no-cache
docker-compose up -d
echo "📋 等待服务启动..."
timeout=60
elapsed=0
check_interval=3
while [ $elapsed -lt $timeout ]; do
if docker inspect --format='{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null | grep -q "running"; then
if curl -f -s -o /dev/null "http://localhost:80" --max-time 5 2>/dev/null; then
echo "✅ 服务已启动并响应正常"
break
else
echo "⏳ 容器运行中,等待服务响应..."
fi
else
echo "⏳ 等待容器启动..."
fi
sleep $check_interval
elapsed=$((elapsed + check_interval))
done
if [ $elapsed -ge $timeout ]; then
echo "❌ 服务启动超时"
echo "📋 当前容器状态:"
docker ps -a | grep "$CONTAINER_NAME"
echo "📋 容器日志:"
docker logs "$CONTAINER_NAME" --tail 20
exit 1
fi
echo "📋 检查容器状态..."
docker ps | grep "$CONTAINER_NAME"
echo "📋 检查容器日志..."
if docker logs "$CONTAINER_NAME" --tail 50 2>/dev/null; then
echo "✅ 容器日志检查完成"
else
echo "⚠️ 容器日志为空或无法访问"
fi
echo "📋 配置SSL证书自动续期..."
if ! crontab -l | grep -q "certbot renew"; then
if ! (crontab -l 2>/dev/null; echo "0 0,12 * * * certbot renew --quiet --post-hook 'docker restart $NGINX_CONTAINER_NAME'") | crontab -; then
echo "❌ SSL证书自动续期任务配置失败"
exit 1
fi
echo "✅ SSL证书自动续期任务已配置"
else
echo "✅ SSL证书自动续期任务已存在"
fi
echo "✅ 部署完成!"
ENDSSH
echo ""
echo "📋 步骤4: 部署后验证..."
if ! ssh "$SERVER_USER@$SERVER_IP" "docker ps | grep -q '$CONTAINER_NAME'"; then
echo "❌ 容器未运行"
exit 1
fi
if ! curl -f -s -o /dev/null "http://$SERVER_IP" --max-time 10; then
echo "⚠️ HTTP服务响应异常"
else
echo "✅ HTTP服务正常"
fi
if ! curl -f -s -o /dev/null "https://$SERVER_IP" --max-time 10; then
echo "⚠️ HTTPS服务响应异常"
else
echo "✅ HTTPS服务正常"
fi
echo "✅ 部署后验证通过"
echo ""
echo "🎉 部署脚本执行完成!"
echo "📋 访问地址:"
echo " HTTP: http://$SERVER_IP"
echo " HTTPS: https://$SERVER_IP"
echo " 域名: https://novalon.cn"
echo ""
echo "📋 后续步骤:"
echo " 1. 验证网站可访问性"
echo " 2. 检查容器运行状态: docker ps"
echo " 3. 查看容器日志: docker logs $CONTAINER_NAME"
echo " 4. 验证HTTPS配置"
echo " 5. 测试网站主要功能"
echo " 6. 检查SSL证书自动续期: crontab -l"
+1 -4
View File
@@ -134,10 +134,7 @@ export COS_REGION="ap-chengdu"
``` ```
3. 运行部署脚本: 3. 运行部署脚本:
```bash > ⚠️ `scripts/deploy-cdn.sh` 尚未实现;当前统一发布脚本 `scripts/deploy.sh` 只负责构建与服务器静态发布,不包含 COS 上传。COS 上传请先使用下方手动方式,或后续按 `coscmd` 命令封装独立脚本。
chmod +x scripts/deploy-cdn.sh
./scripts/deploy-cdn.sh
```
#### 方法2: 手动上传 #### 方法2: 手动上传
+2 -2
View File
@@ -49,8 +49,8 @@ cp .env.production .env.local
### 2. 运行部署脚本 ### 2. 运行部署脚本
```bash ```bash
chmod +x scripts/deploy-production.sh # 统一发布脚本(构建 + 发布)
./scripts/deploy-production.sh ./scripts/deploy.sh deploy
``` ```
### 3. 验证部署 ### 3. 验证部署
+2 -2
View File
@@ -493,8 +493,8 @@ chmod +x scripts/setup-lightweight-monitoring.sh
```bash ```bash
# 运行生产部署脚本 # 运行生产部署脚本
chmod +x scripts/deploy-production.sh # 统一发布脚本(构建 + 发布)
./scripts/deploy-production.sh ./scripts/deploy.sh deploy
``` ```
这个脚本会: 这个脚本会:
+14 -17
View File
@@ -85,35 +85,32 @@
- `ssl/` -> `/etc/nginx/ssl`(只读) - `ssl/` -> `/etc/nginx/ssl`(只读)
- `logs/` -> `/var/log/nginx` - `logs/` -> `/var/log/nginx`
- `../certbot` -> `/var/www/certbot`ACME 验证) - `../certbot` -> `/var/www/certbot`ACME 验证)
- `../novalon-static` -> `/var/www/novalon`(只读,静态文件) - `../novalon-website/dist` -> `/var/www/novalon`(只读,Novalon 主站静态文件)
- `../novalon-static` -> `/var/www/novavis-landing`(只读,Novavis 落地页)
- 使用外部网络 `novalon-network` - 使用外部网络 `novalon-network`
--- ---
### 三、部署脚本(2 个) ### 三、部署脚本(1统一入口
#### 9. [deploy.sh](computer:///sessions/69e889f08f8c93c2514713f1/workspace/deploy.sh) #### 9. [scripts/deploy.sh](../scripts/deploy.sh)
**用途**: 完整部署脚本 -- SSH 到远程服务器执行 Docker 构建+部署 **用途**: 统一发布脚本(唯一发布入口),负责构建、发布、回滚与状态查看
**关键内容**: **关键内容**:
- 默认服务器 `139.155.109.62`root 用户),部署路径 `/home/novalon/docker-app/novalon-website` - 默认服务器 `139.155.109.62`root 用户),远程目标目录 `/home/novalon/docker-app/novalon-website/dist`(生产 Nginx 实际挂载目录)
- 容器名 `novalon-website`Nginx 容器名 `novalon-nginx` - 命令:
- 流程: 部署前检查 -> SSH 验证 -> SCP 上传文件 -> 服务器端 SSL 配置 -> `docker-compose build --no-cache` -> `docker-compose up -d` -> 健康检查 -> SSL 自动续期 cron 配置 - `build`:构建静态产物
- 支持命令行参数: `-i`(IP)、`-u`(用户)、`-p`(项目名)、`-c`(容器名)、`-v`(版本号) - `deploy`:构建 + 远程发布(备份旧版本 -> rsync 上传 -> 设置权限 -> 重载 Nginx -> 验证页面)
- `rollback`:回滚到最近一次远程备份
#### 10. [deploy-dist.sh](computer:///sessions/69e889f08f8c93c2514713f1/workspace/deploy-dist.sh) - `status`:查看生产环境发布状态
**用途**: 轻量部署脚本 -- 仅上传预构建的 `dist/` 目录到服务器。 - 支持 `--skip-build``--auto-rollback``--dry-run` 及服务器/域名/备份保留数等参数
- 支持环境变量覆盖:`SERVER_IP``SERVER_USER``DEPLOY_ROOT``PROJECT_NAME``NGINX_CONTAINER``DOMAIN``BACKUP_RETENTION_COUNT``DIST_DIR`
**关键内容**:
- 同一服务器 `139.155.109.62`,部署到 `/home/novalon/docker-app/novalon-website/dist`
- 流程: 检查 dist 目录 -> SSH 验证 -> 备份旧版本 -> rsync 上传 -> 设置文件权限 -> 验证 -> 清理旧备份(保留最近 3 个)
- 适用于已有 Nginx 容器运行、仅需更新静态文件的场景
--- ---
### 四、引用 "novalon-nginx" / "novalon-website" 的其他文件(29 个) ### 四、引用 "novalon-nginx" / "novalon-website" 的其他文件(29 个)
除上述 10 个核心文件外,以下文件也引用了这两个关键词,按类别归纳: 除上述 9 个核心文件外,以下文件也引用了这两个关键词,按类别归纳:
| 类别 | 文件路径 | | 类别 | 文件路径 |
|------|----------| |------|----------|
+7 -7
View File
@@ -18,16 +18,16 @@
│ novalon-website │ │ novalon-website │
│ - 构建命令: npm run build:clean │ │ - 构建命令: npm run build:clean │
│ - 输出目录: dist/ │ │ - 输出目录: dist/ │
│ - 部署命令: ./deploy-dist.sh │ - 部署命令: ./scripts/deploy.sh deploy
└──────────────────────┬──────────────────────────────┘ └──────────────────────┬──────────────────────────────┘
│ rsync + SSH (root@139.155.109.62) │ rsync + SSH (root@139.155.109.62)
┌─────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────┐
│ 生产服务器 (139.155.109.62) │ │ 生产服务器 (139.155.109.62) │
│ └── /home/novalon/docker-app/ │ │ └── /home/novalon/docker-app/ │
│ ├── novalon-static/ ← 当前版本 │ │ ├── novalon-website/dist/ ← 当前版本 │
│ ├── novalon-static_backup_* ← 自动备份 │ ├── novalon-website/dist_backup_* ← 自动备份 │
│ └── docker-compose.yml │ └── novalon-nginx/
│ └── novalon-nginx-secure ← Nginx 容器 │ │ └── novalon-nginx-secure ← Nginx 容器 │
└──────────────────────┬──────────────────────────────┘ └──────────────────────┬──────────────────────────────┘
│ 反向代理 │ 反向代理
@@ -226,7 +226,7 @@ else
fi fi
# 4.3 部署到生产环境 # 4.3 部署到生产环境
./deploy-dist.sh ./scripts/deploy.sh deploy --skip-build
# 脚本会自动执行: # 脚本会自动执行:
# 1. 检查 dist 目录 # 1. 检查 dist 目录
@@ -551,7 +551,7 @@ git reset --hard HEAD
║ 2. git checkout <stable-commit-hash> ║ ║ 2. git checkout <stable-commit-hash> ║
║ # 切换到稳定版本 ║ ║ # 切换到稳定版本 ║
║ ║ ║ ║
║ 3. ./deploy-dist.sh ║ 3. ./scripts/deploy.sh deploy --skip-build
║ # 重新构建并部署 ║ ║ # 重新构建并部署 ║
║ ║ ║ ║
║ 验证命令: ║ ║ 验证命令: ║
@@ -598,7 +598,7 @@ git reset --hard HEAD
## 📚 **相关文档链接** ## 📚 **相关文档链接**
- **部署脚本**: [deploy-dist.sh](./deploy-dist.sh) - **部署脚本**: [scripts/deploy.sh](../../scripts/deploy.sh)
- **Jenkins Pipeline**: [Jenkinsfile](./Jenkinsfile) - **Jenkins Pipeline**: [Jenkinsfile](./Jenkinsfile)
- **Sentry 监控指南**: [docs/sentry-setup-guide.md](./docs/sentry-setup-guide.md) - **Sentry 监控指南**: [docs/sentry-setup-guide.md](./docs/sentry-setup-guide.md)
- **技术栈降级指南**: [scripts/downgrade-stack.sh](./scripts/downgrade-stack.sh) - **技术栈降级指南**: [scripts/downgrade-stack.sh](./scripts/downgrade-stack.sh)
+2 -2
View File
@@ -136,8 +136,8 @@ NEXT_PUBLIC_GA_MEASUREMENT_ID=G-LGTLCR15KM
```bash ```bash
npm run build:clean npm run build:clean
# 然后执行部署脚本 # 然后执行统一发布脚本(跳过重复构建)
./deploy-dist.sh ./scripts/deploy.sh deploy --skip-build
``` ```
### 3. 监控错误报告 ### 3. 监控错误报告
+2 -2
View File
@@ -245,8 +245,8 @@ SENTRY_DSN=你的真实SENTRY_DSN
### 7.2 重新构建和部署 ### 7.2 重新构建和部署
```bash ```bash
# 回到本地开发机 # 回到本地开发机,执行统一发布脚本
./deploy-dist.sh ./scripts/deploy.sh deploy
``` ```
Jenkins Pipeline 会自动: Jenkins Pipeline 会自动:
+5 -1
View File
@@ -51,7 +51,11 @@
"lighthouse:upload": "lhci upload", "lighthouse:upload": "lhci upload",
"lighthouse:desktop": "lhci autorun --settings.preset=desktop", "lighthouse:desktop": "lhci autorun --settings.preset=desktop",
"lighthouse:mobile": "lhci autorun --settings.preset=mobile", "lighthouse:mobile": "lhci autorun --settings.preset=mobile",
"prepare": "husky" "prepare": "husky",
"deploy": "bash scripts/deploy.sh deploy",
"deploy:skip-build": "bash scripts/deploy.sh deploy --skip-build",
"deploy:rollback": "bash scripts/deploy.sh rollback",
"deploy:status": "bash scripts/deploy.sh status"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1089.0", "@aws-sdk/client-s3": "^3.1089.0",
-84
View File
@@ -1,84 +0,0 @@
#!/bin/bash
# Novalon 静态站点部署脚本
# 用法: bash scripts/deploy-static.sh [环境]
# 环境参数: production (默认)
set -euo pipefail
ENV="${1:-production}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="$PROJECT_DIR/dist"
DEPLOY_DIR="/var/www/novalon"
BACKUP_DIR="/var/www/novalon-backup"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "========================================="
echo " Novalon 静态站点部署"
echo " 环境: $ENV"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================="
# 步骤 1: 构建
echo ""
echo "[1/5] 构建静态站点..."
cd "$PROJECT_DIR"
npm run build
if [ ! -d "$BUILD_DIR" ]; then
echo "❌ 构建失败:dist 目录不存在"
exit 1
fi
echo "✅ 构建完成,产物大小:$(du -sh "$BUILD_DIR" | cut -f1)"
# 步骤 2: 备份当前版本
echo ""
echo "[2/5] 备份当前版本..."
if [ -d "$DEPLOY_DIR" ]; then
mkdir -p "$BACKUP_DIR"
cp -r "$DEPLOY_DIR" "$BACKUP_DIR/novalon-backup-$TIMESTAMP"
# 只保留最近 3 个备份
ls -t "$BACKUP_DIR"/ | tail -n +4 | xargs -I {} rm -rf "$BACKUP_DIR/{}"
echo "✅ 备份完成: $BACKUP_DIR/novalon-backup-$TIMESTAMP"
else
echo "⚠️ 首次部署,无需备份"
mkdir -p "$DEPLOY_DIR"
fi
# 步骤 3: 部署新版本
echo ""
echo "[3/5] 部署新版本..."
# 清空目标目录(保留 .well-known
if [ -d "$DEPLOY_DIR/.well-known" ]; then
mv "$DEPLOY_DIR/.well-known" /tmp/well-known-backup
fi
rm -rf "$DEPLOY_DIR"/*
cp -r "$BUILD_DIR"/* "$DEPLOY_DIR/"
if [ -d /tmp/well-known-backup ]; then
mv /tmp/well-known-backup "$DEPLOY_DIR/.well-known"
fi
echo "✅ 文件部署完成"
# 步骤 4: 设置权限
echo ""
echo "[4/5] 设置文件权限..."
chmod -R 755 "$DEPLOY_DIR"
echo "✅ 权限设置完成"
# 步骤 5: 重载 Nginx
echo ""
echo "[5/5] 重载 Nginx..."
if nginx -t 2>/dev/null; then
nginx -s reload
echo "✅ Nginx 重载成功"
else
echo "⚠️ Nginx 配置检查失败,跳过重载(请手动检查)"
fi
echo ""
echo "========================================="
echo " ✅ 部署完成!"
echo " 站点地址: https://www.novalon.cn"
echo " 部署目录: $DEPLOY_DIR"
echo "========================================="
+405
View File
@@ -0,0 +1,405 @@
#!/usr/bin/env bash
# ============================================================
# Novalon 统一发布脚本
#
# 用法:
# ./scripts/deploy.sh [命令] [选项]
#
# 命令:
# build 构建静态产物 (npm run build / npm run build:clean)
# deploy 构建并发布到生产服务器 (默认命令)
# rollback 回滚到最近一次远程备份
# status 查看生产环境发布状态
# help 显示帮助
#
# 选项:
# --skip-build 跳过本地构建,直接使用现有 dist/
# --auto-rollback 部署/验证失败时自动回滚到最近备份
# --clean 构建前清理 .next 与 dist
# --server-ip IP 服务器地址 (默认 139.155.109.62)
# --server-user USER SSH 用户 (默认 root)
# --deploy-root PATH 服务器部署根目录 (默认 /home/novalon/docker-app)
# --project-name NAME 服务器项目目录名 (默认 novalon-website)
# --nginx-container C Nginx 容器名 (默认 novalon-nginx-secure)
# --domain URL 验证域名 (默认 https://novalon.cn)
# --backup-retention N 远程备份保留份数 (默认 3)
# --dist-dir DIR 本地构建产物目录 (默认 dist)
# --dry-run 只打印远程命令,不实际执行
# -h, --help 显示帮助
#
# 环境变量覆盖:
# SERVER_IP SERVER_USER DEPLOY_ROOT PROJECT_NAME NGINX_CONTAINER
# DOMAIN BACKUP_RETENTION_COUNT DIST_DIR
# ============================================================
set -euo pipefail
# ---------- 默认配置 ----------
SERVER_IP="${SERVER_IP:-139.155.109.62}"
SERVER_USER="${SERVER_USER:-root}"
DEPLOY_ROOT="${DEPLOY_ROOT:-/home/novalon/docker-app}"
PROJECT_NAME="${PROJECT_NAME:-novalon-website}"
NGINX_CONTAINER="${NGINX_CONTAINER:-novalon-nginx-secure}"
DOMAIN="${DOMAIN:-https://novalon.cn}"
BACKUP_RETENTION="${BACKUP_RETENTION_COUNT:-${BACKUP_RETENTION:-3}}"
DIST_DIR="${DIST_DIR:-dist}"
REMOTE_PROJECT_DIR="$DEPLOY_ROOT/$PROJECT_NAME"
REMOTE_DIST_DIR="$REMOTE_PROJECT_DIR/dist"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
LOG_DIR="$PROJECT_ROOT/logs"
LOG_FILE="$LOG_DIR/deploy_$(date +%Y%m%d_%H%M%S).log"
# ---------- 运行时状态 ----------
COMMAND="${1:-deploy}"
SKIP_BUILD=0
AUTO_ROLLBACK=0
CLEAN_BUILD=0
DRY_RUN=0
DEPLOY_STARTED=0
# ---------- 工具函数 ----------
usage() {
awk '
NR == 1 { next }
/^# =+$/ { c++; if (c == 2) exit }
{ line = $0; sub(/^# ?/, "", line); print line }
' "$0"
}
log() {
echo -e "$@"
}
require_local_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
log "❌ 缺少本地命令: $cmd"
exit 1
fi
}
run_remote() {
# 用法: run_remote <远程命令字符串>
# 远程命令会在目标服务器的 bash 中执行
local remote_cmd="$1"
if [[ "$DRY_RUN" == "1" ]]; then
log "🔍 [dry-run] ssh $SERVER_USER@$SERVER_IP \"$remote_cmd\""
return 0
fi
ssh -o ConnectTimeout=8 -o BatchMode=yes "$SERVER_USER@$SERVER_IP" "$remote_cmd"
}
verify_remote() {
log ""
log "=========================================="
log "📋 步骤 7: 验证生产环境"
log "=========================================="
local pages=("/" "/about" "/contact" "/products" "/services")
local failed=0
for page in "${pages[@]}"; do
local url="${DOMAIN}${page}"
local status
status=$(curl -fsS -o /dev/null -w '%{http_code}' -L --connect-timeout 8 --max-time 15 "$url" 2>/dev/null || echo "000")
if [[ "$status" == "200" ]]; then
log "$page ($status)"
else
log "$page ($status)"
failed=1
fi
done
if [[ "$failed" == "1" ]]; then
log "❌ 生产环境验证失败"
return 1
fi
log "✅ 生产环境验证通过"
}
# ---------- 命令实现 ----------
cmd_build() {
log "=========================================="
log "📋 构建静态站点"
log "=========================================="
cd "$PROJECT_ROOT"
if [[ "$CLEAN_BUILD" == "1" ]]; then
npm run build:clean
else
npm run build
fi
if [[ ! -d "$DIST_DIR" ]]; then
log "❌ 构建失败:$DIST_DIR 目录不存在"
exit 1
fi
log "✅ 构建完成,产物大小: $(du -sh "$DIST_DIR" | cut -f1)"
}
cmd_deploy() {
log "=========================================="
log "🚀 Novalon 统一发布"
log " 目标: $SERVER_USER@$SERVER_IP:$REMOTE_DIST_DIR"
log " 域名: $DOMAIN"
log "=========================================="
if [[ "$SKIP_BUILD" != "1" ]]; then
cmd_build
elif [[ ! -d "$DIST_DIR" ]]; then
log "$DIST_DIR 目录不存在,请先构建或去掉 --skip-build"
exit 1
fi
require_local_cmd ssh
require_local_cmd rsync
require_local_cmd curl
# 步骤 1: 检查本地产物
log ""
log "📋 步骤 1: 检查构建产物"
DIST_SIZE=$(du -sh "$DIST_DIR" | cut -f1)
log "$DIST_DIR 目录存在,大小: $DIST_SIZE"
# 步骤 2: 验证 SSH 连接
log ""
log "📋 步骤 2: 验证 SSH 连接"
if ! run_remote "exit"; then
log "❌ 无法连接到服务器 $SERVER_IP"
exit 1
fi
log "✅ SSH 连接验证成功"
# 步骤 3: 远程备份旧版本
log ""
log "📋 步骤 3: 备份服务器上的旧版本"
run_remote "
set -e
PROJECT_DIR='$REMOTE_PROJECT_DIR'
DIST_PATH='$REMOTE_DIST_DIR'
if [ -d \"\$DIST_PATH\" ]; then
TIMESTAMP=\$(date +%Y%m%d_%H%M%S)
BACKUP_PATH=\"\$PROJECT_DIR/dist_backup_\$TIMESTAMP\"
cp -r \"\$DIST_PATH\" \"\$BACKUP_PATH\"
echo \"✅ 备份完成: \$BACKUP_PATH\"
else
echo \"️ 首次部署,无需备份\"
fi
"
DEPLOY_STARTED=1
# 步骤 4: 清理旧备份(保留最近 N 份)
log ""
log "📋 步骤 4: 清理旧备份(保留最近 ${BACKUP_RETENTION} 份)"
run_remote "
set -e
PROJECT_DIR='$REMOTE_PROJECT_DIR'
cd \"\$PROJECT_DIR\" || exit 0
ls -dt ${REMOTE_PROJECT_DIR}/dist_backup_* 2>/dev/null | tail -n +$((BACKUP_RETENTION + 1)) | xargs rm -rf 2>/dev/null || true
echo \"✅ 清理完成\"
"
# 步骤 5: 上传 dist 目录
log ""
log "📋 步骤 5: 上传 dist 目录"
if [[ "$DRY_RUN" == "1" ]]; then
log "🔍 [dry-run] rsync -avz --delete \"$DIST_DIR/\" $SERVER_USER@$SERVER_IP:$REMOTE_DIST_DIR/"
else
ssh "$SERVER_USER@$SERVER_IP" "mkdir -p '$REMOTE_DIST_DIR'"
rsync -avz --delete "$DIST_DIR/" "$SERVER_USER@$SERVER_IP:$REMOTE_DIST_DIR/"
fi
log "✅ dist 目录已上传"
# 步骤 6: 设置权限并重载 Nginx
log ""
log "📋 步骤 6: 设置权限并重载 Nginx"
run_remote "
set -e
DIST_PATH='$REMOTE_DIST_DIR'
chown -R www-data:www-data \"\$DIST_PATH\" 2>/dev/null || true
chmod -R 755 \"\$DIST_PATH\"
echo \"✅ 权限设置完成\"
"
run_remote "
set -e
if docker exec $NGINX_CONTAINER nginx -t 2>/dev/null; then
docker exec $NGINX_CONTAINER nginx -s reload
echo \"✅ Nginx 重载成功\"
else
echo \"⚠️ Nginx 配置检测失败,重启容器...\"
docker restart $NGINX_CONTAINER
sleep 3
echo \"✅ Nginx 容器已重启\"
fi
"
# 步骤 7: 验证生产环境
if ! verify_remote; then
if [[ "$AUTO_ROLLBACK" == "1" ]]; then
log ""
log "🔄 自动回滚到最近备份..."
cmd_rollback
fi
exit 1
fi
log ""
log "🎉 部署成功!"
log "📋 访问地址: $DOMAIN"
}
cmd_rollback() {
log "=========================================="
log "🔄 回滚到最近一次远程备份"
log "=========================================="
require_local_cmd ssh
require_local_cmd curl
if ! run_remote "exit"; then
log "❌ 无法连接到服务器 $SERVER_IP"
exit 1
fi
log "📋 查找最近备份..."
LATEST_BACKUP=$(run_remote "
ls -dt ${REMOTE_PROJECT_DIR}/dist_backup_* 2>/dev/null | head -1
" | tr -d '\r')
if [[ -z "$LATEST_BACKUP" ]]; then
log "❌ 未找到任何备份,无法回滚"
exit 1
fi
log "✅ 最近备份: $LATEST_BACKUP"
run_remote "
set -e
PROJECT_DIR='$REMOTE_PROJECT_DIR'
DIST_PATH='$REMOTE_DIST_DIR'
BACKUP_PATH='$LATEST_BACKUP'
rm -rf \"\$DIST_PATH\"
cp -r \"\$BACKUP_PATH\" \"\$DIST_PATH\"
chown -R www-data:www-data \"\$DIST_PATH\" 2>/dev/null || true
chmod -R 755 \"\$DIST_PATH\"
echo \"✅ 已从备份恢复: \$BACKUP_PATH\"
"
run_remote "
set -e
if docker exec $NGINX_CONTAINER nginx -t 2>/dev/null; then
docker exec $NGINX_CONTAINER nginx -s reload
echo \"✅ Nginx 重载成功\"
else
docker restart $NGINX_CONTAINER
sleep 3
echo \"✅ Nginx 容器已重启\"
fi
"
if verify_remote; then
log ""
log "🎉 回滚成功!"
else
log "❌ 回滚后验证失败,请手动介入"
exit 1
fi
}
cmd_status() {
log "=========================================="
log "📋 生产环境发布状态"
log "=========================================="
require_local_cmd ssh
if ! run_remote "exit"; then
log "❌ 无法连接到服务器 $SERVER_IP"
exit 1
fi
run_remote "
echo \"--- Nginx 容器 ---\"
docker ps --filter name=$NGINX_CONTAINER --format '{{.Names}} {{.Status}}' || echo '❌ 容器未运行'
echo
echo \"--- 远程 dist 目录 ---\"
ls -ld '$REMOTE_DIST_DIR' 2>/dev/null || echo '❌ 目录不存在'
echo \"文件数: \$(find '$REMOTE_DIST_DIR' -type f 2>/dev/null | wc -l)\"
echo
echo \"--- 最近修改 ---\"
find '$REMOTE_DIST_DIR' -maxdepth 1 -type f -printf '%T@ %f\n' 2>/dev/null | sort -rn | head -5
echo
echo \"--- 可用备份 ---\"
ls -dt ${REMOTE_PROJECT_DIR}/dist_backup_* 2>/dev/null | head -${BACKUP_RETENTION} || echo '无备份'
"
}
on_error() {
local rc=$?
if [[ "$COMMAND" == "deploy" && "$AUTO_ROLLBACK" == "1" && "$DEPLOY_STARTED" == "1" ]]; then
log ""
log "❌ 部署失败(exit=$rc),执行自动回滚..."
cmd_rollback >&2 || true
fi
exit "$rc"
}
trap on_error ERR
# ---------- 参数解析 ----------
case "$COMMAND" in
build|deploy|rollback|status|help|-h|--help) ;;
*)
log "❌ 未知命令: $COMMAND"
usage
exit 1
;;
esac
if [[ "$COMMAND" == "help" || "$COMMAND" == "-h" || "$COMMAND" == "--help" ]]; then
usage
exit 0
fi
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--auto-rollback) AUTO_ROLLBACK=1; shift ;;
--clean) CLEAN_BUILD=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--server-ip) SERVER_IP="$2"; shift 2 ;;
--server-user) SERVER_USER="$2"; shift 2 ;;
--deploy-root) DEPLOY_ROOT="$2"; shift 2 ;;
--project-name) PROJECT_NAME="$2"; shift 2 ;;
--nginx-container) NGINX_CONTAINER="$2"; shift 2 ;;
--domain) DOMAIN="$2"; shift 2 ;;
--backup-retention) BACKUP_RETENTION="$2"; shift 2 ;;
--dist-dir) DIST_DIR="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*)
log "❌ 未知参数: $1"
usage
exit 1
;;
esac
done
REMOTE_PROJECT_DIR="$DEPLOY_ROOT/$PROJECT_NAME"
REMOTE_DIST_DIR="$REMOTE_PROJECT_DIR/dist"
# ---------- 日志 ----------
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
log "📋 部署日志: $LOG_FILE"
log ""
# ---------- 执行 ----------
case "$COMMAND" in
build) cmd_build ;;
deploy) cmd_deploy ;;
rollback) cmd_rollback ;;
status) cmd_status ;;
esac
-27
View File
@@ -1,27 +0,0 @@
#!/bin/bash
set -e
echo "🚀 开始部署到生产环境..."
export NODE_ENV=production
if [ ! -d "node_modules" ]; then
echo "📦 安装依赖..."
npm ci --production=false
fi
echo "🔨 构建静态网站..."
npm run build
if [ -d "dist_backup" ]; then
rm -rf dist_backup
fi
if [ -d "dist" ]; then
echo "💾 备份当前版本..."
mv dist dist_backup
fi
echo "✅ 构建完成!"
echo "📊 静态文件位于 dist/ 目录"
echo "🌐 可部署到 Nginx、CDN 或任何静态托管服务"
+2 -1
View File
@@ -20,7 +20,8 @@ JENKINS_API_TOKEN=""
SERVER_IP="139.155.109.62" SERVER_IP="139.155.109.62"
SERVER_USER="root" SERVER_USER="root"
DEPLOY_ROOT="/home/novalon/docker-app" DEPLOY_ROOT="/home/novalon/docker-app"
STATIC_DIR="novalon-static" PROJECT_NAME="novalon-website"
DIST_DIR="dist"
NGINX_CONTAINER="novalon-nginx-secure" NGINX_CONTAINER="novalon-nginx-secure"
DOMAIN="https://novalon.cn" DOMAIN="https://novalon.cn"
+1 -1
View File
@@ -5,7 +5,7 @@ set -e
echo "╔════════════════════════════════════════════╗" echo "╔════════════════════════════════════════════╗"
echo "║ 🚀 Novalon Website CI/CD 自动配置工具 ║" echo "║ 🚀 Novalon Website CI/CD 自动配置工具 ║"
echo "║ Version: 1.0.0 ║" echo "║ Version: 1.0.0 ║"
echo "║ Mode: Docker + Shell (参考 deploy-dist.sh) ║" echo "║ Mode: Docker + Shell (参考 scripts/deploy.sh) ║"
echo "╚════════════════════════════════════════════╝" echo "╚════════════════════════════════════════════╝"
echo "" echo ""