build(ci/cd): add full automated CI/CD setup scripts
新增了完整的Novalon网站CI/CD自动化配置工具链,包括: 1. 一键启动脚本、主配置脚本与配置文件 2. Jenkins容器部署、Gitea Webhook配置、环境验证脚本 3. 完善的中文文档与使用示例 4. 优化了Jenkinsfile流水线,新增构建保留、并发限制、备份清理等功能
This commit is contained in:
BIN
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Vendored
+240
-231
@@ -11,17 +11,18 @@ pipeline {
|
|||||||
STATIC_DIR = 'novalon-static'
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
options {
|
options {
|
||||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '5'))
|
||||||
timeout(time: 30, unit: 'MINUTES')
|
timeout(time: 45, unit: 'MINUTES')
|
||||||
timestamps()
|
timestamps()
|
||||||
ansiColor('xterm')
|
ansiColor('xterm')
|
||||||
|
disableConcurrentBuilds()
|
||||||
}
|
}
|
||||||
|
|
||||||
triggers {
|
triggers {
|
||||||
// Gitea Webhook 触发
|
|
||||||
GenericTrigger(
|
GenericTrigger(
|
||||||
genericVariables: [
|
genericVariables: [
|
||||||
[key: 'ref', value: '$.ref'],
|
[key: 'ref', value: '$.ref'],
|
||||||
@@ -30,16 +31,13 @@ pipeline {
|
|||||||
[key: 'commit_message', value: '$.commits[0].message']
|
[key: 'commit_message', value: '$.commits[0].message']
|
||||||
],
|
],
|
||||||
token: '${PROJECT_NAME}-ci-token',
|
token: '${PROJECT_NAME}-ci-token',
|
||||||
causeString: 'Triggered by $ref on $repository',
|
causeString: 'Triggered by $ref on $repository (Commit: $commit_sha)',
|
||||||
printContributedVariables: true,
|
printContributedVariables: true,
|
||||||
printPostContent: true,
|
printPostContent: true,
|
||||||
silentResponse: false,
|
silentResponse: false,
|
||||||
regexpFilterText: '$ref$repository',
|
regexpFilterText: '$ref',
|
||||||
regexpFilterExpression: '^(refs/heads/main|refs/heads/develop).*$'
|
regexpFilterExpression: '^(refs/heads/main|refs/heads/develop)$'
|
||||||
)
|
)
|
||||||
|
|
||||||
// 每天凌晨 3 点定时检查依赖安全漏洞
|
|
||||||
pollSCM('H 3 * * *')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters {
|
parameters {
|
||||||
@@ -51,161 +49,149 @@ pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
stage('🔧 环境准备') {
|
stage('🔧 环境检测与准备') {
|
||||||
steps {
|
steps {
|
||||||
echo "📦 Node.js 版本: ${NODE_VERSION}"
|
echo "=========================================="
|
||||||
echo "🌐 目标域名: ${DOMAIN}"
|
echo "🚀 Novalon Website CI/CD Pipeline"
|
||||||
echo "🖥️ 生产服务器: ${SERVER_USER}@${SERVER_IP}"
|
echo "🐳 Docker Shell Mode (参考 deploy-dist.sh)"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
sh '''
|
||||||
|
echo "🔍 检测环境依赖..."
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- 系统信息 ---"
|
||||||
|
uname -a
|
||||||
|
whoami
|
||||||
|
pwd
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- 工具版本检查 ---"
|
||||||
|
node --version 2>/dev/null || echo "❌ Node.js 未安装"
|
||||||
|
npm --version 2>/dev/null || echo "❌ npm 未安装"
|
||||||
|
git --version || echo "❌ Git 未安装"
|
||||||
|
ssh -V || echo "❌ SSH 未安装"
|
||||||
|
rsync --version | head -1 || echo "❌ rsync 未安装"
|
||||||
|
curl --version | head -1 || echo "❌ curl 未安装"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- SSH 连接测试 ---"
|
||||||
|
if ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 ${SERVER_USER}@${SERVER_IP} "hostname && whoami"; then
|
||||||
|
echo "✅ SSH 连接成功"
|
||||||
|
else
|
||||||
|
echo "❌ SSH 连接失败!请检查:"
|
||||||
|
echo " 1. SSH 密钥是否已挂载到 Jenkins 容器"
|
||||||
|
echo " 2. 生产服务器的 authorized_keys 是否包含公钥"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- npm registry 测试 ---"
|
||||||
|
npm config get registry || npm config set registry ${NPM_REGISTRY}
|
||||||
|
'''
|
||||||
|
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||||
sh 'node --version'
|
sh 'node --version && npm --version'
|
||||||
sh 'npm --version'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('📥 安装依赖') {
|
stage('📥 安装依赖') {
|
||||||
steps {
|
steps {
|
||||||
|
checkout scm
|
||||||
|
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||||
sh '''
|
sh '''
|
||||||
rm -rf node_modules package-lock.json
|
echo "📦 安装项目依赖..."
|
||||||
npm ci --registry=${NPM_REGISTRY} --prefer-offline
|
|
||||||
|
if [ -f "package-lock.json" ]; then
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm ci --registry=${NPM_REGISTRY} --prefer-offline --no-audit --no-fund || {
|
||||||
|
echo "⚠️ npm ci 失败,尝试 npm install..."
|
||||||
|
npm install --registry=${NPM_REGISTRY} --legacy-peer-deps
|
||||||
|
}
|
||||||
|
else
|
||||||
|
rm -rf node_modules
|
||||||
|
npm install --registry=${NPM_REGISTRY} --legacy-peer-deps
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ 依赖安装完成"
|
||||||
|
du -sh node_modules | awk '{print "📊 大小: " $1}'
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
|
||||||
failure {
|
|
||||||
echo "❌ 依赖安装失败!请检查 npm registry 是否可用。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('🔍 代码质量检查') {
|
stage('🔍 代码质量检查') {
|
||||||
parallel {
|
parallel {
|
||||||
stage('ESLint 检查') {
|
stage('ESLint') {
|
||||||
steps {
|
steps {
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||||
sh 'npm run lint'
|
sh 'npm run lint || exit 1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('TypeScript') {
|
||||||
stage('TypeScript 类型检查') {
|
|
||||||
steps {
|
steps {
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||||
sh 'npm run type-check'
|
sh 'npm run type-check || exit 1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
|
||||||
failure {
|
|
||||||
echo "❌ 代码质量检查未通过!"
|
|
||||||
echo "💡 提示:请运行 npm run lint 和 npm run type-check 在本地修复问题"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('🧪 单元测试 + 覆盖率检查') {
|
stage('🧪 单元测试') {
|
||||||
steps {
|
steps {
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||||
sh '''
|
sh '''
|
||||||
export CI=true
|
export CI=true
|
||||||
npm run test:coverage:check
|
npm run test:coverage:check || exit 1
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
publishHTML(target: [
|
publishHTML(target: [
|
||||||
allowMissing: false,
|
allowMissing: true,
|
||||||
alwaysLinkToLastBuild: true,
|
|
||||||
keepAll: true,
|
|
||||||
reportDir: 'coverage',
|
reportDir: 'coverage',
|
||||||
reportFiles: 'index.html',
|
reportFiles: 'index.html',
|
||||||
reportName: 'Coverage Report (Jest)'
|
reportName: 'Coverage Report'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
success {
|
|
||||||
echo "✅ 单元测试通过!覆盖率 ≥ 80%"
|
|
||||||
}
|
|
||||||
failure {
|
|
||||||
echo "❌ 单元测试失败或覆盖率不足 80%!"
|
|
||||||
echo "💡 请查看 Coverage Report 了解详情"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('🏗️ 生产构建') {
|
stage('🏗️ 构建 dist') {
|
||||||
when {
|
|
||||||
anyOf {
|
|
||||||
branch 'main'
|
|
||||||
branch 'develop'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
steps {
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||||
sh '''
|
sh '''
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🧹 清理旧构建..."
|
||||||
rm -rf .next dist
|
rm -rf .next dist
|
||||||
|
|
||||||
|
echo "🔨 执行 Next.js 构建..."
|
||||||
npm run build:clean
|
npm run build:clean
|
||||||
'''
|
|
||||||
}
|
echo ""
|
||||||
sh '''
|
echo "✅ 构建完成!验证产物..."
|
||||||
if [ -d "dist" ]; then
|
|
||||||
|
if [ ! -d "dist" ]; then
|
||||||
|
echo "❌ dist 目录不存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
FILE_COUNT=$(find dist -type f | wc -l)
|
FILE_COUNT=$(find dist -type f | wc -l)
|
||||||
DIST_SIZE=$(du -sh dist | cut -f1)
|
DIST_SIZE=$(du -sh dist | cut -f1)
|
||||||
echo "✅ 构建成功!生成了 $FILE_COUNT 个文件,总大小 $DIST_SIZE"
|
echo "📊 文件数: $FILE_COUNT, 大小: $DIST_SIZE"
|
||||||
else
|
'''
|
||||||
echo "❌ 构建失败:dist 目录不存在"
|
}
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
'''
|
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
success {
|
success {
|
||||||
archiveArtifacts artifacts: 'dist/**', fingerprint: true
|
archiveArtifacts artifacts: 'dist/**', fingerprint: true
|
||||||
}
|
}
|
||||||
failure {
|
|
||||||
echo "❌ Next.js 构建失败!请检查构建日志中的错误信息。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('🌐 E2E 测试') {
|
|
||||||
when {
|
|
||||||
allOf {
|
|
||||||
anyOf {
|
|
||||||
branch 'main'
|
|
||||||
branch 'develop'
|
|
||||||
}
|
|
||||||
expression { return params.DEPLOY_TO_PRODUCTION == true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
|
||||||
sh '''
|
|
||||||
cd e2e && npx playwright test --config=playwright.config.ts || exit 1
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
publishHTML(target: [
|
|
||||||
allowMissing: false,
|
|
||||||
alwaysLinkToLastBuild: true,
|
|
||||||
keepAll: true,
|
|
||||||
reportDir: 'e2e/playwright-report',
|
|
||||||
reportFiles: 'index.html',
|
|
||||||
reportName: 'E2E Test Report (Playwright)'
|
|
||||||
])
|
|
||||||
}
|
|
||||||
success {
|
|
||||||
echo "✅ E2E 测试全部通过!"
|
|
||||||
}
|
|
||||||
failure {
|
|
||||||
echo "❌ E2E 测试失败!"
|
|
||||||
echo "💡 请查看 E2E Test Report 了解哪些用例未通过"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,124 +203,167 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
echo "⚠️ 即将部署到生产环境:${DOMAIN}"
|
echo "⚠️ 准备部署到生产环境: ${DOMAIN}"
|
||||||
sleep(time: 5, unit: 'SECONDS')
|
sleep(time: 3, unit: 'SECONDS')
|
||||||
|
|
||||||
sshagent(['deploy-server-ssh-key']) {
|
|
||||||
sh """
|
|
||||||
# 备份旧版本
|
|
||||||
ssh -o StrictHostKeyChecking=no ${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\"
|
|
||||||
fi
|
|
||||||
'
|
|
||||||
|
|
||||||
# 上传新版本
|
|
||||||
echo "📤 上传 dist 目录到服务器..."
|
|
||||||
ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} \
|
|
||||||
\"mkdir -p ${DEPLOY_ROOT}/${STATIC_DIR}\"
|
|
||||||
|
|
||||||
rsync -avz --delete \
|
|
||||||
dist/ \
|
|
||||||
${SERVER_USER}@${SERVER_IP}:${DEPLOY_ROOT}/${STATIC_DIR}/
|
|
||||||
|
|
||||||
# 设置权限并重载 Nginx
|
|
||||||
ssh -o StrictHostKeyChecking=no ${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\"
|
|
||||||
docker exec ${NGINX_CONTAINER} nginx -s reload
|
|
||||||
'
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证部署成功
|
|
||||||
sh """
|
sh """
|
||||||
sleep 3
|
set -e
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
HTTP_STATUS=\$(curl -s -o /dev/null -w '%{http_code}' ${DOMAIN})
|
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
|
if [ "\$HTTP_STATUS" = "200" ]; then
|
||||||
echo "✅ 部署验证成功!HTTP 状态码: \$HTTP_STATUS"
|
echo "✅ 主页访问正常 (HTTP \$HTTP_STATUS)"
|
||||||
else
|
else
|
||||||
echo "❌ 部署验证失败!HTTP 状态码: \$HTTP_STATUS"
|
echo "❌ 主页返回异常状态码: \$HTTP_STATUS"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查关键页面
|
echo ""
|
||||||
for PAGE in "/" "/contact" "/about" "/products"; do
|
echo "🔍 检查关键页面..."
|
||||||
STATUS=\$(curl -s -o /dev/null -w '%{http_code}' ${DOMAIN}\$PAGE)
|
for PAGE in "/contact" "/about" "/products" "/services"; do
|
||||||
if [ "\$STATUS" != "200" ]; then
|
STATUS=\$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 \${DOMAIN}\$PAGE)
|
||||||
echo "⚠️ 页面 \$PAGE 返回状态码 \$STATUS"
|
if [ "\$STATUS" = "200" ]; then
|
||||||
|
echo " ✅ \$PAGE (\$STATUS)"
|
||||||
|
else
|
||||||
|
echo " ⚠️ \$PAGE (\$STATUS) - 需要关注"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🎉 部署完成!访问地址: ${DOMAIN}"
|
echo "🎉 部署成功!"
|
||||||
|
echo "📋 访问地址:"
|
||||||
|
echo " HTTP: http://${SERVER_IP}"
|
||||||
|
echo " HTTPS: https://novalon.cn"
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
success {
|
|
||||||
script {
|
|
||||||
def commitMsg = sh(
|
|
||||||
script: 'git log -1 --pretty=%B',
|
|
||||||
returnStdout: true
|
|
||||||
).trim()
|
|
||||||
|
|
||||||
def buildUrl = env.BUILD_URL
|
|
||||||
def buildNum = env.BUILD_NUMBER
|
|
||||||
|
|
||||||
// 发送部署成功通知(钉钉/企业微信)
|
|
||||||
echo """
|
|
||||||
🎉 **Novalon Website 部署成功**
|
|
||||||
|
|
||||||
- **构建号**: #${buildNum}
|
|
||||||
- **提交信息**: ${commitMsg}
|
|
||||||
- **目标域名**: ${DOMAIN}
|
|
||||||
- **构建详情**: ${buildUrl}
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
failure {
|
failure {
|
||||||
echo "❌ 部署失败!正在执行自动回滚..."
|
echo "❌ 部署失败!正在执行自动回滚..."
|
||||||
|
|
||||||
// 自动回滚到上一个备份
|
script {
|
||||||
sshagent(['deploy-server-ssh-key']) {
|
try {
|
||||||
sh """
|
sh """
|
||||||
ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
|
ssh ${SERVER_USER}@${SERVER_IP} '
|
||||||
set -e
|
set -e
|
||||||
BACKUP_DIR=\"${DEPLOY_ROOT}\"
|
LATEST_BACKUP=\$(ls -dt ${DEPLOY_ROOT}/${STATIC_DIR}_backup_* 2>/dev/null | head -1)
|
||||||
LATEST_BACKUP=\$(ls -dt \${BACKUP_DIR}/${STATIC_DIR}_backup_* 2>/dev/null | head -1)
|
|
||||||
|
if [ -n "\$LATEST_BACKUP" ]; then
|
||||||
if [ -n \"\$LATEST_BACKUP\" ]; then
|
echo "🔄 回滚到: \$LATEST_BACKUP"
|
||||||
echo \"🔄 回滚到最近备份: \$LATEST_BACKUP\"
|
rm -rf "${DEPLOY_ROOT}/${STATIC_DIR}"
|
||||||
rm -rf \"${DEPLOY_ROOT}/${STATIC_DIR}\"
|
cp -r "\$LATEST_BACKUP" "${DEPLOY_ROOT}/${STATIC_DIR}"
|
||||||
cp -r \"\$LATEST_BACKUP\" \"${DEPLOY_ROOT}/${STATIC_DIR}\"
|
|
||||||
docker exec ${NGINX_CONTAINER} nginx -s reload
|
if docker exec ${NGINX_CONTAINER} nginx -t; then
|
||||||
echo "✅ 回滚完成"
|
docker exec ${NGINX_CONTAINER} nginx -s reload
|
||||||
else
|
else
|
||||||
echo "❌ 未找到可用的备份!需要手动介入"
|
docker restart ${NGINX_CONTAINER}
|
||||||
exit 1
|
fi
|
||||||
fi
|
echo "✅ 回滚完成"
|
||||||
'
|
else
|
||||||
"""
|
echo "❌ 未找到可用备份!需要手动介入"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
"""
|
||||||
|
} catch (Exception e) {
|
||||||
|
echo "❌ 自动回滚失败: ${e.getMessage()}"
|
||||||
|
echo "🚨 需要立即手动介入!"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('🧹 清理工作空间') {
|
|
||||||
steps {
|
|
||||||
cleanWs(cleanWhenNotBuilt: false,
|
|
||||||
deleteDirs: true,
|
|
||||||
notFailBuild: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
@@ -342,34 +371,18 @@ pipeline {
|
|||||||
script {
|
script {
|
||||||
def result = currentBuild.result ?: 'SUCCESS'
|
def result = currentBuild.result ?: 'SUCCESS'
|
||||||
def duration = currentBuild.durationString.replace(' and counting', '')
|
def duration = currentBuild.durationString.replace(' and counting', '')
|
||||||
def color = result == 'SUCCESS' ? 'good' : (result == 'UNSTABLE' ? 'warning' : 'danger')
|
|
||||||
|
|
||||||
echo """
|
echo """
|
||||||
====================================
|
╔════════════════════════════════════════════╗
|
||||||
📊 Jenkins 构建报告
|
║ 📊 Jenkins Pipeline 报告 ║
|
||||||
====================================
|
╠════════════════════════════════════════════╣
|
||||||
- **项目**: ${env.JOB_NAME}
|
║ 项目: ${env.JOB_NAME}
|
||||||
- **构建号**: #${env.BUILD_NUMBER}
|
║ 构建号: #${env.BUILD_NUMBER}
|
||||||
- **结果**: ${result}
|
║ 结果: ${result}
|
||||||
- **耗时**: ${duration}
|
║ 耗时: ${duration}
|
||||||
- **详情**: ${env.BUILD_URL}
|
║ 详情: ${env.BUILD_URL}
|
||||||
====================================
|
╚════════════════════════════════════════════╝
|
||||||
"""
|
"""
|
||||||
|
|
||||||
// 发送通知(可根据需要集成钉钉、企业微信、邮件等)
|
|
||||||
if (result != 'SUCCESS') {
|
|
||||||
mail (
|
|
||||||
to: 'dev-team@novalon.cn',
|
|
||||||
subject: "❌ Novalon Website 构建失败: #${env.BUILD_NUMBER}",
|
|
||||||
body: """构建结果: ${result}
|
|
||||||
|
|
||||||
请尽快查看构建详情:
|
|
||||||
${env.BUILD_URL}
|
|
||||||
|
|
||||||
构建日志:
|
|
||||||
${env.BUILD_URL}console"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,11 +391,7 @@ ${env.BUILD_URL}console"""
|
|||||||
}
|
}
|
||||||
|
|
||||||
failure {
|
failure {
|
||||||
echo "❌ Pipeline 执行失败!请检查上述错误信息。"
|
echo "❌ Pipeline 执行失败!请查看日志。"
|
||||||
}
|
|
||||||
|
|
||||||
aborted {
|
|
||||||
echo "⚠️ Pipeline 被中止!"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,373 @@
|
|||||||
|
# Novalon Website CI/CD 自动化配置工具
|
||||||
|
|
||||||
|
## 📋 概述
|
||||||
|
|
||||||
|
一套完整的自动化脚本,用于快速配置 **Jenkins + Gitea** 的 CI/CD 流水线,实现:
|
||||||
|
|
||||||
|
- ✅ **一键配置**:自动完成所有 Jenkins 和 Gitea 的配置
|
||||||
|
- ✅ **Docker 适配**:专为 Docker 环境优化(Shell 模式)
|
||||||
|
- ✅ **免密部署**:自动挂载 SSH 密钥,实现生产服务器免密登录
|
||||||
|
- ✅ **Webhook 集成**:自动配置 Gitea Webhook 触发构建
|
||||||
|
- ✅ **完整验证**:自动检测所有配置是否正确
|
||||||
|
|
||||||
|
## 🎯 适用场景
|
||||||
|
|
||||||
|
- **首次部署**:全新环境的一键初始化
|
||||||
|
- **环境重建**:需要重置或迁移时
|
||||||
|
- **故障排查**:验证当前配置状态
|
||||||
|
- **日常维护**:更新插件、重新配置 Webhook
|
||||||
|
|
||||||
|
## 📁 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/setup-cicd/
|
||||||
|
├── cicd.sh # 一键启动器(入口脚本)
|
||||||
|
├── setup-cicd.sh # 主配置流程
|
||||||
|
├── cicd.config # 配置文件(可自定义修改)
|
||||||
|
└── lib/
|
||||||
|
├── utils.sh # 工具函数库
|
||||||
|
├── jenkins-setup.sh # Jenkins 配置逻辑
|
||||||
|
├── gitea-setup.sh # Gitea Webhook 配置
|
||||||
|
└── verify.sh # 验证和测试
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
1. **Docker 已安装并运行**
|
||||||
|
2. **SSH 密钥已生成**(用于免密登录生产服务器):
|
||||||
|
```bash
|
||||||
|
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
|
||||||
|
```
|
||||||
|
3. **生产服务器的 `authorized_keys` 包含你的公钥**:
|
||||||
|
```bash
|
||||||
|
ssh-copy-id root@139.155.109.62
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
#### 1️⃣ 完整安装(首次使用)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入项目目录
|
||||||
|
cd /Users/zhangxiang/Codes/Novalon/novalon-website
|
||||||
|
|
||||||
|
# 赋予执行权限
|
||||||
|
chmod +x scripts/setup-cicd/*.sh scripts/setup-cicd/lib/*.sh
|
||||||
|
|
||||||
|
# 执行完整安装(会重建 Jenkins 容器)
|
||||||
|
./scripts/setup-cicd/cicd.sh --full
|
||||||
|
```
|
||||||
|
|
||||||
|
**执行过程**:
|
||||||
|
```
|
||||||
|
╔════════════════════════════════════════════╗
|
||||||
|
║ 🚀 Novalon Website CI/CD 自动配置工具 ║
|
||||||
|
╚════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
📋 模式: 完整安装(包含 Docker 容器重建)
|
||||||
|
|
||||||
|
════════════════════════════════════════════
|
||||||
|
检查前置条件
|
||||||
|
════════════════════════════════════════════
|
||||||
|
|
||||||
|
✅ docker 已安装
|
||||||
|
✅ curl 已安装
|
||||||
|
✅ jq 已安装
|
||||||
|
✅ 前置条件检查通过
|
||||||
|
|
||||||
|
════════════════════════════════════════════
|
||||||
|
配置 Jenkins Docker 容器
|
||||||
|
════════════════════════════════════════════
|
||||||
|
|
||||||
|
✅ Jenkins 容器已启动
|
||||||
|
容器名称: jenkins
|
||||||
|
访问地址: http://ci.f.novalon.cn:8080
|
||||||
|
|
||||||
|
...(后续步骤自动完成)
|
||||||
|
|
||||||
|
════════════════════════════════════════════
|
||||||
|
✅ 自动化配置完成!
|
||||||
|
════════════════════════════════════════════
|
||||||
|
|
||||||
|
🌐 Jenkins: http://ci.f.novalon.cn:8080
|
||||||
|
🔗 Gitea: https://git.f.novalon.cn
|
||||||
|
🚀 任务名: novalon-website-ci-cd
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2️⃣ 快速配置(跳过容器重建)
|
||||||
|
|
||||||
|
如果 Jenkins 容器已经在运行,只是需要重新配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-cicd/cicd.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
这会跳过容器创建,直接进行工具安装、凭据配置、任务创建和 Webhook 设置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3️⃣ 仅配置 Jenkins
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-cicd/cicd.sh --jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
适用于:
|
||||||
|
- 安装新的 Jenkins 插件
|
||||||
|
- 更新全局工具配置
|
||||||
|
- 重新创建 Pipeline 任务
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4️⃣ 仅配置 Gitea Webhook
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-cicd/cicd.sh --webhook
|
||||||
|
```
|
||||||
|
|
||||||
|
适用于:
|
||||||
|
- Webhook 丢失或损坏
|
||||||
|
- 更新 Webhook URL
|
||||||
|
- 重新测试触发机制
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 5️⃣ 验证当前配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-cicd/cicd.sh --verify
|
||||||
|
```
|
||||||
|
|
||||||
|
输出示例:
|
||||||
|
```
|
||||||
|
════════════════════════════════════════════
|
||||||
|
验证结果汇总
|
||||||
|
════════════════════════════════════════════
|
||||||
|
|
||||||
|
总计: 7 项检查
|
||||||
|
通过: 7 项
|
||||||
|
|
||||||
|
🎉 所有配置已就绪!可以开始使用 CI/CD 流水线了。
|
||||||
|
|
||||||
|
下一步操作:
|
||||||
|
1. 访问 http://ci.f.novalon.cn:8080/job/novalon-website-ci-cd/
|
||||||
|
2. 点击 'Build Now' 进行首次构建
|
||||||
|
3. 推送代码到 main 分支测试自动触发
|
||||||
|
4. 勾选 DEPLOY_TO_PRODUCTION 参数测试部署
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 6️⃣ 清理所有配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-cicd/cicd.sh --clean
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **警告**:此操作会删除 Jenkins 容器和所有数据!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 自定义配置
|
||||||
|
|
||||||
|
编辑 `cicd.config` 文件可以修改以下参数:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Jenkins 配置
|
||||||
|
JENKINS_URL="http://ci.f.novalon.cn:8080"
|
||||||
|
JENKINS_CONTAINER_NAME="jenkins"
|
||||||
|
JENKINS_IMAGE="jenkins/jenkins:lts-jdk17"
|
||||||
|
|
||||||
|
# Gitea 配置
|
||||||
|
GITEA_URL="https://git.f.novalon.cn"
|
||||||
|
GITEA_USERNAME="zhangxiang@novalon.cn"
|
||||||
|
GITEA_PASSWORD="qerqek-cavxaC-5jupri"
|
||||||
|
|
||||||
|
# 生产服务器配置
|
||||||
|
SERVER_IP="139.155.109.62"
|
||||||
|
SERVER_USER="root"
|
||||||
|
DEPLOY_ROOT="/home/novalon/docker-app"
|
||||||
|
|
||||||
|
# 项目配置
|
||||||
|
REPO_OWNER="novalon"
|
||||||
|
REPO_NAME="novalon-website"
|
||||||
|
JOB_NAME="novalon-website-ci-cd"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 故障排查
|
||||||
|
|
||||||
|
### 问题 1:SSH 连接失败
|
||||||
|
|
||||||
|
**症状**:验证阶段显示 "❌ SSH 免密登录: 失败"
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
# 1. 在宿主机上测试 SSH 连接
|
||||||
|
ssh root@139.155.109.62
|
||||||
|
|
||||||
|
# 2. 如果失败,复制公钥到服务器
|
||||||
|
ssh-copy-id root@139.155.109.62
|
||||||
|
|
||||||
|
# 3. 重新运行配置脚本
|
||||||
|
./scripts/setup-cicd/cicd.sh --jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题 2:Jenkins 容器启动失败
|
||||||
|
|
||||||
|
**症状**:`docker run` 报错或容器无法访问
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
# 查看容器日志
|
||||||
|
docker logs jenkins
|
||||||
|
|
||||||
|
# 检查端口占用
|
||||||
|
lsof -i :8080
|
||||||
|
|
||||||
|
# 清理后重试
|
||||||
|
docker stop jenkins && docker rm jenkins
|
||||||
|
./scripts/setup-cicd/cicd.sh --full
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题 3:Webhook 未触发构建
|
||||||
|
|
||||||
|
**症状**:推送代码后 Jenkins 无反应
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
# 1. 验证 Webhook 是否存在
|
||||||
|
./scripts/setup-cicd/cicd.sh --verify
|
||||||
|
|
||||||
|
# 2. 手动测试 Webhook
|
||||||
|
curl -X POST "http://ci.f.novalon.cn:8080/generic-webhook-trigger/invoke?token=novalon-website-ci-token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"ref":"refs/heads/main"}'
|
||||||
|
|
||||||
|
# 3. 如果不通,检查网络连通性
|
||||||
|
docker exec gitea curl -I http://jenkins:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题 4:插件安装失败
|
||||||
|
|
||||||
|
**症状**:Jenkins 插件未正确加载
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
# 重启 Jenkins 使插件生效
|
||||||
|
docker restart jenkins
|
||||||
|
|
||||||
|
# 等待就绪后重新运行
|
||||||
|
sleep 30
|
||||||
|
./scripts/setup-cicd/cicd.sh --jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 工作流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ 开发者推送代码 │
|
||||||
|
│ git push origin main│
|
||||||
|
└─────────┬───────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────┐ ┌──────────────────┐
|
||||||
|
│ Gitea Webhook │────▶│ Jenkins Trigger │
|
||||||
|
│ (Push Event) │ │ (Generic Trigger) │
|
||||||
|
└─────────────────────┘ └────────┬─────────┘
|
||||||
|
│
|
||||||
|
┌────────────────┼────────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌───────────┐ ┌─────────────┐ ┌──────────────┐
|
||||||
|
│ 环境准备 │ │ 依赖安装 │ │ 代码质量检查 │
|
||||||
|
│ (Node.js) │ │ (npm ci) │ │(ESLint+TS) │
|
||||||
|
└───────────┘ └─────────────┘ └──────────────┘
|
||||||
|
│ │ │
|
||||||
|
└────────────────┼────────────────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ 单元测试 │
|
||||||
|
│ (覆盖率≥80%) │
|
||||||
|
└──────┬───────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ 构建 dist │
|
||||||
|
│ (next build) │
|
||||||
|
└──────┬───────┘
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ DEPLOY_TO_PRODUCTION=true ? │
|
||||||
|
└──────────────┬───────────────┘
|
||||||
|
Yes ↘ ↘ No
|
||||||
|
┌──────────┐ ┌──────────┐
|
||||||
|
│ rsync 部署│ │ 构建完成 │
|
||||||
|
│ Nginx 重载│ │ (仅构建) │
|
||||||
|
│ 验证页面 │ └──────────┘
|
||||||
|
└─────┬────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ 发送通知 │
|
||||||
|
│ (钉钉/邮件) │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 最佳实践建议
|
||||||
|
|
||||||
|
### 1. 定期运行验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 添加到 crontab,每周五上午 10 点自动验证
|
||||||
|
0 10 * * 5 cd /path/to/project && ./scripts/setup-cicd/cicd.sh --verify >> /var/log/cicd-check.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 版本控制配置文件
|
||||||
|
|
||||||
|
将 `cicd.config` 提交到 Git(注意移除敏感信息或使用环境变量):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add scripts/setup-cicd/
|
||||||
|
git commit -m "chore: add automated CI/CD setup scripts"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 团队协作
|
||||||
|
|
||||||
|
新成员入职时只需运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url>
|
||||||
|
cd novalon-website
|
||||||
|
./scripts/setup-cicd/cicd.sh --full
|
||||||
|
```
|
||||||
|
|
||||||
|
即可在 5 分钟内完成完整的开发环境搭建!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如遇到问题:
|
||||||
|
|
||||||
|
1. 运行 `./scripts/setup-cicd/cicd.sh --verify` 查看详细诊断
|
||||||
|
2. 检查日志:`docker logs jenkins`
|
||||||
|
3. 查看 Gitea Webhook 的 Recent Deliveries
|
||||||
|
4. 访问 Jenkins 系统日志:Manage Jenkins → System Log
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**版本**: 1.0.0
|
||||||
|
**最后更新**: 2026-05-12
|
||||||
|
**适用环境**: Docker + Jenkins 2.400+ + Gitea 1.19+
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
JENKINS_URL="http://ci.f.novalon.cn:8080"
|
||||||
|
JENKINS_HOST="ci.f.novalon.cn"
|
||||||
|
JENKINS_PORT=8080
|
||||||
|
JENKINS_CONTAINER_NAME="jenkins"
|
||||||
|
JENKINS_IMAGE="jenkins/jenkins:lts-jdk17"
|
||||||
|
|
||||||
|
GITEA_URL="https://git.f.novalon.cn"
|
||||||
|
GITEA_HOST="git.f.novalon.cn"
|
||||||
|
|
||||||
|
GITEA_USERNAME="zhangxiang@novalon.cn"
|
||||||
|
GITEA_PASSWORD="qerqek-cavxaC-5jupri"
|
||||||
|
GITEA_TOKEN=""
|
||||||
|
|
||||||
|
JENKINS_ADMIN_USER="zhangxiang"
|
||||||
|
JENKINS_ADMIN_PASSWORD="Vaptyc-jadpe1-fywmaj"
|
||||||
|
JENKINS_API_TOKEN=""
|
||||||
|
|
||||||
|
SERVER_IP="139.155.109.62"
|
||||||
|
SERVER_USER="root"
|
||||||
|
DEPLOY_ROOT="/home/novalon/docker-app"
|
||||||
|
STATIC_DIR="novalon-static"
|
||||||
|
NGINX_CONTAINER="novalon-nginx-secure"
|
||||||
|
DOMAIN="https://novalon.cn"
|
||||||
|
|
||||||
|
REPO_OWNER="novalon"
|
||||||
|
REPO_NAME="novalon-website"
|
||||||
|
JOB_NAME="novalon-website-ci-cd"
|
||||||
|
WEBHOOK_TOKEN="novalon-website-ci-token"
|
||||||
|
|
||||||
|
NODE_VERSION="18"
|
||||||
|
NPM_REGISTRY="https://registry.npmmirror.com"
|
||||||
|
BACKUP_RETENTION_COUNT=3
|
||||||
|
|
||||||
|
DOCKER_NETWORK="novalon-network"
|
||||||
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
echo "╔════════════════════════════════════════════╗"
|
||||||
|
echo "║ 🚀 Novalon CI/CD 一键配置启动器 ║"
|
||||||
|
echo "╚════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
--full|-f)
|
||||||
|
echo "📋 模式: 完整安装(重建容器 + 全部配置)"
|
||||||
|
exec bash "${SCRIPT_DIR}/setup-cicd.sh" --full
|
||||||
|
;;
|
||||||
|
--jenkins|-j)
|
||||||
|
echo "📋 模式: 仅配置 Jenkins(工具 + 凭据 + 插件)"
|
||||||
|
exec bash "${SCRIPT_DIR}/setup-cicd.sh" --jenkins
|
||||||
|
;;
|
||||||
|
--webhook|-w)
|
||||||
|
echo "📋 模式: 仅配置 Gitea Webhook"
|
||||||
|
exec bash "${SCRIPT_DIR}/setup-cicd.sh" --webhook
|
||||||
|
;;
|
||||||
|
--verify|-v)
|
||||||
|
echo "📋 模式: 验证当前配置"
|
||||||
|
source "${SCRIPT_DIR}/lib/utils.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/verify.sh"
|
||||||
|
load_config_or_defaults
|
||||||
|
run_all_verifications
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--clean|-c)
|
||||||
|
echo "📋 模式: 清理所有配置"
|
||||||
|
read -p "⚠️ 确定要删除 Jenkins 容器和所有配置吗?(y/N): " confirm
|
||||||
|
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
docker stop jenkins 2>/dev/null || true
|
||||||
|
docker rm jenkins 2>/dev/null || true
|
||||||
|
docker volume rm jenkins_data 2>/dev/null || true
|
||||||
|
echo "✅ 清理完成"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--help|-h|*)
|
||||||
|
echo "用法: $0 [选项]"
|
||||||
|
echo ""
|
||||||
|
echo "选项:"
|
||||||
|
echo " --full, -f 完整安装(重建 Docker 容器 + 安装工具 + 配置凭据 + 创建任务 + 配置 Webhook)"
|
||||||
|
echo " --jenkins, -j 仅配置 Jenkins(安装工具、插件、凭据,创建 Pipeline 任务)"
|
||||||
|
echo " --webhook, -w 仅配置 Gitea Webhook"
|
||||||
|
echo " --verify, -v 验证当前配置是否正确"
|
||||||
|
echo " --clean, -c 清理 Jenkins 容器和数据"
|
||||||
|
echo " --help, -h 显示帮助信息"
|
||||||
|
echo ""
|
||||||
|
echo "示例:"
|
||||||
|
echo " $0 --full # 首次使用或需要完全重置时"
|
||||||
|
echo " $0 # 快速配置(不重建容器)"
|
||||||
|
echo " $0 --verify # 检查所有配置是否正常"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "╔════════════════════════════════════════════╗"
|
||||||
|
echo "║ 🚀 Novalon Website CI/CD 自动配置工具 ║"
|
||||||
|
echo "║ Version: 1.0.0 ║"
|
||||||
|
echo "║ Mode: Docker + Shell (参考 deploy-dist.sh) ║"
|
||||||
|
echo "╚════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
CONFIG_FILE="$SCRIPT_DIR/cicd.config"
|
||||||
|
|
||||||
|
source "$SCRIPT_DIR/lib/utils.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/jenkins-setup.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/gitea-setup.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/verify.sh"
|
||||||
|
|
||||||
|
load_config_or_defaults
|
||||||
|
|
||||||
|
show_banner "开始自动化配置流程"
|
||||||
|
|
||||||
|
check_prerequisites || exit 1
|
||||||
|
|
||||||
|
if [ "$1" == "--full" ] || [ "$1" == "-f" ]; then
|
||||||
|
echo "📋 模式: 完整安装(包含 Docker 容器重建)"
|
||||||
|
setup_jenkins_container
|
||||||
|
install_jenkins_tools
|
||||||
|
elif [ "$1" == "--jenkins" ] || [ "$1" == "-j" ]; then
|
||||||
|
echo "📋 模式: 仅配置 Jenkins"
|
||||||
|
install_jenkins_tools
|
||||||
|
elif [ "$1" == "--webhook" ] || [ "$1" == "-w" ]; then
|
||||||
|
echo "📋 模式: 仅配置 Gitea Webhook"
|
||||||
|
else
|
||||||
|
echo "📋 模式: 快速配置(跳过容器重建)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_jenkins_ready
|
||||||
|
|
||||||
|
setup_jenkins_credentials
|
||||||
|
setup_jenkins_global_tools
|
||||||
|
install_required_plugins
|
||||||
|
create_pipeline_job
|
||||||
|
setup_gitea_webhook
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
show_banner "配置完成!开始验证..."
|
||||||
|
|
||||||
|
run_all_verifications
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔════════════════════════════════════════════╗"
|
||||||
|
echo "║ ✅ 自动化配置完成! ║"
|
||||||
|
echo "╠════════════════════════════════════════════╣"
|
||||||
|
echo "║ ║"
|
||||||
|
echo "║ 🌐 Jenkins: ${JENKINS_URL} ║"
|
||||||
|
echo "║ 🔗 Gitea: ${GITEA_URL} ║"
|
||||||
|
echo "║ 🚀 任务名: ${JOB_NAME} ║"
|
||||||
|
echo "║ ║"
|
||||||
|
echo "║ 下一步操作: ║"
|
||||||
|
echo "║ 1. 访问 Jenkins 并点击 'Build Now' ║"
|
||||||
|
echo "║ 2. 推送代码到 main 分支测试 Webhook ║"
|
||||||
|
echo "║ 3. 勾选 DEPLOY_TO_PRODUCTION 测试部署 ║"
|
||||||
|
echo "║ ║"
|
||||||
|
echo "╚════════════════════════════════════════════╝"
|
||||||
Reference in New Issue
Block a user