ci: 发布Admin Nginx代理配置

This commit is contained in:
sunlei 2026-06-24 17:17:13 +08:00
parent 8616f16383
commit 3bade23ef1
3 changed files with 104 additions and 1 deletions

74
Jenkinsfile vendored
View File

@ -22,8 +22,12 @@ pipeline {
parameters {
booleanParam(name: 'DEPLOY_STATIC_FILES', defaultValue: true, description: '构建成功后是否发布 dist 到 Nginx 静态目录;仅发布分支生效')
booleanParam(name: 'DEPLOY_NGINX_CONFIG', defaultValue: true, description: '构建成功后是否发布并热加载 Admin Nginx 配置;仅发布分支生效')
string(name: 'PUBLISH_BRANCH_PATTERN', defaultValue: '^(main|master|release/.+)$', description: '允许发布静态文件的分支正则')
string(name: 'DEPLOY_TARGET_DIR', defaultValue: '/home/jenkins/agent/frontends/html/admin', description: 'Nginx 挂载目录中 admin 项目的静态文件目录')
string(name: 'NGINX_CONTAINER_NAME', defaultValue: 'kt-frontends-nginx', description: '承载 Admin 静态站的 Nginx 容器名')
string(name: 'NGINX_CONFIG_SOURCE', defaultValue: 'deploy/nginx-admin.conf', description: '仓库内 Admin Nginx 配置文件路径')
string(name: 'NGINX_CONFIG_TARGET', defaultValue: '/etc/nginx/conf.d/nginx-admin.conf', description: 'Nginx 容器内 Admin 配置目标路径')
string(name: 'VITE_BASE', defaultValue: '/', description: '构建进 Admin 的 Vite base 路径')
string(name: 'VITE_GLOB_API_URL', defaultValue: '/api', description: '构建进 Admin 的后端 API 前缀')
choice(name: 'VITE_ROUTER_HISTORY', choices: ['hash', 'html5'], description: 'vue-router 模式')
@ -85,6 +89,8 @@ pipeline {
Publish branch: ${env.IS_PUBLISH_BRANCH}
Deploy static files: ${params.DEPLOY_STATIC_FILES}
Deploy target: ${params.DEPLOY_TARGET_DIR}
Deploy nginx config: ${params.DEPLOY_NGINX_CONFIG}
Nginx container: ${params.NGINX_CONTAINER_NAME}
API URL: ${params.VITE_GLOB_API_URL}
Vite base: ${params.VITE_BASE}
Router history: ${params.VITE_ROUTER_HISTORY}
@ -184,11 +190,77 @@ pipeline {
}
}
}
stage('Deploy Nginx Config') {
when {
allOf {
expression { return params.DEPLOY_NGINX_CONFIG }
expression { return env.IS_CHANGE_REQUEST != 'true' }
expression { return env.IS_PUBLISH_BRANCH == 'true' }
}
}
steps {
script {
if (!isUnix()) {
error('Deploy Nginx Config stage requires a Linux/NAS Jenkins Agent.')
}
def containerName = params.NGINX_CONTAINER_NAME?.trim()
def configSource = params.NGINX_CONFIG_SOURCE?.trim()
def configTarget = params.NGINX_CONFIG_TARGET?.trim()
if (!containerName || !configSource || !configTarget) {
error('NGINX_CONTAINER_NAME, NGINX_CONFIG_SOURCE, and NGINX_CONFIG_TARGET are required when DEPLOY_NGINX_CONFIG is enabled.')
}
withEnv([
"NGINX_CONTAINER_NAME=${containerName}",
"NGINX_CONFIG_SOURCE=${configSource}",
"NGINX_CONFIG_TARGET=${configTarget}",
]) {
runCmd("""
set -e
test -f "\${NGINX_CONFIG_SOURCE}"
case "\${NGINX_CONTAINER_NAME}" in
""|*[!A-Za-z0-9_.-]*)
echo "Unsafe NGINX_CONTAINER_NAME: \${NGINX_CONTAINER_NAME}"
exit 1
;;
esac
case "\${NGINX_CONFIG_TARGET}" in
""|"/"|"/etc"|"/etc/nginx"|"/etc/nginx/conf.d"|*"/.."*|*".."*)
echo "Unsafe NGINX_CONFIG_TARGET: \${NGINX_CONFIG_TARGET}"
exit 1
;;
esac
docker ps --format '{{.Names}}' | grep -Fx "\${NGINX_CONTAINER_NAME}" >/dev/null
backup_path="\${NGINX_CONFIG_TARGET}.bak-${env.BUILD_NUMBER}"
docker exec "\${NGINX_CONTAINER_NAME}" sh -lc "if [ -f '\${NGINX_CONFIG_TARGET}' ]; then cp '\${NGINX_CONFIG_TARGET}' '\${backup_path}'; fi"
docker cp "\${NGINX_CONFIG_SOURCE}" "\${NGINX_CONTAINER_NAME}:\${NGINX_CONFIG_TARGET}"
if ! docker exec "\${NGINX_CONTAINER_NAME}" nginx -t; then
echo "Nginx config validation failed; restoring previous config."
docker exec "\${NGINX_CONTAINER_NAME}" sh -lc "if [ -f '\${backup_path}' ]; then cp '\${backup_path}' '\${NGINX_CONFIG_TARGET}'; fi"
docker exec "\${NGINX_CONTAINER_NAME}" nginx -t || true
exit 1
fi
docker exec "\${NGINX_CONTAINER_NAME}" nginx -s reload
docker exec "\${NGINX_CONTAINER_NAME}" sh -lc "nginx -T 2>/dev/null | grep -n '/napcat-webui/' >/dev/null"
""".stripIndent())
}
}
}
}
}
post {
success {
archiveArtifacts artifacts: 'apps/web-antdv-next/dist/**,apps/web-antdv-next/dist.zip,package.json,pnpm-lock.yaml,Jenkinsfile', fingerprint: true, allowEmptyArchive: true
archiveArtifacts artifacts: 'apps/web-antdv-next/dist/**,apps/web-antdv-next/dist.zip,deploy/nginx-admin.conf,package.json,pnpm-lock.yaml,Jenkinsfile', fingerprint: true, allowEmptyArchive: true
}
}
}

