diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index bb3faaa0..40c97dca 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -268,6 +268,101 @@ lanes: variables: LANE: v03 NODE: NC01 + production: + extends: lanes.v03 + variables: + LANE: production + NODE: NC01 + environment: production + version: v0.3 + sourceBranch: release + workspace: /root/hwlab-release + cicdRepo: /root/hwlab-release-cicd.git + cicdRepoLock: /tmp/hwlab-release-cicd-repo.lock + app: hwlab-node-production + pipeline: hwlab-production-ci-image-publish + pipelineRunPrefix: hwlab-production-ci-poll + serviceAccountName: hwlab-production-tekton-runner + controlPlaneFieldManager: unidesk-hwlab-production-control-plane + gitopsBranch: release-gitops + catalogPath: deploy/artifact-catalog.production.json + runtime: + path: deploy/gitops/node/runtime-production + namespace: hwlab-production + renderDir: runtime-production + tektonDir: tekton-production + argoApplicationFile: application-production.yaml + public: + webUrl: https://hwlab.pikapython.com + apiUrl: https://hwlab.pikapython.com + targets: + NC01: + environment: production + workspace: /root/hwlab-release + sourceWorkspace: + install: + stateDir: /tmp/hwlab-release-source-workspace-bootstrap + sourceAuthority: + giteaMirrorRepoKey: hwlab-nc01-production + cicdRepo: /root/hwlab-release-cicd.git + cicdRepoLock: /tmp/hwlab-release-cicd-repo.lock + app: hwlab-node-production + pipeline: hwlab-nc01-production-ci-image-publish + pipelineRunPrefix: hwlab-nc01-production-ci-poll + serviceAccountName: hwlab-nc01-production-tekton-runner + controlPlaneFieldManager: unidesk-hwlab-nc01-production-control-plane + pipelineProvenance: + configRef: config/hwlab-node-lanes.yaml#lanes.production.targets.NC01 + gitopsBranch: release-gitops + catalogPath: deploy/artifact-catalog.nc01-production.json + runtime: + path: deploy/gitops/node/nc01/runtime-production + namespace: hwlab-production + renderDir: runtime-production + public: + webUrl: https://hwlab.pikapython.com + apiUrl: https://hwlab.pikapython.com + bootstrapAdmin: + displayName: HWLAB Production Admin + passwordSourceRef: hwlab/nc01-production-bootstrap-admin.env + secretName: hwlab-production-bootstrap-admin + codeAgentProvider: + secretName: hwlab-production-code-agent-provider + sourceRef: hwlab/nc01-production-code-agent-provider.env + codeAgentRuntime: + kafkaEventBridge: + configRef: config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api + clientId: hwlab-production-cloud-api + transactionalProjectorConsumerGroupId: hwlab-production-agentrun-event-projector + hwlabEventConsumerGroupId: hwlab-production-workbench-live-sse + publicExposure: + extends: templates.hwlabV03.publicExposurePk01 + mode: pk01-caddy-frp + publicBaseUrl: https://hwlab.pikapython.com + hostname: hwlab.pikapython.com + expectedA: 82.156.23.220 + frpc: + secretName: hwlab-production-frpc-secrets + webProxy: + remotePort: 22196 + apiProxy: + remotePort: 22195 + externalPostgres: + database: hwlab_nc01_production + migrationLedger: + identity: hwlab-nc01-production + table: hwlab_schema_migrations + cloudApi: + secretName: hwlab-cloud-api-production-db + sourceRef: hwlab/nc01-production-cloud-api-db.env + role: hwlab_nc01_production_app + openfga: + secretName: hwlab-production-openfga + sourceRef: hwlab/nc01-production-openfga-db.env + role: hwlab_nc01_production_app + variables: + LANE: production + NODE: NC01 networkProfiles: node-ci-egress: proxy: @@ -834,6 +929,9 @@ templates: externalPostgresConsumer: port: 5432 sslmode: require + migrationLedger: + identity: "hwlab-${nodeLower}-${lane}" + table: hwlab_schema_migrations cloudApi: secretName: hwlab-cloud-api-v03-db secretKey: database-url diff --git a/config/platform-infra/kafka.yaml b/config/platform-infra/kafka.yaml index a52487d8..9472ae2b 100644 --- a/config/platform-infra/kafka.yaml +++ b/config/platform-infra/kafka.yaml @@ -146,6 +146,22 @@ clients: - hwlab.event.debug.v1 dlqTopics: - agentrun.hwlab.event.dlq.v1 + - id: hwlab-production-cloud-api + namespace: hwlab-production + serviceAccountName: hwlab-cloud-api + kafkaUserName: hwlab-production-cloud-api + secretName: hwlab-production-cloud-api + produceTopics: + - hwlab.event.v1 + - hwlab.event.debug.v1 + - hwlab.agentrun.command.v1 + consumeTopics: + - agentrun.event.v1 + - agentrun.event.debug.v1 + - agentrun.hwlab.event.v1 + - hwlab.event.debug.v1 + dlqTopics: + - agentrun.hwlab.event.dlq.v1 - id: agentrun-v02-manager namespace: agentrun-v02 serviceAccountName: agentrun-nc01-v02-mgr diff --git a/config/platform-infra/pipelines-as-code.yaml b/config/platform-infra/pipelines-as-code.yaml index cea135da..ceedaddf 100644 --- a/config/platform-infra/pipelines-as-code.yaml +++ b/config/platform-infra/pipelines-as-code.yaml @@ -178,6 +178,22 @@ repositories: variables: NODE: NC01 LANE: v03 + - extends: templates.repositories.hwlabV03 + variables: + NODE: NC01 + LANE: production + id: hwlab-nc01-production + name: hwlab-nc01-production + secretName: pac-gitea-hwlab-nc01-production + params: + source_branch: release + gitops_branch: release-gitops + source_snapshot_prefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/release + pipeline_name: hwlab-nc01-production-ci-image-publish + pipeline_run_prefix: hwlab-nc01-production-ci-poll + service_account: hwlab-nc01-production-tekton-runner + lane: production + runtime_namespace: hwlab-production - id: selfmedia-nc01 name: selfmedia-nc01 namespace: selfmedia-ci @@ -372,6 +388,27 @@ consumers: hostNetwork: true dnsPolicy: ClusterFirstWithHostNet fsGroup: 1000 + - extends: templates.consumers.hwlabV03 + variables: + NODE: NC01 + LANE: production + id: hwlab-nc01-production + repositoryRef: hwlab-nc01-production + lane: production + pipeline: hwlab-nc01-production-ci-image-publish + pipelineRunPrefix: hwlab-nc01-production-ci-poll + argoApplication: hwlab-node-production + sourceArtifact: + mode: remote-pipeline-annotation + renderer: hwlab-runtime-lane + configRef: config/hwlab-node-lanes.yaml#lanes.production.targets.NC01 + pipelinePath: ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml + pipelineRunPath: .tekton/hwlab-nc01-production-pac.yaml + maxKeepRuns: 8 + taskRunTemplate: + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + fsGroup: 1000 - id: selfmedia-nc01 repositoryRef: selfmedia-nc01 node: NC01 diff --git a/scripts/src/hwlab-fake-model-provider.ts b/scripts/src/hwlab-fake-model-provider.ts index 73d565e2..165364c5 100644 --- a/scripts/src/hwlab-fake-model-provider.ts +++ b/scripts/src/hwlab-fake-model-provider.ts @@ -8,7 +8,7 @@ import { runCommand, type CommandResult } from "./command"; import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery"; import { resolveAgentRunLaneTarget, type AgentRunLaneSpec } from "./agentrun-lanes"; import { resolveSecretSourceRoot } from "./agentrun/secrets"; -import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "./hwlab-node-lanes"; +import { hwlabRuntimeLaneIds, hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "./hwlab-node-lanes"; import { parseEnvFile, sha256Fingerprint, shQuote } from "./platform-infra-ops-library"; type FakeModelProviderAction = "plan" | "materialize" | "apply" | "status" | "smoke"; @@ -149,7 +149,7 @@ function parseFakeModelProviderOptions(args: string[]): FakeModelProviderOptions const node = requiredOption(args, "--node"); if (!/^[A-Z][A-Z0-9_-]*$/u.test(node)) throw new Error("--node must be a simple node id such as NC01"); const laneRaw = requiredOption(args, "--lane"); - if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of v02, v03; got ${laneRaw}`); + if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of ${hwlabRuntimeLaneIds().join(", ")}; got ${laneRaw}`); const provider = optionValue(args, "--provider") ?? "fake-echo"; if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/u.test(provider)) throw new Error("--provider must be a Kubernetes-safe id"); const confirm = args.includes("--confirm"); diff --git a/scripts/src/hwlab-node-lanes.ts b/scripts/src/hwlab-node-lanes.ts index 3d3d9be3..0fd191d3 100644 --- a/scripts/src/hwlab-node-lanes.ts +++ b/scripts/src/hwlab-node-lanes.ts @@ -78,6 +78,11 @@ export interface HwlabRuntimeExternalPostgresRuntimeAccessSpec { readonly port: number; } +export interface HwlabRuntimeExternalPostgresMigrationLedgerSpec { + readonly identity: string; + readonly table: string; +} + export interface HwlabRuntimeExternalPostgresSpec { readonly provider: string; readonly configRef: string; @@ -87,6 +92,7 @@ export interface HwlabRuntimeExternalPostgresSpec { readonly runtimeAccess?: HwlabRuntimeExternalPostgresRuntimeAccessSpec; readonly sslmode: "require"; readonly database: string; + readonly migrationLedger: HwlabRuntimeExternalPostgresMigrationLedgerSpec; readonly cloudApi: HwlabRuntimeExternalPostgresComponentSpec; readonly openfga: HwlabRuntimeExternalPostgresComponentSpec; } @@ -1415,6 +1421,14 @@ function externalPostgresRuntimeAccessConfig(value: unknown, path: string): Hwla }; } +function externalPostgresMigrationLedgerConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresMigrationLedgerSpec { + const raw = asRecord(value, path); + return { + identity: stringField(raw, "identity", path), + table: stringField(raw, "table", path), + }; +} + function externalPostgresConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); @@ -1429,6 +1443,7 @@ function externalPostgresConfig(value: unknown, path: string): HwlabRuntimeExter ...(raw.runtimeAccess === undefined ? {} : { runtimeAccess: externalPostgresRuntimeAccessConfig(raw.runtimeAccess, `${path}.runtimeAccess`) }), sslmode, database: stringField(raw, "database", path), + migrationLedger: externalPostgresMigrationLedgerConfig(raw.migrationLedger, `${path}.migrationLedger`), cloudApi: externalPostgresComponentConfig(raw.cloudApi, `${path}.cloudApi`), openfga: externalPostgresComponentConfig(raw.openfga, `${path}.openfga`), }; diff --git a/scripts/src/hwlab-node/entry.ts b/scripts/src/hwlab-node/entry.ts index 749918b6..0db2be5f 100644 --- a/scripts/src/hwlab-node/entry.ts +++ b/scripts/src/hwlab-node/entry.ts @@ -600,7 +600,7 @@ export function parseNodeObservabilityOptions(args: string[]): NodeObservability const node = requiredOption(args, "--node"); assertNodeId(node); const laneRaw = requiredOption(args, "--lane"); - if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of v02, v03; got ${laneRaw}`); + if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of ${hwlabRuntimeLaneIds().join(", ")}; got ${laneRaw}`); const confirm = args.includes("--confirm"); const dryRun = args.includes("--dry-run"); if (confirm && dryRun) throw new Error("observability accepts only one of --confirm or --dry-run"); diff --git a/scripts/src/hwlab-node/plan.ts b/scripts/src/hwlab-node/plan.ts index 2565ea31..4233b1ae 100644 --- a/scripts/src/hwlab-node/plan.ts +++ b/scripts/src/hwlab-node/plan.ts @@ -55,7 +55,7 @@ export function parseNodeScopedDelegatedOptions(domain: DelegatedNodeDomain, arg const node = requiredOption(args, "--node"); assertNodeId(node); const laneRaw = requiredOption(args, "--lane"); - if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of v02, v03; got ${laneRaw}`); + if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of ${hwlabRuntimeLaneIds().join(", ")}; got ${laneRaw}`); const spec = hwlabRuntimeLaneSpecForNode(laneRaw, node); const confirm = args.includes("--confirm"); const dryRun = args.includes("--dry-run"); @@ -201,6 +201,7 @@ export function nodeRuntimeExpected(spec: HwlabRuntimeLaneSpec): Record