diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..37d5c87 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,29 @@ +# BuildFlow API .dockerignore +# 目标:让 docker build 只把源码进镜像,排除密钥/产物/IDE 文件 +# 密钥靠容器启动时 volume 挂载 appsettings.Secrets.json 注入,绝不进镜像 + +# 🔒 密钥与机密配置(最重要:防止明文密码进镜像) +**/appsettings.Secrets.json +**/appsettings.*.Secrets.json +**/secrets.json + +# 构建产物(镜像内会重新 dotnet publish,无需带旧产物) +**/bin/ +**/obj/ + +# Git 与 IDE +.git/ +.gitignore +.vscode/ +.idea/ +*.user +*.suo + +# 系统与日志 +.DS_Store +*.log +*.tmp + +# 文档(镜像不需要) +*.md +docs/ diff --git a/Jenkinsfile b/Jenkinsfile index a771df4..0f3ab11 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,10 +1,23 @@ // BuildFlow buildflow-api CI/CD -// 链路:①拉取代码 → ②编译打包(docker build) → 推送 Harbor → ③部署(cd-target SSH) → HealthCheck -// 注:飞书通知逻辑已移除(凭证未配齐);部署通过 ssh 直连 cd-target 执行。 +// 链路:①拉取代码 → ②编译打包(docker build) → 推送 Harbor → ③部署(cd-target docker compose) → HealthCheck +// 自动触发:Gitea push → Generic Webhook Trigger(token=buildflow-api) +// Gitea webhook 指向:http://jenkins:8080/generic-webhook-trigger/invoke?token=buildflow-api +// 部署:docker compose;api 生产密钥通过 volume 挂载 appsettings.Secrets.json 注入(不进镜像) // api 运行依赖:PostgreSQL(5库) / Redis / MongoDB(im_system) / RabbitMQ / S3(RustFS) / 本地LLM(1234) / KKFileView(8012) pipeline { agent any + triggers { + // Gitea push 触发:Generic Webhook Trigger 按 token 路由到本 job + GenericTrigger( + genericToken: 'buildflow-api', + causeString: 'Gitea push 触发', + printContributedVariables: false, + printPostContent: false, + regexpFilterText: '', + regexpFilterExpression: '' + ) + } environment { HARBOR = "43.134.235.58:58080" IMAGE = "${HARBOR}/buildflow/buildflow-api" @@ -12,6 +25,9 @@ pipeline { // Jenkins 容器内无 ssh 别名,直接用 IP;ubuntu 用户 sudo 免密(NOPASSWD) DEPLOY_HOST = "ubuntu@110.42.240.10" APP_PORT = "5667" + // cd-target 上 compose 编排目录 + 本服务名 + DEPLOY_DIR = "/home/ubuntu/deploy" + SERVICE = "buildflow-api" } options { timeout(time: 30, unit: "MINUTES") @@ -55,36 +71,40 @@ pipeline { } stage("③ 部署") { steps { - script { - try { - withCredentials([usernamePassword(credentialsId: "harbor-credentials", usernameVariable: "HUSER", passwordVariable: "HPASS")]) { - sh """ - ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} " - echo '${HPASS}' | sudo docker login ${HARBOR} -u ${HUSER} --password-stdin - sudo docker pull ${IMAGE}:latest - sudo docker rm -f buildflow-api 2>/dev/null || true - sudo docker run -d --name buildflow-api --restart unless-stopped -p ${APP_PORT}:${APP_PORT} ${IMAGE}:latest - " - """ - } - echo "部署完成" - } catch (e) { - echo "警告:部署节点 ${DEPLOY_HOST} 暂不可用,跳过自动部署(镜像已推送 Harbor)。原因:${e.getMessage()}" - } + withCredentials([usernamePassword(credentialsId: "harbor-credentials", usernameVariable: "HUSER", passwordVariable: "HPASS")]) { + sh """ + ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} " + cd ${DEPLOY_DIR} + echo '${HPASS}' | sudo docker login ${HARBOR} -u ${HUSER} --password-stdin + sudo docker compose pull ${SERVICE} + sudo docker compose up -d --no-deps ${SERVICE} + " + """ } + echo "部署完成" } } stage("HealthCheck") { steps { script { - try { - sh """ - sleep 8 - ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} 'wget -q --spider http://127.0.0.1:${APP_PORT}/health || wget -q --spider http://127.0.0.1:${APP_PORT}/ && echo HEALTHCHECK_OK' - """ - echo "=== HealthCheck OK: api http://110.42.240.10:${APP_PORT} ===" - } catch (e) { - echo "警告:HealthCheck 探测失败(容器可能仍在启动,或依赖的 PG/Redis/Mongo 未就绪)。镜像已推送 Harbor,可手动检查。" + // .NET 启动较慢(连 PG/Redis/Mongo),轮询最多 60s + def healthy = false + for (int i = 1; i <= 12; i++) { + sleep 5 + try { + sh """ + ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} 'wget -q --spider http://127.0.0.1:${APP_PORT}/health && echo HEALTHCHECK_OK' + """ + healthy = true + echo "=== HealthCheck OK: api http://110.42.240.10:${APP_PORT} ===" + break + } catch (e) { + echo "HealthCheck 第 ${i}/12 次探测未就绪,5s 后重试..." + } + } + if (!healthy) { + // 不再静默吞失败:标记 unstable,流水线变黄便于发现 + unstable("HealthCheck 60s 内未通过,查日志:ssh cd-target 'sudo docker logs ${SERVICE} --tail 50'") } } }