diff --git a/config/platform-db/postgres-pk01.yaml b/config/platform-db/postgres-pk01.yaml index a165b472..3bedd837 100644 --- a/config/platform-db/postgres-pk01.yaml +++ b/config/platform-db/postgres-pk01.yaml @@ -121,6 +121,11 @@ postgres: user: sub2api address: 10.0.8.0/22 method: scram-sha-256 + - type: hostssl + database: hwlab_d601_v03 + user: hwlab_d601_v03_app + address: 10.0.8.0/22 + method: scram-sha-256 - type: hostssl database: sub2api user: sub2api @@ -349,7 +354,7 @@ exports: sourceSecretRef: platform-db/hwlab-d601-v03-db.env render: envKey: DATABASE_URL - format: postgresql://$(HWLAB_D601_V03_DB_USER):$(HWLAB_D601_V03_DB_PASSWORD)@$(PGHOST):5432/$(HWLAB_D601_V03_DB_NAME)?sslmode=require + format: postgresql://$(HWLAB_D601_V03_DB_USER):$(HWLAB_D601_V03_DB_PASSWORD)@$(PGHOST):5432/$(HWLAB_D601_V03_DB_NAME)?sslmode=require&uselibpqcompat=true variables: PGHOST: 82.156.23.220 writeToSecretSource: diff --git a/docs/reference/cli.md b/docs/reference/cli.md index e79fabf3..37d77bd6 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -24,6 +24,8 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动 `hwlab nodes control-plane infra tools-image status|build|logs --node D601 --lane v03` 是 D601 tools image 的受控入口。Dockerfile 必须由 `config/hwlab-node-control-plane.yaml` 的 `tekton.toolsImage.dockerfileInline` 声明,输入镜像必须列在 `publicBaseImages`,构建参数和网络模式也来自 YAML;confirmed build 只在 D601 后台异步构建并推送到 node-local registry,返回 status/logs 轮询命令。`hwlab nodes control-plane infra argo status|apply|logs --node D601 --lane v03` 是 D601 Argo CD 的声明式安装入口。Argo 版本、官方 manifest URL、镜像 rewrite/preload、field manager、imagePullPolicy、CRD 列表、期望 Deployment/StatefulSet 以及生成的 AppProject/Application 都必须来自同一个 YAML;`argo apply --confirm` 只执行可重复 server-side apply 和后台轮询,不把原生 `kubectl apply`、手工 Argo CLI 或临时 manifest 作为正式安装路径。 +`hwlab nodes control-plane runtime-migration --node --lane vNN [--dry-run|--allow-live-db-read --dry-run|--confirm]` 是 node-scoped runtime lane 的受控 schema migration 入口。它只通过目标 runtime namespace 当前 `deployment/hwlab-cloud-api -c hwlab-cloud-api` 内 repo-owned `cmd/hwlab-cloud-api/migrate.ts` 执行,输出 report path、source commit 和有界 stdout/stderr 摘要;不读取或打印 Secret 值、不手写 `psql`、不把 pod 内临时命令沉淀成正式流程。D601 v03 这类由 UniDesk YAML 声明的外置 PK01 PostgreSQL 切换,DB/Secret/bridge 仍以 UniDesk YAML 和 `platform-db postgres ...`、`hwlab nodes control-plane apply|trigger-current` 为 source truth;runtime migration 只负责在已发布 runtime 上补齐应用 schema。 + ## Command Model - `help` 输出命令索引,适合作为交互式入口。 diff --git a/scripts/src/hwlab-node.ts b/scripts/src/hwlab-node.ts index a2b3599f..b5ae2168 100644 --- a/scripts/src/hwlab-node.ts +++ b/scripts/src/hwlab-node.ts @@ -117,6 +117,8 @@ export function hwlabNodeHelp(): Record { "bun scripts/cli.ts hwlab nodes control-plane refresh --node G14 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes control-plane sync --node D601 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes control-plane trigger-current --node G14 --lane v03 --confirm", + "bun scripts/cli.ts hwlab nodes control-plane runtime-migration --node D601 --lane v03 --dry-run", + "bun scripts/cli.ts hwlab nodes control-plane runtime-migration --node D601 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes control-plane allow-endpoint-bridge --node G14 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes git-mirror status --node G14 --lane v03", "bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-v03-openfga", @@ -142,7 +144,7 @@ async function runNodeDelegatedDomain(config: Config, domain: DelegatedNodeDomai } if (domain === "control-plane" && scoped.node !== defaultSpec.nodeId) { if (scoped.action === "status") return nodeRuntimeControlPlaneStatus(scoped); - if (scoped.action === "apply" || scoped.action === "trigger-current" || scoped.action === "refresh" || scoped.action === "sync") { + if (scoped.action === "apply" || scoped.action === "trigger-current" || scoped.action === "refresh" || scoped.action === "sync" || scoped.action === "runtime-migration") { if (scoped.confirm && !scoped.dryRun && !scoped.wait) return startNodeDelegatedJob(scoped); return nodeRuntimeControlPlaneRun(scoped); } @@ -183,6 +185,7 @@ function parseNodeScopedDelegatedOptions(domain: DelegatedNodeDomain, args: stri dryRun: boolean; wait: boolean; rerun: boolean; + allowLiveDbRead: boolean; timeoutSeconds: number; originalArgs: string[]; spec: HwlabRuntimeLaneSpec; @@ -206,6 +209,7 @@ function parseNodeScopedDelegatedOptions(domain: DelegatedNodeDomain, args: stri dryRun, wait: args.includes("--wait"), rerun: args.includes("--rerun"), + allowLiveDbRead: args.includes("--allow-live-db-read"), timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 1800, 3600), originalArgs: [...args], spec, @@ -343,7 +347,7 @@ function nodeRuntimeUnsupportedAction(scoped: ReturnType): Record { + const spec = scoped.spec; + if (scoped.allowLiveDbRead && scoped.confirm) throw new Error("control-plane runtime-migration accepts --allow-live-db-read only with dry-run/source-check mode, not --confirm"); + if (!scoped.confirm && !scoped.dryRun) throw new Error("control-plane runtime-migration requires --dry-run or --confirm"); + const head = resolveNodeRuntimeLaneHead(spec); + const sourceCommit = head.sourceCommit; + if (sourceCommit === null) { + return { + ok: false, + command: `hwlab nodes control-plane runtime-migration --node ${scoped.node} --lane ${scoped.lane}`, + node: scoped.node, + lane: scoped.lane, + phase: "source-head", + mutation: false, + degradedReason: "node-runtime-source-head-unresolved", + headProbe: compactRuntimeCommand(head.result), + }; + } + const reportPath = `/tmp/hwlab-${scoped.node.toLowerCase()}-${scoped.lane}-runtime-migration-${shortSha(sourceCommit)}.json`; + const migrationArgs = scoped.dryRun + ? [ + ...(scoped.allowLiveDbRead ? ["--dry-run", "--allow-live-db-read", "--confirm-dev"] : ["--check"]), + "--report", + reportPath, + ] + : [ + "--apply", + "--confirm-dev", + "--confirmed-non-production", + "--report", + reportPath, + ]; + const result = runNodeK3sArgs(spec, [ + "kubectl", + "exec", + "-n", + spec.runtimeNamespace, + "deployment/hwlab-cloud-api", + "-c", + "hwlab-cloud-api", + "--", + "sh", + "-lc", + `cd /workspace/hwlab-boot/repo && exec bun cmd/hwlab-cloud-api/migrate.ts ${migrationArgs.map(shellQuote).join(" ")}`, + ], scoped.timeoutSeconds); + const ok = isCommandSuccess(result); + return { + ok, + command: `hwlab nodes control-plane runtime-migration --node ${scoped.node} --lane ${scoped.lane}`, + node: scoped.node, + lane: scoped.lane, + mode: scoped.dryRun ? scoped.allowLiveDbRead ? "live-read-dry-run" : "source-check" : "confirmed-apply", + sourceCommit, + runtimeNamespace: spec.runtimeNamespace, + target: "deployment/hwlab-cloud-api -c hwlab-cloud-api", + workingDirectory: "/workspace/hwlab-boot/repo", + migrationCommand: ["bun", "cmd/hwlab-cloud-api/migrate.ts", ...migrationArgs], + reportPath, + mutation: !scoped.dryRun && ok, + result: compactRuntimeCommand(result), + degradedReason: ok ? undefined : "node-runtime-migration-failed", + next: scoped.dryRun + ? { + liveReadDryRun: `bun scripts/cli.ts hwlab nodes control-plane runtime-migration --node ${scoped.node} --lane ${scoped.lane} --allow-live-db-read --dry-run`, + apply: `bun scripts/cli.ts hwlab nodes control-plane runtime-migration --node ${scoped.node} --lane ${scoped.lane} --confirm`, + } + : { + health: `curl -fsS --max-time 20 ${spec.publicApiUrl}/health/live`, + status: `bun scripts/cli.ts hwlab nodes control-plane status --node ${scoped.node} --lane ${scoped.lane}`, + }, + }; +} + function nodeRuntimeApply(scoped: ReturnType): Record { const spec = scoped.spec; const head = resolveNodeRuntimeLaneHead(spec);