From eb162ddba67be574e14d3d2b4c112cc04f8525a0 Mon Sep 17 00:00:00 2001 From: sunlei Date: Wed, 24 Jun 2026 15:13:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20NapCat=20WebUI=20G?= =?UTF-8?q?ateway=20=E9=83=A8=E7=BD=B2=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API.md | 8 ++ Jenkinsfile | 30 ++++++- README.md | 2 + dockerfile.gateway | 46 ++++++++++ k8s/prod/api.yaml | 85 +++++++++++++++++++ .../gateway-deployment.spec.ts | 66 ++++++++++++++ 6 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 dockerfile.gateway create mode 100644 test/modules/qqbot/napcat-webui-gateway/gateway-deployment.spec.ts diff --git a/API.md b/API.md index c64886d..d92cfe1 100644 --- a/API.md +++ b/API.md @@ -379,6 +379,14 @@ QQBot 运行态包括 NapCat 容器登录、OneBot v11 反向 WebSocket、MQTT NapCat Chinese Desktop Runtime v8 使用 KT `NapCatQQ` fork 源码构建出的 `NapCat.Shell` artifact,并在 QQ `KickedOffLine` 后重置 native login service 再请求二维码;同一次踢下线事件只消费一次 reset,且只有明确二维码过期或扫码确认窗口失效的 QR session failure 才自动换码,避免扫码确认期间被 `ErrType=1/ErrCode=1` 抢刷新打断。构建前必须运行 `scripts/napcat-desktop-cn-stage-build.mjs` 生成 Docker build context;生产 `QQBOT_NAPCAT_IMAGE` 应指向验证过的 `kt-napcat-desktop-cn:desktop-cn-v8` digest。 +### NapCat WebUI Gateway + +NapCat WebUI Gateway 是独立部署的内部代理服务,生产镜像由 `dockerfile.gateway` 打包 `dist/apps/napcat-webui-gateway/main.js`,K8s 服务名为 `kt-napcat-webui-gateway`,端口 `48086`。API 侧只通过内部路由 `NAPCAT_WEBUI_GATEWAY_INTERNAL_BASE_URL` 创建、续期、撤销会话和交换一次性 ticket;浏览器只访问公开前缀 `NAPCAT_WEBUI_GATEWAY_PUBLIC_BASE_URL` 下的代理页面、静态资源和 WebSocket 转发,不能直连 NapCat 容器 WebUI。 + +必需环境变量:`NAPCAT_WEBUI_GATEWAY_INTERNAL_BASE_URL`、`NAPCAT_WEBUI_GATEWAY_PUBLIC_BASE_URL`、`NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET`、`NAPCAT_WEBUI_GATEWAY_REDIS_HOST`、`NAPCAT_WEBUI_GATEWAY_REDIS_PORT`、`NAPCAT_WEBUI_GATEWAY_SESSION_TTL_MS`、`NAPCAT_WEBUI_GATEWAY_TICKET_TTL_MS`、`NAPCAT_WEBUI_GATEWAY_UPSTREAM_TIMEOUT_MS`。生产 `NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET` 只来自 Jenkins 私有 `.env.production` 生成的 `kt-template-online-api-env` Secret,不写入 Git 或 manifest 字面量。 + +部署验收使用:`pnpm exec jest --runTestsByPath test/modules/qqbot/napcat-webui-gateway/gateway-deployment.spec.ts --runInBand`、`pnpm run typecheck`、`pnpm run build`、`Test-Path .\dist\apps\napcat-webui-gateway\main.js`、`git diff --check`。安全验收要求浏览器永远不接收 WebUI token、Credential、上游 URL/端口、Docker 拓扑、Redis 地址或内部 secret。 + `napcat_login_event` 实体和表仅作为历史 schema 兼容保留;watchdog 不再写入 quick/password 恢复事件,也不再依赖该表判断是否恢复登录。 外发消息不直接抢发:后端会按 `QQBOT_SEND_GLOBAL_INTERVAL_MS`、`QQBOT_SEND_TARGET_INTERVAL_MS` 和 `QQBOT_SEND_JITTER_MS` 预约发送窗口,默认全局 2500ms、同会话 8000ms、抖动 0-800ms;如果等待超过 `QQBOT_SEND_MAX_QUEUE_WAIT_MS`,本次发送会在下发前被拒绝。在线命令和自动回复规则会叠加运行时保底冷却,默认命令 5000ms、规则 30000ms;复读机默认连续 4 次相同普通文本才触发,同一会话默认 10 分钟内只复读一次,并限制普通文本长度,减少自动行为被风控识别的概率。 diff --git a/Jenkinsfile b/Jenkinsfile index 9568cf4..b3f0974 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -38,6 +38,7 @@ def requiredRuntimeEnvKeys() { 'FFLOGS_CLIENT_SECRET', 'QQBOT_PLUGIN_QUEUE_REDIS_HOST', 'QQBOT_PLUGIN_QUEUE_REDIS_PORT', + 'NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET', ] } @@ -85,6 +86,7 @@ pipeline { string(name: 'PUBLISH_BRANCH_PATTERN', defaultValue: '^(main|master|release/.+)$', description: '允许推送镜像的分支正则') string(name: 'DOCKER_REGISTRY', defaultValue: 'k3d-kt-registry.localhost:5000', description: '镜像仓库地址;K8s 发布默认使用 fnOS NAS 上的 k3d 本地 registry') string(name: 'IMAGE_NAME', defaultValue: 'kt-template-online-api', description: 'Docker 镜像名称') + string(name: 'GATEWAY_IMAGE_NAME', defaultValue: 'kt-napcat-webui-gateway', description: 'NapCat WebUI Gateway Docker 镜像名称') string(name: 'IMAGE_TAG', defaultValue: '', description: '镜像标签,为空时使用 分支名-BUILD_NUMBER;PR 使用源分支名') string(name: 'CONTAINER_NAME', defaultValue: 'kt-template-online-api', description: '业务容器名称') string(name: 'CONTAINER_PORT', defaultValue: '48085', description: '宿主机映射端口,容器内固定使用 48085') @@ -133,6 +135,8 @@ pipeline { env.DOCKER_REGISTRY_EFFECTIVE = registry ?: '' env.DOCKER_IMAGE = registry ? "${registry}/${params.IMAGE_NAME}:${env.IMAGE_TAG_FINAL}" : "${params.IMAGE_NAME}:${env.IMAGE_TAG_FINAL}" env.DOCKER_IMAGE_LATEST = registry ? "${registry}/${params.IMAGE_NAME}:latest" : "${params.IMAGE_NAME}:latest" + env.GATEWAY_DOCKER_IMAGE = registry ? "${registry}/${params.GATEWAY_IMAGE_NAME}:${env.IMAGE_TAG_FINAL}" : "${params.GATEWAY_IMAGE_NAME}:${env.IMAGE_TAG_FINAL}" + env.GATEWAY_DOCKER_IMAGE_LATEST = registry ? "${registry}/${params.GATEWAY_IMAGE_NAME}:latest" : "${params.GATEWAY_IMAGE_NAME}:latest" // Agent 由 NAS 侧预先创建;这里仅确认 CI 所需的 Node/pnpm 环境可用。 if (isUnix()) { @@ -183,6 +187,8 @@ pipeline { Docker registry: ${env.DOCKER_REGISTRY_EFFECTIVE ?: '-'} Docker image: ${env.DOCKER_IMAGE} Docker latest: ${env.DOCKER_IMAGE_LATEST} + Gateway image: ${env.GATEWAY_DOCKER_IMAGE} + Gateway latest: ${env.GATEWAY_DOCKER_IMAGE_LATEST} Deploy target: ${params.DEPLOY_TARGET} Publish branch: ${env.IS_PUBLISH_BRANCH} Run container: ${params.RUN_DOCKER_CONTAINER} @@ -237,16 +243,24 @@ pipeline { if (isUnix()) { runCmd(""" test -f dist/main.js + test -f dist/apps/napcat-webui-gateway/main.js docker build -f dockerfile -t ${env.DOCKER_IMAGE} . + docker build -f dockerfile.gateway -t ${env.GATEWAY_DOCKER_IMAGE} . if [ '${env.DOCKER_IMAGE}' != '${env.DOCKER_IMAGE_LATEST}' ]; then docker tag ${env.DOCKER_IMAGE} ${env.DOCKER_IMAGE_LATEST} fi + if [ '${env.GATEWAY_DOCKER_IMAGE}' != '${env.GATEWAY_DOCKER_IMAGE_LATEST}' ]; then + docker tag ${env.GATEWAY_DOCKER_IMAGE} ${env.GATEWAY_DOCKER_IMAGE_LATEST} + fi """.stripIndent()) } else { runCmd('', """ if not exist dist\\main.js exit /b 1 + if not exist dist\\apps\\napcat-webui-gateway\\main.js exit /b 1 docker build -f dockerfile -t ${env.DOCKER_IMAGE} . + docker build -f dockerfile.gateway -t ${env.GATEWAY_DOCKER_IMAGE} . if not "${env.DOCKER_IMAGE}"=="${env.DOCKER_IMAGE_LATEST}" docker tag ${env.DOCKER_IMAGE} ${env.DOCKER_IMAGE_LATEST} + if not "${env.GATEWAY_DOCKER_IMAGE}"=="${env.GATEWAY_DOCKER_IMAGE_LATEST}" docker tag ${env.GATEWAY_DOCKER_IMAGE} ${env.GATEWAY_DOCKER_IMAGE_LATEST} """.stripIndent()) } } @@ -267,9 +281,14 @@ pipeline { runCmd(""" docker push ${env.DOCKER_IMAGE} docker push ${env.DOCKER_IMAGE_LATEST} + docker push ${env.GATEWAY_DOCKER_IMAGE} + docker push ${env.GATEWAY_DOCKER_IMAGE_LATEST} """.stripIndent()) } else { - runCmd("docker push ${env.DOCKER_IMAGE}") + runCmd(""" + docker push ${env.DOCKER_IMAGE} + docker push ${env.GATEWAY_DOCKER_IMAGE} + """.stripIndent()) } } } @@ -295,6 +314,7 @@ pipeline { def namespace = params.K8S_NAMESPACE?.trim() ?: 'kt-prod' def deploymentName = params.K8S_DEPLOYMENT?.trim() ?: 'kt-template-online-api' def containerName = params.K8S_CONTAINER?.trim() ?: 'api' + def gatewayDeploymentName = 'kt-napcat-webui-gateway' def envSecret = params.K8S_ENV_SECRET?.trim() ?: 'kt-template-online-api-env' def rolloutTimeout = params.K8S_ROLLOUT_TIMEOUT?.trim() ?: '180s' def containerEnvFile = params.CONTAINER_ENV_FILE?.trim() @@ -338,10 +358,14 @@ pipeline { kubectl ${kubeConfigArg} apply -f ${shellQuote(manifestFile)} kubectl ${kubeConfigArg} ${namespaceArg} set image ${shellQuote("deployment/${deploymentName}")} ${shellQuote("${containerName}=${env.DOCKER_IMAGE}")} + kubectl ${kubeConfigArg} ${namespaceArg} set image ${shellQuote('deployment/kt-napcat-webui-gateway')} ${shellQuote("gateway=${env.GATEWAY_DOCKER_IMAGE}")} kubectl ${kubeConfigArg} ${namespaceArg} annotate ${shellQuote("deployment/${deploymentName}")} \\ ${shellQuote("kubernetes.io/change-cause=${changeCause}")} --overwrite + kubectl ${kubeConfigArg} ${namespaceArg} annotate ${shellQuote('deployment/kt-napcat-webui-gateway')} \\ + ${shellQuote("kubernetes.io/change-cause=${changeCause}")} --overwrite kubectl ${kubeConfigArg} ${namespaceArg} rollout status ${shellQuote("deployment/${deploymentName}")} --timeout=${shellQuote(rolloutTimeout)} - kubectl ${kubeConfigArg} ${namespaceArg} get pod,svc -l app=${shellQuote(deploymentName)} + kubectl ${kubeConfigArg} ${namespaceArg} rollout status ${shellQuote('deployment/kt-napcat-webui-gateway')} --timeout=${shellQuote(rolloutTimeout)} + kubectl ${kubeConfigArg} ${namespaceArg} get pod,svc -l ${shellQuote("app in (${deploymentName},${gatewayDeploymentName})")} """.stripIndent()) } } @@ -407,7 +431,7 @@ pipeline { post { success { - archiveArtifacts artifacts: 'dist/**,package.json,pnpm-lock.yaml,dockerfile,k8s/**,ci/fnos-k8s/**', fingerprint: true, allowEmptyArchive: true + archiveArtifacts artifacts: 'dist/**,package.json,pnpm-lock.yaml,dockerfile,dockerfile.gateway,k8s/**,ci/fnos-k8s/**', fingerprint: true, allowEmptyArchive: true } } } diff --git a/README.md b/README.md index b4fd9de..0621a2b 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Admin 环境总览面板使用 `ENV_DASHBOARD_*` 只读配置聚合 local-dev、 NapCat Runtime/Protocol Profile 已完成本地 API/Admin 实施,线上发布和账号闭环按 `docs/superpowers/plans/2026-06-18-qqbot-napcat-runtime-protocol-profile-implementation-plan.md` 的 Task 10 执行。当前实现覆盖运行态/协议/会话行为/历史登录事件兼容表/风险模式表,真实物理设备风格 hostname/MAC,NapCat/OneBot 配置 hash,KT `zh_CN.UTF-8` 中国桌面派生镜像资产,只读 `/qqbot/napcat/runtime/detail` 证据接口,watchdog 离线巡检告警,以及 Admin 账号页“运行态”抽屉;不绕过 QQ/Tencent 验证码、不修改 QQ/NTQQ 签名协议、不启用 privileged/host network,也不做账号级每小时/每日累计发送预算。NapCat Chinese Desktop Runtime v8 使用 KT `NapCatQQ` fork 源码构建出的 `NapCat.Shell` artifact,并在 QQ `KickedOffLine` 后重置 native login service 再请求二维码;同一次踢下线事件只消费一次 reset,且只有明确二维码过期或扫码确认窗口失效的 QR session failure 才自动换码,避免扫码确认期间被 `ErrType=1/ErrCode=1` 抢刷新打断。镜像必须先用 `scripts/napcat-desktop-cn-stage-build.mjs` staged build context,生产 `QQBOT_NAPCAT_IMAGE` 应指向验证过的 `kt-napcat-desktop-cn:desktop-cn-v8` digest。 +NapCat WebUI Gateway 是独立运行的 NestJS 入口,生产镜像使用 `dockerfile.gateway` 打包 `dist/apps/napcat-webui-gateway/main.js` 并监听 `48086`。API 通过内部地址 `NAPCAT_WEBUI_GATEWAY_INTERNAL_BASE_URL=http://kt-napcat-webui-gateway:48086` 创建/续期/撤销 WebUI 会话,Admin 浏览器只访问公开前缀 `NAPCAT_WEBUI_GATEWAY_PUBLIC_BASE_URL=/napcat-webui`。Gateway 运行时需要 `NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET`、`NAPCAT_WEBUI_GATEWAY_REDIS_HOST`、`NAPCAT_WEBUI_GATEWAY_REDIS_PORT`、`NAPCAT_WEBUI_GATEWAY_SESSION_TTL_MS`、`NAPCAT_WEBUI_GATEWAY_TICKET_TTL_MS`、`NAPCAT_WEBUI_GATEWAY_UPSTREAM_TIMEOUT_MS`;生产 secret 由 Jenkins 从私有 `.env.production` 重建到 `kt-template-online-api-env`,不得写入 Git。验收命令:`pnpm exec jest --runTestsByPath test/modules/qqbot/napcat-webui-gateway/gateway-deployment.spec.ts --runInBand`、`pnpm run typecheck`、`pnpm run build`、`Test-Path .\dist\apps\napcat-webui-gateway\main.js`、`git diff --check`。安全边界:浏览器不得收到 WebUI token、Credential、上游 URL/端口、Docker 拓扑、Redis 地址或内部 secret。 + ## 启动 ```bash diff --git a/dockerfile.gateway b/dockerfile.gateway new file mode 100644 index 0000000..1111c2f --- /dev/null +++ b/dockerfile.gateway @@ -0,0 +1,46 @@ +FROM node:22-bookworm-slim + +WORKDIR /app + +ENV NODE_ENV=production +ENV TZ=Asia/Shanghai +ENV APP_PORT=48086 +ENV LOG_LEVEL=info +ENV LOG_APP_NAME=kt-napcat-webui-gateway +ENV LOG_PRETTY=false +ENV LOKI_ENV=production +ENV LOKI_PUSH_ENDPOINT=/loki/api/v1/push +ENV LOKI_QUERY_ENDPOINT=/loki/api/v1/query_range +ENV LOKI_PUSH_TIMEOUT_MS=30000 +ENV LOKI_QUERY_TIMEOUT_MS=10000 +ENV LOKI_BATCH_INTERVAL_SECONDS=5 +ENV LOKI_BATCH_MAX_BUFFER_SIZE=10000 +ENV LOKI_QUERY_MAX_LIMIT=1000 +ENV LOKI_SILENCE_ERRORS=true +ENV FFLOGS_BASE_URL=https://cn.fflogs.com +ENV FFLOGS_WEB_BASE_URL=https://cn.fflogs.com +ENV FFLOGS_GRAPHQL_URL=https://cn.fflogs.com/api/v2/client +ENV FFLOGS_TOKEN_URL=https://cn.fflogs.com/oauth/token +ENV FFLOGS_DEFAULT_SERVER_REGION=CN +ENV FFLOGS_REQUEST_TIMEOUT_MS=10000 + +COPY package.json pnpm-lock.yaml ./ + +RUN apt-get update \ + && apt-get install -y --no-install-recommends fontconfig fonts-noto-cjk openssh-client \ + && fc-cache -f \ + && rm -rf /var/lib/apt/lists/* + +# 生产镜像只安装运行依赖,dist 由 Jenkins Build stage 提前产出。 +# 跳过安装阶段脚本,避免 NODE_ENV=production 时 devDependency 中的 husky 不存在导致 prepare 失败。 +RUN npm install -g pnpm@9.15.9 --registry=https://registry.npmmirror.com \ + && pnpm config set registry https://registry.npmmirror.com \ + && pnpm install --prod --frozen-lockfile --ignore-scripts \ + && pnpm rebuild skia-canvas + +# dist 由 Jenkins 的 Build stage 生成,这里只打包运行产物。 +COPY dist ./dist + +EXPOSE 48086 + +CMD ["node", "dist/apps/napcat-webui-gateway/main"] diff --git a/k8s/prod/api.yaml b/k8s/prod/api.yaml index 5a9650c..1ba9215 100644 --- a/k8s/prod/api.yaml +++ b/k8s/prod/api.yaml @@ -42,6 +42,10 @@ spec: value: kt:qqbot:plugin-task - name: BANGDREAM_TSUGU_CACHE_ROOT value: /data/qqbot/plugins/bangdream/cache + - name: NAPCAT_WEBUI_GATEWAY_INTERNAL_BASE_URL + value: http://kt-napcat-webui-gateway:48086 + - name: NAPCAT_WEBUI_GATEWAY_PUBLIC_BASE_URL + value: /napcat-webui # Jenkins 每次发布会从 Agent 私有 .env.production 重建这个 Secret。 envFrom: - secretRef: @@ -162,6 +166,87 @@ spec: --- apiVersion: v1 kind: Service +metadata: + name: kt-napcat-webui-gateway + namespace: kt-prod + labels: + app: kt-napcat-webui-gateway +spec: + type: ClusterIP + selector: + app: kt-napcat-webui-gateway + ports: + - name: http + port: 48086 + targetPort: 48086 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kt-napcat-webui-gateway + namespace: kt-prod + labels: + app: kt-napcat-webui-gateway +spec: + replicas: 1 + revisionHistoryLimit: 3 + strategy: + type: Recreate + selector: + matchLabels: + app: kt-napcat-webui-gateway + template: + metadata: + labels: + app: kt-napcat-webui-gateway + spec: + containers: + - name: gateway + image: k3d-kt-registry.localhost:5000/kt-napcat-webui-gateway:latest + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 48086 + env: + - name: NODE_ENV + value: production + - name: TZ + value: Asia/Shanghai + - name: NAPCAT_WEBUI_GATEWAY_PORT + value: "48086" + - name: NAPCAT_WEBUI_GATEWAY_REDIS_HOST + value: kt-qqbot-plugin-redis + - name: NAPCAT_WEBUI_GATEWAY_REDIS_PORT + value: "6379" + - name: NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET + valueFrom: + secretKeyRef: + name: kt-template-online-api-env + key: NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET + readinessProbe: + tcpSocket: + port: 48086 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 6 + livenessProbe: + tcpSocket: + port: 48086 + initialDelaySeconds: 20 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: v1 +kind: Service metadata: name: kt-qqbot-plugin-redis namespace: kt-prod diff --git a/test/modules/qqbot/napcat-webui-gateway/gateway-deployment.spec.ts b/test/modules/qqbot/napcat-webui-gateway/gateway-deployment.spec.ts new file mode 100644 index 0000000..7d92e37 --- /dev/null +++ b/test/modules/qqbot/napcat-webui-gateway/gateway-deployment.spec.ts @@ -0,0 +1,66 @@ +import { existsSync, readFileSync } from 'fs'; +import { resolve } from 'path'; + +const REPO_ROOT = resolve(__dirname, '../../../..'); + +/** + * 读取仓库根目录下的部署配置文件。 + * @param relativePath - 仓库根目录相对路径。 + * @returns 文件文本内容。 + */ +function readRepoFile(relativePath: string): string { + return readFileSync(resolve(REPO_ROOT, relativePath), 'utf8'); +} + +/** + * 确认仓库根目录下的部署配置文件存在。 + * @param relativePath - 仓库根目录相对路径。 + * @returns 绝对文件路径。 + */ +function expectRepoFile(relativePath: string): string { + const filePath = resolve(REPO_ROOT, relativePath); + expect(existsSync(filePath)).toBe(true); + return filePath; +} + +describe('NapCat WebUI Gateway deployment configuration', () => { + it('packages the standalone gateway app in a dedicated runtime image', () => { + expectRepoFile('dockerfile.gateway'); + + const dockerfile = readRepoFile('dockerfile.gateway'); + + expect(dockerfile).toContain('dist/apps/napcat-webui-gateway/main'); + expect(dockerfile).toContain('EXPOSE 48086'); + expect(dockerfile).toContain('LOG_APP_NAME=kt-napcat-webui-gateway'); + }); + + it('declares gateway API linkage and cluster runtime without literal secrets', () => { + const manifest = readRepoFile('k8s/prod/api.yaml'); + + expect(manifest).toContain('kt-napcat-webui-gateway'); + expect(manifest).toContain('containerPort: 48086'); + expect(manifest).toContain('NAPCAT_WEBUI_GATEWAY_REDIS_HOST'); + expect(manifest).toContain('NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET'); + expect(manifest).toContain('NAPCAT_WEBUI_GATEWAY_INTERNAL_BASE_URL'); + expect(manifest).toContain('NAPCAT_WEBUI_GATEWAY_PUBLIC_BASE_URL'); + expect(manifest).toMatch( + /name:\s*NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET[\s\S]*?valueFrom:[\s\S]*?secretKeyRef:/, + ); + expect(manifest).not.toMatch( + /name:\s*NAPCAT_WEBUI_GATEWAY_INTERNAL_SECRET[\s\S]*?value:\s*["']?[^"'\s]+/, + ); + }); + + it('builds, pushes and rolls out the gateway image through Jenkins', () => { + const jenkinsfile = readRepoFile('Jenkinsfile'); + + expect(jenkinsfile).toContain('GATEWAY_IMAGE_NAME'); + expect(jenkinsfile).toContain('dockerfile.gateway'); + expect(jenkinsfile).toContain('kt-napcat-webui-gateway'); + expect(jenkinsfile).toContain('GATEWAY_DOCKER_IMAGE'); + expect(jenkinsfile).toMatch(/docker push \$\{env\.GATEWAY_DOCKER_IMAGE\}/); + expect(jenkinsfile).toMatch( + /rollout status [\s\S]*deployment\/kt-napcat-webui-gateway/, + ); + }); +});