Dev #21
@@ -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
@@ -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
@@ -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 "🚨 需要立即手动介入!"
|
||||||
|
|||||||
@@ -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
@@ -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. 测试网站主要功能"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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: 手动上传
|
||||||
|
|
||||||
|
|||||||
@@ -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. 验证部署
|
||||||
|
|||||||
@@ -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
@@ -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 个核心文件外,以下文件也引用了这两个关键词,按类别归纳:
|
||||||
|
|
||||||
| 类别 | 文件路径 |
|
| 类别 | 文件路径 |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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. 监控错误报告
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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 "========================================="
|
|
||||||
Executable
+405
@@ -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
|
||||||
@@ -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 或任何静态托管服务"
|
|
||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user