View File

@ -90,5 +90,17 @@ describe('qqbot account NapCat login view boundary', () => {
expect(nginxSource).toContain('location ^~ /napcat-webui/');
expect(nginxSource).toContain('48086');
expect(nginxSource).toContain('proxy_http_version 1.1');
expect(nginxSource).toContain('location ^~ /kt-k8s-dashboard/');
});
it('publishes the Admin nginx route file during main-branch deployment', () => {
const jenkinsSource = readRepoSource('Jenkinsfile');
expect(jenkinsSource).toContain("booleanParam(name: 'DEPLOY_NGINX_CONFIG'");
expect(jenkinsSource).toContain("stage('Deploy Nginx Config')");
expect(jenkinsSource).toContain('docker cp');
expect(jenkinsSource).toContain('NGINX_CONFIG_SOURCE');
expect(jenkinsSource).toContain('nginx -t');
expect(jenkinsSource).toContain('nginx -s reload');
});
});

View File

@ -14,6 +14,25 @@ server {
try_files $uri =404;
}
# KT K8s Dashboard reverse proxy begin
location = /kt-k8s-dashboard {
return 301 /kt-k8s-dashboard/;
}
location ^~ /kt-k8s-dashboard/ {
proxy_pass http://172.17.0.1:4466/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /kt-k8s-dashboard;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
}
# KT K8s Dashboard reverse proxy end
location ^~ /api/ {
proxy_pass http://192.168.31.224:48085/;
proxy_http_version 1.1;