diff --git a/.project-docs/30-worklog/tasks/20260814-go-deploy-artifacts-2a5f8e1d.md b/.project-docs/30-worklog/tasks/20260814-go-deploy-artifacts-2a5f8e1d.md new file mode 100644 index 0000000..7cde9bb --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260814-go-deploy-artifacts-2a5f8e1d.md @@ -0,0 +1,52 @@ +# Task: Build first-deployment artifacts for the Go stack + +## Identity + +- Task ID: 20260814-go-deploy-artifacts-2a5f8e1d +- Mode: Feature +- Branch: main +- Worktree: /Users/brother7/Documents/AI/NianAIGC +- Base commit: ca019abb14859f7413214b2b6a11a8a07cebf5f7 +- Owner: dsh +- Status: Ready for Integration + +## Scope + +- Build the deployment artifacts for the first production deployment of the ADR-003 split topology: Go container image build, ACK Deployment/Service for the Go API workload, split-path Ingress routing, and workload configuration updates. +- Human direction (2026-08-14): first production deployment runs Next.js (pages/static/SSR) plus Go (backend paths) directly; no Node Worker and no migration Job pod in production. + +## Intent And Constraints + +- Production Web workload holds no RDS/provider credentials and only needs the shared session secret for local cookie verification (its middleware already verifies the cookie with HMAC locally, no database access). +- Go workload runs non-root, root filesystem read-only, with writable emptyDir mounts for runtime/logs/settings/temp. +- Keep the manifest contract checker (`check-ack-manifests.mjs`) authoritative for the new topology. +- Keep deprecated manifests (worker, migration Job) on disk with header comments. + +## Outcome + +- Added `backend/Dockerfile` (multi-stage `golang:1.21-alpine` → `alpine:3.20`, static `CGO_ENABLED=0` build, non-root uid/gid 10001, ca-certificates + tzdata) and `backend/.dockerignore`. +- Added `deploy/ack/go-api.yaml`: Deployment `zhinian-go-api` (1 replica, `/api/ready` database-aware readiness, runAsNonRoot, readOnlyRootFilesystem, RDS CA + data + tmp volumes, bootstrap/provider/webhook secrets, embedded WorkerLoop config) plus ClusterIP Service `zhinian-go-api:8080`. +- Added `zhinian-go-runtime` ConfigMap to `deploy/ack/configmap.yaml` with the full Go runtime surface (DB/TLS settings, auth, embedded worker, billing, runtime/log/settings dirs). +- Updated `deploy/ack/web.yaml`: removed RDS credentials, worker token, and RDS CA mount; readiness switched to process-level `/api/health` (Web is database-free in production). +- Updated `deploy/ack/ingress.yaml`: `/api`, `/uploads`, `/generated-results` → `zhinian-go-api`; `/api/internal/worker` still → selectorless deny Service; pages/static → Web. +- Updated `deploy/ack/secrets.example.yaml` with `zhinian-go-db`, `zhinian-go-bootstrap`, `zhinian-go-providers`, `zhinian-go-secrets` and notes that the session secret must match across workloads; marked local-only secrets. +- Marked `deploy/ack/worker.yaml` deprecated (production uses the embedded WorkerLoop). +- Updated `scripts/check-ack-manifests.mjs` assertions for the split topology (Web database-free, Go API non-root/database-aware readiness/bootstrap config, Ingress split routing). +- Updated `docs/DEPLOYMENT.md`, `README.zh-CN.md`, and `README.md` deployment/tech-stack guidance (Go image build command, apply order, split topology). + +## Verification + +- `npm run deploy:check` — PASS (9 manifest files, new assertions). +- All `deploy/ack/*.yaml` parse as valid multi-document YAML. +- `CGO_ENABLED=0 go build ./cmd/zhinian-api` — PASS. +- Docker image build itself must run on a machine with Docker; the Dockerfile is static-checked against the build steps in `scripts/run-go-command.mjs` conventions. + +## Follow-ups + +- Build and push the `zhinian-go-api` image, then validate the manifests with `kubectl apply --dry-run=server` on the target ACK cluster. +- Validate the full stack against non-production RDS/OSS/provider/Webhook dependencies before the first rollout. +- Decide whether to delete the deprecated `worker.yaml` and `migration-job.yaml`. + +## Promotion Candidates + +- Canonical memory (current-state Next Steps, commitments) still lists "build the Go workload deployment artifacts" as open; promote completion there in the next integration pass. diff --git a/README.md b/README.md index c12fced..699e16c 100644 --- a/README.md +++ b/README.md @@ -314,9 +314,12 @@ ZHINIAN_INTERNAL_WORKER_TOKEN=change-me-worker-token - 分镜提示词与 `@素材` 引用编排 - 火山 Visual 签名 canonical request - Next.js 生产构建 +- Go 后端全量测试与构建(`backend/`,契约 fixture 同步) ```bash npm test npm run build npm run health +npm run go:test +npm run go:build ``` diff --git a/README.zh-CN.md b/README.zh-CN.md index 5820155..23886d0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -29,11 +29,12 @@ ## 技术栈 -- Next.js 15 +- Next.js 15(前端:页面、静态资源、SSR) +- Go 1.21(生产后端:独占 `/api`、`/uploads`、`/generated-results`,内嵌 WorkerLoop,见 `backend/` 与 [`docs/DEPLOYMENT.md`](./docs/DEPLOYMENT.md)) - React 19 - TypeScript - GSAP -- PostgreSQL(生产)/本地 JSON(开发) +- PostgreSQL(生产)/本地 JSON(开发) - Aliyun OSS 可选 - Vitest diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..6261a86 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,4 @@ +zhinian-api +coverage.out +*.test +.runtime/ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..8b68be9 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1 +# Build context: the repository backend/ directory. +# docker build -f backend/Dockerfile -t REGISTRY/PROJECT/zhinian-go-api:TAG backend/ + +FROM golang:1.21-alpine AS build +WORKDIR /src +ENV CGO_ENABLED=0 GOOS=linux +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -trimpath -ldflags="-s -w" -o /out/zhinian-api ./cmd/zhinian-api + +FROM alpine:3.20 +RUN apk add --no-cache ca-certificates tzdata \ + && addgroup -S -g 10001 zhinian \ + && adduser -S -D -H -u 10001 -G zhinian zhinian \ + && mkdir -p /var/lib/zhinian \ + && chown -R zhinian:zhinian /var/lib/zhinian +COPY --from=build /out/zhinian-api /usr/local/bin/zhinian-api +USER 10001:10001 +EXPOSE 8080 +ENTRYPOINT ["zhinian-api"] diff --git a/deploy/ack/configmap.yaml b/deploy/ack/configmap.yaml index 9a0193b..01c05ff 100644 --- a/deploy/ack/configmap.yaml +++ b/deploy/ack/configmap.yaml @@ -16,3 +16,31 @@ data: DATABASE_IDLE_TIMEOUT_MS: "30000" DATABASE_STATEMENT_TIMEOUT_MS: "30000" DATABASE_APPLICATION_NAME: zhinian-web +--- +# Go API runtime settings. Provider endpoints/models use code defaults unless +# overridden here; credentials always come from zhinian-go-providers. +apiVersion: v1 +kind: ConfigMap +metadata: + name: zhinian-go-runtime + namespace: zhinian +data: + NODE_ENV: production + GO_BACKEND_PORT: "8080" + ZHINIAN_DATA_BACKEND: postgres + ZHINIAN_AUTH_REQUIRED: "true" + ZHINIAN_AUTH_COOKIE_SECURE: "true" + ZHINIAN_PUBLIC_BASE_URL: https://REPLACE_WITH_PUBLIC_HOST + DATABASE_SSL_MODE: verify-full + DATABASE_CA_CERT_PATH: /etc/zhinian/rds/ca.pem + DATABASE_POOL_MAX: "10" + DATABASE_CONNECTION_TIMEOUT_MS: "5000" + DATABASE_IDLE_TIMEOUT_MS: "30000" + DATABASE_STATEMENT_TIMEOUT_MS: "30000" + DATABASE_APPLICATION_NAME: zhinian-go-api + ZHINIAN_GO_EMBEDDED_WORKER: "true" + ZHINIAN_WORKER_ID: zhinian-go-api-embedded + ZHINIAN_BILLING_REQUIRED: "1" + ZHINIAN_RUNTIME_DIR: /var/lib/zhinian/runtime + ZHINIAN_LOG_DIR: /var/lib/zhinian/logs + ZHINIAN_SETTINGS_FILE: /var/lib/zhinian/settings.env diff --git a/deploy/ack/go-api.yaml b/deploy/ack/go-api.yaml new file mode 100644 index 0000000..542ea40 --- /dev/null +++ b/deploy/ack/go-api.yaml @@ -0,0 +1,207 @@ +# Go API workload: owns /api, /uploads, and /generated-results from the first +# production deployment. The embedded WorkerLoop replaces the Node Worker; do +# not deploy zhinian-worker in production. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: zhinian-go-api + namespace: zhinian +spec: + # Keep one replica until generated assets live in shared OSS storage and the + # RDS/provider connection budget is measured for more. + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app.kubernetes.io/name: zhinian + app.kubernetes.io/component: go-api + template: + metadata: + labels: + app.kubernetes.io/name: zhinian + app.kubernetes.io/component: go-api + spec: + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: go-api + image: REGISTRY/PROJECT/zhinian-go-api:REPLACE_TAG + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8080 + envFrom: + - configMapRef: + name: zhinian-go-runtime + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: zhinian-go-db + key: DATABASE_URL + # The session secret must be the same value Next.js uses so the + # frontend middleware and the Go backend verify the same cookies. + - name: ZHINIAN_AUTH_SESSION_SECRET + valueFrom: + secretKeyRef: + name: zhinian-web-auth + key: ZHINIAN_AUTH_SESSION_SECRET + - name: ZHINIAN_BOOTSTRAP_ADMIN_PHONE + valueFrom: + secretKeyRef: + name: zhinian-go-bootstrap + key: ZHINIAN_BOOTSTRAP_ADMIN_PHONE + - name: ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: zhinian-go-bootstrap + key: ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD + - name: ZHINIAN_BOOTSTRAP_ADMIN_NAME + valueFrom: + secretKeyRef: + name: zhinian-go-bootstrap + key: ZHINIAN_BOOTSTRAP_ADMIN_NAME + optional: true + - name: VOLCENGINE_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: VOLCENGINE_ACCESS_KEY_ID + optional: true + - name: VOLCENGINE_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: VOLCENGINE_SECRET_ACCESS_KEY + optional: true + - name: JIMENG_IMAGE_GENERATE_46_REQ_KEY + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: JIMENG_IMAGE_GENERATE_46_REQ_KEY + optional: true + - name: EVOLINK_API_KEY + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: EVOLINK_API_KEY + optional: true + - name: SEEDANCE_API_KEY + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: SEEDANCE_API_KEY + optional: true + - name: BAILIAN_API_KEY + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: BAILIAN_API_KEY + optional: true + - name: DASHSCOPE_API_KEY + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: DASHSCOPE_API_KEY + optional: true + - name: ALI_OSS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: ALI_OSS_ACCESS_KEY_ID + optional: true + - name: ALI_OSS_ACCESS_KEY_SECRET + valueFrom: + secretKeyRef: + name: zhinian-go-providers + key: ALI_OSS_ACCESS_KEY_SECRET + optional: true + - name: ZHINIAN_WEBHOOK_SECRET + valueFrom: + secretKeyRef: + name: zhinian-go-secrets + key: ZHINIAN_WEBHOOK_SECRET + - name: ZHINIAN_API_KEYS + valueFrom: + secretKeyRef: + name: zhinian-go-secrets + key: ZHINIAN_API_KEYS + optional: true + - name: ZHINIAN_INTERNAL_WORKER_TOKEN + valueFrom: + secretKeyRef: + name: zhinian-go-secrets + key: ZHINIAN_INTERNAL_WORKER_TOKEN + optional: true + volumeMounts: + - name: rds-ca + mountPath: /etc/zhinian/rds + readOnly: true + - name: data + mountPath: /var/lib/zhinian + - name: tmp + mountPath: /tmp + startupProbe: + httpGet: + path: /api/health + port: http + periodSeconds: 5 + failureThreshold: 24 + readinessProbe: + httpGet: + path: /api/ready + port: http + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /api/health + port: http + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 10001 + readOnlyRootFilesystem: true + volumes: + - name: rds-ca + secret: + secretName: zhinian-rds-ca + - name: data + emptyDir: {} + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: zhinian-go-api + namespace: zhinian +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: zhinian + app.kubernetes.io/component: go-api + ports: + - name: http + port: 8080 + targetPort: 8080 diff --git a/deploy/ack/ingress.yaml b/deploy/ack/ingress.yaml index 1c6c74b..35c93ad 100644 --- a/deploy/ack/ingress.yaml +++ b/deploy/ack/ingress.yaml @@ -12,7 +12,7 @@ spec: http: paths: # Longest-prefix matching sends public internal-API traffic to the - # selectorless deny Service instead of the Web workload. + # selectorless deny Service instead of any workload. - path: /api/internal/worker pathType: Prefix backend: @@ -20,6 +20,30 @@ spec: name: zhinian-public-deny port: number: 80 + # Backend paths belong to the Go API workload from the first + # production deployment. + - path: /api + pathType: Prefix + backend: + service: + name: zhinian-go-api + port: + number: 8080 + - path: /uploads + pathType: Prefix + backend: + service: + name: zhinian-go-api + port: + number: 8080 + - path: /generated-results + pathType: Prefix + backend: + service: + name: zhinian-go-api + port: + number: 8080 + # Pages and static assets stay with Next.js. - path: / pathType: Prefix backend: diff --git a/deploy/ack/secrets.example.yaml b/deploy/ack/secrets.example.yaml index 38099d9..3f49a6e 100644 --- a/deploy/ack/secrets.example.yaml +++ b/deploy/ack/secrets.example.yaml @@ -1,4 +1,6 @@ # Example only. Replace every placeholder and keep the populated file out of Git. +# Secrets marked "(local development only)" are not referenced by the first +# production deployment; keep or drop them as your local workflow requires. apiVersion: v1 kind: Secret metadata: @@ -6,6 +8,7 @@ metadata: namespace: zhinian type: Opaque stringData: + # Local development only: the production Web workload holds no RDS credentials. DATABASE_URL: postgresql://APP_USER:APP_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE --- apiVersion: v1 @@ -15,6 +18,7 @@ metadata: namespace: zhinian type: Opaque stringData: + # Manual schema execution only: run database/migrations/*.sql with this role. DATABASE_URL: postgresql://MIGRATION_USER:MIGRATION_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE --- apiVersion: v1 @@ -24,6 +28,7 @@ metadata: namespace: zhinian type: Opaque stringData: + # Local development only: the Node Worker is not deployed in production. ZHINIAN_INTERNAL_WORKER_TOKEN: REPLACE_WITH_A_LONG_RANDOM_VALUE --- apiVersion: v1 @@ -33,4 +38,60 @@ metadata: namespace: zhinian type: Opaque stringData: + # Shared by the Next.js middleware and the Go backend so both verify the + # same session cookies. Keep identical across workloads. ZHINIAN_AUTH_SESSION_SECRET: REPLACE_WITH_A_DIFFERENT_LONG_RANDOM_VALUE +--- +apiVersion: v1 +kind: Secret +metadata: + name: zhinian-go-db + namespace: zhinian +type: Opaque +stringData: + # Application role (least privilege): grants applied manually after the SQL. + DATABASE_URL: postgresql://APP_USER:APP_PASSWORD@RDS_INTERNAL_HOST:5432/APP_DATABASE +--- +apiVersion: v1 +kind: Secret +metadata: + name: zhinian-go-bootstrap + namespace: zhinian +type: Opaque +stringData: + # First super administrator, created once at Go startup when no super + # administrator exists. Password must be at least 8 characters. + ZHINIAN_BOOTSTRAP_ADMIN_PHONE: REPLACE_WITH_ADMIN_PHONE + ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD: REPLACE_WITH_STRONG_PASSWORD + ZHINIAN_BOOTSTRAP_ADMIN_NAME: 平台超级管理员 +--- +apiVersion: v1 +kind: Secret +metadata: + name: zhinian-go-providers + namespace: zhinian +type: Opaque +stringData: + # Provider credentials. Delete keys for providers you do not use; the Go + # workload tolerates missing optional keys and fails closed when an enabled + # engine has no credentials. + VOLCENGINE_ACCESS_KEY_ID: REPLACE_OR_REMOVE + VOLCENGINE_SECRET_ACCESS_KEY: REPLACE_OR_REMOVE + JIMENG_IMAGE_GENERATE_46_REQ_KEY: REPLACE_OR_REMOVE + EVOLINK_API_KEY: REPLACE_OR_REMOVE + SEEDANCE_API_KEY: REPLACE_OR_REMOVE + BAILIAN_API_KEY: REPLACE_OR_REMOVE + DASHSCOPE_API_KEY: REPLACE_OR_REMOVE + ALI_OSS_ACCESS_KEY_ID: REPLACE_OR_REMOVE + ALI_OSS_ACCESS_KEY_SECRET: REPLACE_OR_REMOVE +--- +apiVersion: v1 +kind: Secret +metadata: + name: zhinian-go-secrets + namespace: zhinian +type: Opaque +stringData: + ZHINIAN_WEBHOOK_SECRET: REPLACE_WITH_A_LONG_RANDOM_VALUE + ZHINIAN_API_KEYS: REPLACE_WITH_PUBLIC_API_KEYS + ZHINIAN_INTERNAL_WORKER_TOKEN: REPLACE_OR_REMOVE diff --git a/deploy/ack/web.yaml b/deploy/ack/web.yaml index 87d25cb..7e5f5fc 100644 --- a/deploy/ack/web.yaml +++ b/deploy/ack/web.yaml @@ -1,3 +1,8 @@ +# Next.js frontend workload for the first production deployment: serves pages, +# static assets, and SSR only. All /api, /uploads, and /generated-results +# traffic is routed to zhinian-go-api by the Ingress, so this workload holds no +# RDS credentials and needs only the shared session secret for local cookie +# verification in the middleware. apiVersion: apps/v1 kind: Deployment metadata: @@ -10,7 +15,7 @@ spec: strategy: type: RollingUpdate rollingUpdate: - maxSurge: 1 # Budget RDS connections for (replicas + maxSurge) * DATABASE_POOL_MAX. + maxSurge: 1 maxUnavailable: 0 selector: matchLabels: @@ -37,25 +42,13 @@ spec: - configMapRef: name: zhinian-runtime env: - - name: DATABASE_URL - valueFrom: - secretKeyRef: - name: zhinian-web-db - key: DATABASE_URL - - name: ZHINIAN_INTERNAL_WORKER_TOKEN - valueFrom: - secretKeyRef: - name: zhinian-worker-auth - key: ZHINIAN_INTERNAL_WORKER_TOKEN + # The session secret must be the same value the Go backend uses so + # the frontend middleware and the Go backend verify the same cookies. - name: ZHINIAN_AUTH_SESSION_SECRET valueFrom: secretKeyRef: name: zhinian-web-auth key: ZHINIAN_AUTH_SESSION_SECRET - volumeMounts: - - name: rds-ca - mountPath: /etc/zhinian/rds - readOnly: true startupProbe: httpGet: path: /api/health @@ -64,7 +57,7 @@ spec: failureThreshold: 24 readinessProbe: httpGet: - path: /api/ready + path: /api/health port: http periodSeconds: 10 timeoutSeconds: 5 @@ -89,7 +82,3 @@ spec: drop: ["ALL"] # The current image runs as root. Add a fixed non-root image user and # verify /app/.runtime permissions before enabling runAsNonRoot. - volumes: - - name: rds-ca - secret: - secretName: zhinian-rds-ca diff --git a/deploy/ack/worker.yaml b/deploy/ack/worker.yaml index c84ba82..040a65a 100644 --- a/deploy/ack/worker.yaml +++ b/deploy/ack/worker.yaml @@ -1,3 +1,6 @@ +# DEPRECATED (2026-08-14): production deploys the Go API workload with the +# embedded WorkerLoop (ZHINIAN_GO_EMBEDDED_WORKER=true). The Node Worker is +# local-development only; do not apply this manifest in production. apiVersion: apps/v1 kind: Deployment metadata: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index e451f71..5edd741 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -15,10 +15,25 @@ kubectl -n zhinian create secret generic zhinian-rds-ca \ --from-file=ca.pem=./path/to/downloaded-rds-ca.pem kubectl apply -f deploy/ack/configmap.yaml kubectl apply -f deploy/ack/secrets.example.yaml # 仅作模板;先替换全部占位值 -kubectl apply -f deploy/ack/web.yaml -f deploy/ack/worker.yaml \ +kubectl apply -f deploy/ack/web.yaml -f deploy/ack/go-api.yaml \ -f deploy/ack/service.yaml -f deploy/ack/ingress.yaml ``` +生产拓扑为 ADR-003 双工作负载:Next.js 只服务页面/静态资源/SSR(不持有任何 +RDS/服务商凭据,仅共享会话密钥);Go 工作负载 `zhinian-go-api` 独占 +`/api`、`/uploads`、`/generated-results`,内嵌 WorkerLoop(生产**不部署** +Node Worker,`worker.yaml` 已弃用保留)。Ingress 按路径分流:页面 → Web, +后端路径 → Go,`/api/internal/worker` → 无端点 deny Service。 + +Go 镜像构建: + +```bash +docker build -f backend/Dockerfile -t REGISTRY/PROJECT/zhinian-go-api:TAG backend/ +``` + +Go 首次启动时从 `zhinian-go-bootstrap` Secret 读取 +`ZHINIAN_BOOTSTRAP_ADMIN_*`,仅当不存在任何超级管理员时创建一次。 + 数据库 schema 由部署负责人在发布前手工执行,**不部署迁移 Job Pod**(清单 `deploy/ack/migration-job.yaml` 已弃用保留):使用迁移角色账号依次执行 `database/migrations/0001_initial_schema.sql`、`0002_generation_lifecycle_fencing.sql`, diff --git a/scripts/check-ack-manifests.mjs b/scripts/check-ack-manifests.mjs index 3303318..1e8225d 100644 --- a/scripts/check-ack-manifests.mjs +++ b/scripts/check-ack-manifests.mjs @@ -17,11 +17,28 @@ assert(migrationJob.includes("secretName: zhinian-rds-ca"), "migration Job must const web = read("web.yaml"); assert(/^\s*replicas: 1\s*$/m.test(web), "Web must default to one replica until object storage is shared"); -assert(web.includes("path: /api/ready"), "Web must use database-aware readiness"); +assert(web.includes("path: /api/health"), "Web must use process-level readiness (it is database-free in production)"); +assert(!web.includes("zhinian-web-db"), "Web must not hold RDS credentials in production"); +assert(!web.includes("rds-ca"), "Web must not mount the RDS CA in production"); + +const goApi = read("go-api.yaml"); +assert(/^\s*replicas: 1\s*$/m.test(goApi), "Go API must default to one replica until object storage is shared"); +assert(goApi.includes("path: /api/ready"), "Go API must use database-aware readiness"); +assert(goApi.includes("runAsNonRoot: true"), "Go API must run as a non-root user"); +assert(goApi.includes("name: zhinian-go-runtime"), "Go API must consume the Go runtime ConfigMap"); +assert(goApi.includes("name: zhinian-go-bootstrap"), "Go API must receive bootstrap administrator credentials"); +assert(goApi.includes("secretName: zhinian-rds-ca"), "Go API must mount the RDS CA"); + +const configMap = read("configmap.yaml"); +assert(configMap.includes("ZHINIAN_GO_EMBEDDED_WORKER: \"true\""), "Go runtime ConfigMap must embed the WorkerLoop"); const ingress = read("ingress.yaml"); assert(ingress.includes("path: /api/internal/worker"), "Ingress must intercept the internal worker prefix"); -assert(ingress.includes("name: zhinian-public-deny"), "Ingress must route the internal prefix away from Web"); +assert(ingress.includes("name: zhinian-public-deny"), "Ingress must route the internal prefix away from the workloads"); +assert(ingress.includes("name: zhinian-go-api"), "Ingress must route backend paths to the Go API Service"); +assert(ingress.includes("path: /uploads"), "Ingress must route /uploads to the Go API"); +assert(ingress.includes("path: /generated-results"), "Ingress must route /generated-results to the Go API"); +assert(ingress.includes("name: zhinian-web"), "Ingress must route pages/static paths to Web"); const service = read("service.yaml"); assert(service.includes("name: zhinian-public-deny"), "selectorless deny Service is required");