fix: reload AgentRun DB secrets from YAML
This commit is contained in:
@@ -214,6 +214,7 @@ YAML-only lane 以 `config/agentrun.yaml` 为部署真相;node/lane、source w
|
||||
bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02
|
||||
bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 [--dry-run|--confirm]
|
||||
bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 [--dry-run|--confirm]
|
||||
bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 [--dry-run|--confirm]
|
||||
bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 [--dry-run|--confirm]
|
||||
bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 [--full]
|
||||
```
|
||||
@@ -221,6 +222,7 @@ bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 [--full]
|
||||
- `plan`: 只读解析 YAML,输出控制面、source、image build、GitOps、runtime 和 Secret plan,不打印 Secret value
|
||||
- `apply`: 按 YAML 渲染并 apply Tekton RBAC/Pipeline、Argo AppProject/Application 和 runtime namespace
|
||||
- `secret-sync`: 按 YAML 的 Secret sourceRef/keyMapping 同步 runtime Secret 和外置 DB Secret,只输出 fingerprint
|
||||
- `restart`: patch manager Deployment 的 restart annotation 并等待 rollout,用于 Secret export/DB 连接串变化后让 workload 读取新 Secret;不要手工删除 Pod
|
||||
- `trigger-current`: 确保 source branch/workspace,删除新 lane source branch 的 `deploy/deploy.json`,构建并推送 YAML 声明的 image,渲染 GitOps/artifact catalog,触发 git-mirror sync 和 provenance PipelineRun
|
||||
- `status`: 汇总 node/lane 控制面、runtime、Argo、Secret、source workspace 和 GitOps 对齐状态
|
||||
|
||||
|
||||
@@ -149,11 +149,13 @@ PK01 host-native PostgreSQL 是平台外置状态库样板,声明文件是 `co
|
||||
```bash
|
||||
bun scripts/cli.ts platform-db postgres plan --config config/platform-db/postgres-pk01.yaml
|
||||
bun scripts/cli.ts platform-db postgres status --config config/platform-db/postgres-pk01.yaml
|
||||
bun scripts/cli.ts platform-db postgres export-secrets --config config/platform-db/postgres-pk01.yaml --confirm
|
||||
bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm
|
||||
bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm --wait
|
||||
```
|
||||
|
||||
- `plan` / `status` 只读;`apply --confirm` 默认创建本地异步 job;`apply --confirm --wait` 会启动 PK01 侧 root-owned job 并短轮询。
|
||||
- `export-secrets --confirm` 只按 YAML 重新物化本地 Secret source/export 文件,不触碰 PK01 远端 PostgreSQL;连接串格式、consumer target 或 Secret export 变更优先用它,再走对应消费者的 Secret sync。
|
||||
- 输出只显示 Secret key 名、presence、fingerprint、连接 host、SSL 状态和状态摘要;禁止打印密码或完整 `DATABASE_URL`。
|
||||
- 同一个 PK01 PostgreSQL 实例可承载多个 YAML 声明的 role/database;新增消费者按 `secrets.entries`、`objects.roles`、`objects.databases`、`postgres.auth.pgHba` 和 `exports.connectionStrings` 成套声明,不新开 PostgreSQL 实例,也不默认用 schema 隔离应用状态。
|
||||
- 跨节点消费者必须直连 YAML 的 `postgres.network.connectionHost`,当前是 PK01 公网 endpoint;不要让 D601/G14/Sub2API/HWLAB/AgentRun 通过 master server 中转 PostgreSQL。
|
||||
|
||||
@@ -461,7 +461,7 @@ exports:
|
||||
sourceSecretRef: platform-db/agentrun-v02-db.env
|
||||
render:
|
||||
envKey: DATABASE_URL
|
||||
format: postgresql://$(AGENTRUN_V02_DB_USER):$(AGENTRUN_V02_DB_PASSWORD)@$(PGHOST):5432/$(AGENTRUN_V02_DB_NAME)?sslmode=require
|
||||
format: postgresql://$(AGENTRUN_V02_DB_USER):$(AGENTRUN_V02_DB_PASSWORD)@$(PGHOST):5432/$(AGENTRUN_V02_DB_NAME)?sslmode=require&uselibpqcompat=true
|
||||
variables:
|
||||
PGHOST: 82.156.23.220
|
||||
writeToSecretSource:
|
||||
|
||||
@@ -85,6 +85,8 @@ bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run
|
||||
bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --confirm
|
||||
bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --dry-run
|
||||
bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --confirm
|
||||
bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 --dry-run
|
||||
bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 --confirm
|
||||
bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --dry-run
|
||||
bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --confirm
|
||||
bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 --full
|
||||
@@ -92,7 +94,7 @@ bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02 --full
|
||||
|
||||
`status` 只读观察 `G14:/root/agentrun-v01` 当前 commit、对应 PipelineRun、GitOps latest、Argo Application、`agentrun-v01` workload、manager source commit 和 git mirror 摘要,并报告 Argo revision 是否对齐 `v0.1-gitops` latest。默认输出是 compact commander 视图,只保留 `summary`、阶段耗时、对齐状态和 drill-down 命令;需要远端 stdout/stderr tail 时显式加 `--full`,需要原始 git mirror cache 输出时显式加 `--raw`。`status` 额外支持 `--pipeline-run <name>` 与 `--source-commit <sha>` 定点查询,返回 `target`、`targetValidation` 和 `next.*` drill-down,便于直接判断某次 run 是成功、历史成功、运行中、缺失还是 source mismatch。`status` 会向 stderr 输出 `agentrun.control-plane.status.progress` 阶段事件,覆盖 `source`、`runtime` 和 `git-mirror`,避免长时间聚合时无可见进展。`trigger-current` 会先把固定 source worktree 快进到 `origin/v0.1`,再以当前 commit 创建 commit-pinned PipelineRun;同名 PipelineRun 正在运行或已经成功时必须拒绝重复触发,只允许在失败态或不存在时创建。该命令只提交 CI/CD 工作,不等待完整 PipelineRun 或 rollout 完成,后续用 `status` 轮询。`refresh` 只对 `argocd/agentrun-g14-v01` 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。
|
||||
|
||||
YAML-only lane 的 `trigger-current` 会先确保目标 source workspace/branch 存在,再从 UniDesk YAML 声明的 image build、GitOps branch/path、runtime namespace、Secret、数据库和 manager env 渲染 artifact catalog 与 GitOps desired state。该路径会删除新 lane source branch 中的 `deploy/deploy.json`,因为部署真相已经迁入 UniDesk YAML;旧 `v0.1` branch 中历史文件只作为迁移前遗留产物存在,不能作为新 lane 的事实来源。
|
||||
YAML-only lane 的 `trigger-current` 会先确保目标 source workspace/branch 存在,再从 UniDesk YAML 声明的 image build、GitOps branch/path、runtime namespace、Secret、数据库和 manager env 渲染 artifact catalog 与 GitOps desired state。该路径会删除新 lane source branch 中的 `deploy/deploy.json`,因为部署真相已经迁入 UniDesk YAML;旧 `v0.1` branch 中历史文件只作为迁移前遗留产物存在,不能作为新 lane 的事实来源。Secret export 格式或外部数据库连接参数变化时,先用 `platform-db postgres export-secrets --confirm` 物化本地 Secret source,再用 `agentrun control-plane secret-sync --node <node> --lane <lane> --confirm` 下发,最后用 `agentrun control-plane restart --node <node> --lane <lane> --confirm` 让 manager Deployment 通过 rollout 读取新 Secret;不要手工删除 Pod 或直接 patch Secret。
|
||||
|
||||
`cleanup-runs` 是 AgentRun `v0.1` 完成态 CI workspace retention 入口,只清理 `agentrun-ci` namespace 中超过 `--min-age-minutes` 的 `agentrun-v01-ci-*` PipelineRun,通过 Tekton ownerRef 释放临时 workspace PVC。dry-run 必须披露候选 PipelineRun、owned PVC、active mount 保护、local-path 实际估算 bytes 和 confirm 命令。默认保护最新完成的 PipelineRun,保留当前 CI/CD 状态证据。`cleanup-released-pvs` 是二次回收入口,只处理 `agentrun-ci`、`local-path`、`Delete` reclaim policy 的 `Released` PV;它不触碰 `agentrun-v01` runtime namespace、业务 PVC、Secret、registry storage 或 GitOps desired state。磁盘治理和 G14 safe-stop 规则见 `docs/reference/gc.md`。
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动
|
||||
- `gc plan|run --confirm|db-trace|policy|remote` 是主 server 和受控 provider 的磁盘高水位一次性缓解与长期防膨胀入口。`plan` 只读输出候选、风险、估算收益和保护对象;`run` 必须显式 `--confirm`;`gc remote <providerId> ...` 通过 UniDesk SSH 透传执行远端 GC,`--target-use-percent N` 会在 `summary.target` 中报告目标水位所需释放量、候选估算、预计水位、缺口和 safe-stop 决策。默认只包含 allowlisted `/tmp` 诊断目录;非 allowlist stale `/tmp` 直接子项必须显式 `--include-stale-tmp`,并只允许删除 `/tmp` 一级子项且避开系统 socket/session 前缀。G14/HWLAB registry retention、受限 core dump、保护对象、safe-stop 线和长期收益表的权威规则见 `docs/reference/gc.md`。
|
||||
- `server rebuild <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>` 创建异步 job,先构建目标服务镜像,随后在 `.state/locks/server-compose.lock` 串行保护下用 `--no-deps --force-recreate` 替换目标 service 并等待容器 `healthy/running`;该命令用于替代手工删除容器的兜底流程,其中 `dev-frontend-proxy` 只更新主 server dev 入口薄代理,`todo-note`、`code-queue-mgr`、`project-manager`、`baidu-netdisk` 和 `oa-event-flow` 只重建主 server 承载的对应后端,不会重建或删除 database 命名卷。D601 Code Queue 执行面不由 `server rebuild` 管理;Rust backend-core 常规迭代不得用该命令在 master server 编译,只有明确的 backend-core 主 server 上线例外可以按限流、异步轮询和 health 证据执行,规则见 `docs/reference/dev-environment.md`。
|
||||
- `provider attach <providerId> [--master-server URL] [--up] [--force]` 在新计算节点生成两项配置的 provider-gateway 挂载包:`.state/provider-<ID>.env` 默认只包含 `UNIDESK_MASTER_SERVER` 与 `PROVIDER_ID`,`provider-<ID>.yml` 固定 Docker socket、`pid: "host"`、`restart: always`、只读 `/workspace` 和 SSH 维护私钥挂载;`--up` 会立即执行生成的 `docker compose up -d --build`。`provider triage <providerId> [--observed-error text] [--observed-scope scope] [--microservice id ...] [--full|--raw]` 是只读多信号健康裁决入口,会把单路径 `provider is not online`、SSH 超时、registry 失败和 service proxy 失败归类成 `runner-local-observation-gap`、`service-degraded`、`provider-degraded` 或 `global-blocker`。默认输出只返回裁决、scope、失败/降级/未知信号和有界 evidence 摘要,完整 evidence 必须显式加 `--full` 或 `--raw`;推荐交叉验证命令仍包含 `debug health`、`debug dispatch <providerId> host.ssh --wait-ms 15000`、`trans <providerId> argv true`、`artifact-registry health --provider-id <providerId>`、`microservice health k3sctl-adapter`、`microservice health code-queue` 和 `codex tasks --view supervisor --limit 20`。
|
||||
- `platform-db postgres plan|status|apply --config config/platform-db/postgres-pk01.yaml` 管理 YAML 声明的 PK01 host-native PostgreSQL。`plan` 和 `status` 只读采集远端 host、PostgreSQL、TLS、DNS alias 和 Secret 形态;`apply --confirm` 默认创建本地异步 job,`apply --confirm --wait` 用远端 root-owned job 收敛 systemd PostgreSQL、TLS、`pg_hba`、role/database、Secret export 和备份 timer。输出不得打印密码或完整 `DATABASE_URL`。跨节点消费者使用 YAML 中的 `connectionHost` 直连 PK01 公网 endpoint,DNS alias 不作为 `sslmode=require` 切库 blocker,PK01 规则见 `docs/reference/pk01.md`。
|
||||
- `platform-db postgres plan|status|export-secrets|apply --config config/platform-db/postgres-pk01.yaml` 管理 YAML 声明的 PK01 host-native PostgreSQL。`plan` 和 `status` 只读采集远端 host、PostgreSQL、TLS、DNS alias 和 Secret 形态;`export-secrets --confirm` 只按 YAML 物化本地 Secret source/export 文件,不触碰远端 PostgreSQL;`apply --confirm` 默认创建本地异步 job,`apply --confirm --wait` 用远端 root-owned job 收敛 systemd PostgreSQL、TLS、`pg_hba`、role/database、Secret export 和备份 timer。输出不得打印密码或完整 `DATABASE_URL`。跨节点消费者使用 YAML 中的 `connectionHost` 直连 PK01 公网 endpoint,DNS alias 不作为 `sslmode=require` 切库 blocker,PK01 规则见 `docs/reference/pk01.md`。
|
||||
- `trans <route> [operation args...]` / `tran <route> [operation args...]` 通过 backend-core 内网 WebSocket broker 和 provider-gateway 的 Host SSH / WSL SSH 维护桥连接目标节点;`route` 基础形态是 provider id,例如 `D601` 或 `G14`,也可以扩展为纯定位路径 `provider:plane[:namespace:resource[:container]]`,例如 `D601:win`、`D601:win/c/test`、`G14:k3s`、`D601:k3s` 或 `G14:k3s:<namespace>:<workload>`。WSL provider 的 Windows plane 固定使用 `win`,不得使用 `win32`;Windows operation 必须显式区分:`ps` 执行 Windows PowerShell heredoc 或一行 PowerShell 命令,`cmd` 执行 cmd.exe/batch,`skills` 发现 Windows skill 目录。需要 Windows cwd 时用 `trans D601:win/c/test ps` 或 `trans D601:win/c/test cmd cd`,CLI 自动设置 UTF-8/Python 编码默认值;`cmd` 额外设置 `chcp 65001`。非交互远端命令优先使用 `trans <providerId> argv ...`;需要 POSIX shell 脚本、管道、变量或循环时优先使用 quoted heredoc 单步传输,例如 `trans G14 script <<'SCRIPT'`、`trans G14:k3s script <<'SCRIPT'` 或 `trans G14:k3s:<namespace>:<workload> script <<'SCRIPT'`,把脚本走 stdin。`script` 只表示 host/k3s POSIX shell,不表示 Windows PowerShell;Windows PowerShell 必须写 `trans <provider>:win ps <<'PS'`。`script -- '<单个字符串>'` 是无需 stdin 的远端 POSIX shell one-liner,例如 `trans G14:/root/hwlab script -- 'cd /root/hwlab && git status --short --branch'`;`script -- <多个 argv>` 才是 direct argv,适合 `trans D601:/path script -- sed -n '1,20p' file` 这类带短横线的单进程命令。顶层 remote option parser 必须保留命令已经开始后的 `--`,不得把它吞成全局选项结束符。需要远端改文本文件时默认优先使用 `<route> apply-patch < patch.diff`;需要可靠传输非文本或整文件时使用 `<route> upload <local-file> <remote-file>` 和 `<route> download <remote-file> <local-file>`,CLI 会按字节数与 SHA-256 自动校验并在 provider-gateway stdin/argv 限制下切换客户端分块策略;需要旧 helper 时显式使用 `<provider>:k3s:<namespace>:<workload> apply-patch-v1` 或 `<providerId> apply-patch-v1`。ssh-like 命令遇到 timeout/kex/255 类失败时,CLI 会在 stderr 追加一行 `UNIDESK_SSH_HINT` JSON,提示 stdin script/argv 重试和 provider triage 交叉验证。
|
||||
- `trans <route> apply-patch < patch.diff` 是默认推荐的远端 patch 入口:本地 TypeScript line-based engine 解析和计算新文件内容,远端 route 只负责读写文件;支持 host workspace、k3s pod workspace、Windows workspace route(例如 `D601:win/c/test`)和 frontend transport,并优先处理长中文/Unicode、低上下文插入、重复块 `@@` 定位等旧 helper 容易失败的场景。`apply-patch` 输出按 Codex 标准文本口径,不套 UniDesk JSON 限制:成功 stdout 为 `Success. Updated the following files:`,失败 stdout 为空、stderr 写失败原因;多文件补丁中途失败时,stderr 只列出第一个失败前已成功执行的 hunk 和失败 hunk,随后按 Codex 语义停止,不继续尝试后续 hunk。v2 兼容常见 MiniMax/MXCX 非标准补丁输入,例如重复 nested `*** Begin Patch` / `*** End Patch` envelope、unified-diff hunk header、Add/Delete 误加 `@@`、Update context 漏掉前导空格,并在 stderr 给出 canonical 写法 hint;parser 或上下文失败时仍坚持唯一 v2 引擎,只提示修正 patch 文本或 hunk context,不自动重试或切换到 `apply-patch-v1`;大块/函数替换因上下文过期失败时,正确动作是重新读取当前目标块、缩小或拆分 Update File hunk 后继续用 `apply-patch`,不得改走 `download`/`upload`、远端 Python/Perl/sed heredoc 或整文件重写。Windows route 复用同一套 v2 核心算法,只把底层读写替换成 PowerShell 文件系统接口;`trans <providerId> apply-patch-v1 [tool args...] < patch.diff` 保留为显式 legacy 入口,直接调用远端注入的 `apply_patch` sh/perl helper;默认 `apply-patch` 不把 v1 当 fallback。
|
||||
- `apply-patch` v2 每次结束都会在 stderr 追加一行 `UNIDESK_APPLY_PATCH_TIMING {json}`,字段包含 `durationMs`、`patchBytes`、`fileCount`、`hunkCount`、`changedCount`、`remoteOperationCount`、`remoteOperationCounts`、`remoteElapsedMs`、`remoteFailureCount`、`providerId`、`route` 和 `transport`(可得时)。普通 POSIX host/k3s 和 Windows workspace 远端的多文件 `Update File` patch 会优先合并成 bulk read/write,避免每个文件单独 stat/read/write 的 SSH 往返;Add/Delete/Move 等复杂 patch 保持原有逐步语义。timing 摘要只用于定位慢在 patch 解析、远端 stat/read/write 或 bulk read/write、provider session 还是传输层,不能替代 Codex 标准 stdout/stderr 成功失败文本,也不是门禁或自动判断。
|
||||
|
||||
@@ -70,7 +70,7 @@ The public certificate depends on DNS. The `api.pikapython.com` record must reso
|
||||
|
||||
## Host PostgreSQL
|
||||
|
||||
PK01 host-native PostgreSQL is declared by `config/platform-db/postgres-pk01.yaml` and managed through `bun scripts/cli.ts platform-db postgres plan|status|apply`; daily operation commands live in `$unidesk-ops` at `.agents/skills/unidesk-ops/SKILL.md`. It is a host systemd service, not a Docker container or k3s workload. The YAML is the source of truth for PostgreSQL version, TLS mode, listening addresses, `pg_hba` source CIDRs, generated Secret source files, exported `DATABASE_URL`, and backup timer settings.
|
||||
PK01 host-native PostgreSQL is declared by `config/platform-db/postgres-pk01.yaml` and managed through `bun scripts/cli.ts platform-db postgres plan|status|export-secrets|apply`; daily operation commands live in `$unidesk-ops` at `.agents/skills/unidesk-ops/SKILL.md`. It is a host systemd service, not a Docker container or k3s workload. The YAML is the source of truth for PostgreSQL version, TLS mode, listening addresses, `pg_hba` source CIDRs, generated Secret source files, exported `DATABASE_URL`, and backup timer settings.
|
||||
|
||||
The PK01 PostgreSQL cluster is a shared platform database instance. Multiple UniDesk services may be declared as separate `objects.roles[]` and `objects.databases[]` entries, with matching Secret sources, `pg_hba` hostssl rules and optional connection-string exports. A new platform consumer should use its own database and role inside this existing instance; do not create a second PostgreSQL instance or use one database with multiple schemas as the default isolation boundary for application state.
|
||||
|
||||
@@ -80,6 +80,8 @@ Cross-node platform consumers must connect directly to the YAML-declared `postgr
|
||||
|
||||
Exported connection strings are written under the configured ignored Secret root and must never be committed or printed in full. CLI output may show key names, presence, fingerprints, selected host, SSL status, app connection status, and source/target Secret references only. If a consumer's public egress IP changes, update the YAML `allowSources` and matching `pg_hba` rules, then use the `$unidesk-ops` PK01 Host PostgreSQL workflow to apply and recheck status.
|
||||
|
||||
When only a connection-string export format or consumer target changes, use `platform-db postgres export-secrets --confirm` first. That command updates local ignored Secret source/export files from YAML without restarting or reconfiguring the PK01 PostgreSQL service; the consumer-specific Secret sync command is still required afterward.
|
||||
|
||||
Backup coverage is explicit YAML state. Do not assume every declared database is backed up unless the backup configuration names it directly or the CLI reports multi-database coverage.
|
||||
|
||||
## Disk GC Policy
|
||||
|
||||
@@ -79,6 +79,8 @@ export function agentRunHelp(): unknown {
|
||||
"bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02",
|
||||
"bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --dry-run",
|
||||
"bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --confirm",
|
||||
"bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 --dry-run",
|
||||
"bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 --confirm",
|
||||
"bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --dry-run",
|
||||
"bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --confirm",
|
||||
"bun scripts/cli.ts agentrun control-plane status",
|
||||
@@ -121,6 +123,7 @@ export async function runAgentRunCommand(config: UniDeskConfig | null, args: str
|
||||
if (action === "apply") return await controlPlaneApply(config, parseLaneConfirmOptions(actionArgs));
|
||||
if (action === "status") return await status(config, parseStatusOptions(actionArgs));
|
||||
if (action === "secret-sync") return await secretSync(config, parseSecretSyncOptions(actionArgs));
|
||||
if (action === "restart") return await restartYamlLane(config, parseLaneConfirmOptions(actionArgs));
|
||||
if (action === "expose") return await exposeAgentRun(config, parseConfirmOptions(actionArgs));
|
||||
if (action === "trigger-current") return await triggerCurrent(config, parseTriggerOptions(actionArgs));
|
||||
if (action === "refresh") return await refresh(config, parseConfirmOptions(actionArgs));
|
||||
@@ -2267,12 +2270,60 @@ async function statusYamlLane(config: UniDeskConfig, options: StatusOptions, tar
|
||||
postgresStatus: spec.database.configRef ? `bun scripts/cli.ts platform-db postgres status --config ${spec.database.configRef}` : null,
|
||||
postgresApply: spec.database.configRef ? `bun scripts/cli.ts platform-db postgres apply --config ${spec.database.configRef} --confirm` : null,
|
||||
secretSync: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
||||
restart: `bun scripts/cli.ts agentrun control-plane restart --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
||||
statusFull: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane} --full`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function restartYamlLane(config: UniDeskConfig, options: LaneConfirmOptions): Promise<Record<string, unknown>> {
|
||||
const { configPath, spec } = resolveAgentRunLaneTarget(options);
|
||||
const plan = {
|
||||
node: spec.nodeId,
|
||||
kubeRoute: spec.nodeKubeRoute,
|
||||
lane: spec.lane,
|
||||
namespace: spec.runtime.namespace,
|
||||
deployment: spec.runtime.managerDeployment,
|
||||
annotation: "kubectl.kubernetes.io/restartedAt",
|
||||
reason: "reload-lane-secrets-or-runtime-env",
|
||||
valuesPrinted: false,
|
||||
};
|
||||
if (options.dryRun || !options.confirm) {
|
||||
return {
|
||||
ok: true,
|
||||
command: "agentrun control-plane restart",
|
||||
mode: "dry-run",
|
||||
mutation: false,
|
||||
configPath,
|
||||
target: agentRunLaneSummary(spec),
|
||||
plan,
|
||||
next: {
|
||||
confirm: `bun scripts/cli.ts agentrun control-plane restart --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
||||
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
const result = await capture(config, spec.nodeKubeRoute, ["script", "--", restartYamlLaneScript(spec)]);
|
||||
const payload = captureJsonPayload(result);
|
||||
return {
|
||||
ok: result.exitCode === 0 && payload.ok !== false,
|
||||
command: "agentrun control-plane restart",
|
||||
mode: "confirmed-rollout-restart",
|
||||
mutation: true,
|
||||
configPath,
|
||||
target: agentRunLaneSummary(spec),
|
||||
plan,
|
||||
result: payload,
|
||||
capture: compactCapture(result, { full: result.exitCode !== 0, stdoutTailChars: 4000, stderrTailChars: 4000 }),
|
||||
next: {
|
||||
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function secretSync(config: UniDeskConfig, options: SecretSyncOptions): Promise<Record<string, unknown>> {
|
||||
const { configPath, spec } = resolveAgentRunLaneTarget(options);
|
||||
const sources = collectLaneSecretSources(spec);
|
||||
@@ -3864,6 +3915,52 @@ function secretSyncScript(_spec: AgentRunLaneSpec, values: Array<{ targetRef: {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function restartYamlLaneScript(spec: AgentRunLaneSpec): string {
|
||||
return [
|
||||
"set +e",
|
||||
`namespace=${shQuote(spec.runtime.namespace)}`,
|
||||
`deployment=${shQuote(spec.runtime.managerDeployment)}`,
|
||||
"ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"kubectl -n \"$namespace\" patch deployment \"$deployment\" --type='merge' -p \"{\\\"spec\\\":{\\\"template\\\":{\\\"metadata\\\":{\\\"annotations\\\":{\\\"kubectl.kubernetes.io/restartedAt\\\":\\\"$ts\\\"}}}}}\" >/tmp/agentrun-restart-patch.out 2>/tmp/agentrun-restart-patch.err",
|
||||
"patch_rc=$?",
|
||||
"if [ \"$patch_rc\" -eq 0 ]; then",
|
||||
" kubectl -n \"$namespace\" rollout status deployment/\"$deployment\" --timeout=180s >/tmp/agentrun-rollout-status.out 2>/tmp/agentrun-rollout-status.err",
|
||||
" rollout_rc=$?",
|
||||
"else",
|
||||
" rollout_rc=$patch_rc",
|
||||
"fi",
|
||||
"pods_json=$(kubectl -n \"$namespace\" get pod -l app.kubernetes.io/name=agentrun-mgr -o json 2>/dev/null || printf '{}')",
|
||||
"deployment_json=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o json 2>/dev/null || printf '{}')",
|
||||
"patch_err=$(cat /tmp/agentrun-restart-patch.err 2>/dev/null || true)",
|
||||
"rollout_err=$(cat /tmp/agentrun-rollout-status.err 2>/dev/null || true)",
|
||||
"PODS_JSON=\"$pods_json\" DEPLOYMENT_JSON=\"$deployment_json\" PATCH_RC=\"$patch_rc\" ROLLOUT_RC=\"$rollout_rc\" TS=\"$ts\" PATCH_ERR=\"$patch_err\" ROLLOUT_ERR=\"$rollout_err\" node <<'NODE'",
|
||||
"const pods = JSON.parse(process.env.PODS_JSON || '{}');",
|
||||
"const deployment = JSON.parse(process.env.DEPLOYMENT_JSON || '{}');",
|
||||
"const conditions = Array.isArray(deployment.status?.conditions) ? deployment.status.conditions : [];",
|
||||
"const items = Array.isArray(pods.items) ? pods.items : [];",
|
||||
"const compactError = (value) => String(value || '').replace(/\\s+/g, ' ').trim().slice(0, 500) || null;",
|
||||
"console.log(JSON.stringify({",
|
||||
" ok: process.env.PATCH_RC === '0' && process.env.ROLLOUT_RC === '0',",
|
||||
" restartedAt: process.env.TS,",
|
||||
" patch: { exitCode: Number(process.env.PATCH_RC || '1'), error: compactError(process.env.PATCH_ERR) },",
|
||||
" rollout: { exitCode: Number(process.env.ROLLOUT_RC || '1'), error: compactError(process.env.ROLLOUT_ERR) },",
|
||||
" readyReplicas: deployment.status?.readyReplicas ?? null,",
|
||||
" replicas: deployment.status?.replicas ?? null,",
|
||||
" conditions: conditions.map((item) => ({ type: item.type, status: item.status, reason: item.reason ?? null })),",
|
||||
" pods: items.map((pod) => ({",
|
||||
" name: pod.metadata?.name ?? null,",
|
||||
" phase: pod.status?.phase ?? null,",
|
||||
" ready: (pod.status?.containerStatuses ?? []).map((item) => item.ready),",
|
||||
" restarts: (pod.status?.containerStatuses ?? []).map((item) => item.restartCount),",
|
||||
" waiting: (pod.status?.containerStatuses ?? []).map((item) => item.state?.waiting?.reason ?? null),",
|
||||
" })),",
|
||||
" valuesPrinted: false",
|
||||
"}));",
|
||||
"NODE",
|
||||
"exit \"$rollout_rc\"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function applyYamlScript(yaml: string, fieldManager: string, dryRun: boolean): string {
|
||||
const encoded = Buffer.from(yaml, "utf8").toString("base64");
|
||||
return [
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ export function rootHelp(): unknown {
|
||||
{ command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send|control-plane|git-mirror", description: "Use AgentRun v0.1 resource primitives with low-noise human output by default; session follow-up uses send only and the server decides internal steer vs turn." },
|
||||
{ command: "platform-infra sub2api|langbot|n8n|wechat-archive ...", description: "Deploy platform-infra services such as Sub2API, LangBot and n8n, manage YAML-controlled public FRP/Caddy exposure and WeChat archive workflows, and inspect status/logs without printing secrets." },
|
||||
{ command: "secrets plan|sync|status", description: "Plan, push and inspect YAML-declared local secret source keys to Kubernetes Secrets without printing or reverse-engineering values." },
|
||||
{ command: "platform-db postgres plan|status|apply", description: "Manage YAML-declared host-native PostgreSQL 16 on PK01 for Sub2API/platform state, with PostgreSQL native TLS and redacted secret exports." },
|
||||
{ command: "platform-db postgres plan|status|export-secrets|apply", description: "Manage YAML-declared host-native PostgreSQL 16 on PK01 for Sub2API/platform state, with PostgreSQL native TLS and redacted secret exports." },
|
||||
{ command: "hwlab cd audit --env dev | hwlab cd status --env dev | hwlab cd apply --env dev --dry-run", description: "Legacy D601 HWLAB DEV CD wrapper kept for explicit old-path diagnostics; current HWLAB rollout uses G14 GitOps." },
|
||||
{ command: "code-agent-sandbox", description: "Independent Code Agent Sandbox service skeleton for adapter, mode, and credential-boundary diagnostics." },
|
||||
{ command: "schedule list|get|runs|run|retry-run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run <id> supports --wait-ms N and retry-run reuses the failed run's schedule." },
|
||||
|
||||
@@ -289,11 +289,13 @@ interface RemoteJobStart {
|
||||
|
||||
export function platformDbHelp(): unknown {
|
||||
return {
|
||||
command: "platform-db postgres plan|status|apply",
|
||||
command: "platform-db postgres plan|status|apply|export-secrets",
|
||||
output: "json",
|
||||
usage: [
|
||||
`bun scripts/cli.ts platform-db postgres plan --config ${defaultConfigPath}`,
|
||||
`bun scripts/cli.ts platform-db postgres status --config ${defaultConfigPath} [--full|--raw]`,
|
||||
`bun scripts/cli.ts platform-db postgres export-secrets --config ${defaultConfigPath} --dry-run`,
|
||||
`bun scripts/cli.ts platform-db postgres export-secrets --config ${defaultConfigPath} --confirm`,
|
||||
`bun scripts/cli.ts platform-db postgres apply --config ${defaultConfigPath} --dry-run`,
|
||||
`bun scripts/cli.ts platform-db postgres apply --config ${defaultConfigPath} --confirm`,
|
||||
`bun scripts/cli.ts platform-db postgres apply --config ${defaultConfigPath} --confirm --wait`,
|
||||
@@ -302,6 +304,7 @@ export function platformDbHelp(): unknown {
|
||||
safety: {
|
||||
configTruth: defaultConfigPath,
|
||||
defaultMutation: false,
|
||||
exportSecrets: "export-secrets only materializes YAML-declared local Secret source/export files; it does not touch the remote PostgreSQL service.",
|
||||
apply: "apply --confirm returns an async UniDesk job; the job uses short trans calls and a remote PK01 job instead of keeping one long SSH session open.",
|
||||
redaction: "Passwords and full DATABASE_URL values are never printed; output is limited to key names, presence, fingerprints and state.",
|
||||
noK3s: "PK01 PostgreSQL is host-systemd, not Docker or k3s.",
|
||||
@@ -315,6 +318,7 @@ export async function runPlatformDbCommand(config: UniDeskConfig, args: string[]
|
||||
const options = parseOptions(args.slice(2));
|
||||
if (action === "plan") return await plan(config, options);
|
||||
if (action === "status") return await status(config, options);
|
||||
if (action === "export-secrets") return await exportSecrets(options);
|
||||
if (action === "apply") return await apply(config, options);
|
||||
return unsupported(args);
|
||||
}
|
||||
@@ -359,7 +363,7 @@ function parseOptions(args: string[]): PlatformDbOptions {
|
||||
throw new Error(`unsupported platform-db option: ${arg}`);
|
||||
}
|
||||
}
|
||||
if (options.confirm && options.dryRun) throw new Error("apply accepts only one of --confirm or --dry-run");
|
||||
if (options.confirm && options.dryRun) throw new Error("platform-db postgres accepts only one of --confirm or --dry-run");
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -526,6 +530,40 @@ async function apply(config: UniDeskConfig, options: PlatformDbOptions): Promise
|
||||
};
|
||||
}
|
||||
|
||||
async function exportSecrets(options: PlatformDbOptions): Promise<Record<string, unknown>> {
|
||||
const pg = readPostgresHostConfig(options.configPath);
|
||||
if (!options.confirm || options.dryRun) {
|
||||
const localState = buildLocalSecretState(pg, false);
|
||||
return {
|
||||
ok: localState.inspection.ok,
|
||||
action: "platform-db-postgres-export-secrets",
|
||||
mode: "dry-run",
|
||||
mutation: false,
|
||||
config: configSummary(pg),
|
||||
localSecrets: localState.summary,
|
||||
next: {
|
||||
confirm: `bun scripts/cli.ts platform-db postgres export-secrets --config ${pg.configPath} --confirm`,
|
||||
syncConsumers: "run the consumer-specific secret sync command after confirmed export",
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
const localState = ensureLocalSecretState(pg);
|
||||
return {
|
||||
ok: true,
|
||||
action: "platform-db-postgres-export-secrets",
|
||||
mode: "confirmed",
|
||||
mutation: true,
|
||||
config: configSummary(pg),
|
||||
localSecrets: localState.summary,
|
||||
next: {
|
||||
status: `bun scripts/cli.ts platform-db postgres status --config ${pg.configPath}`,
|
||||
syncSecrets: "run the consumer-specific secret sync command after confirmed export",
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function readPostgresHostConfig(pathArg: string): PostgresHostConfig {
|
||||
const configPath = resolveConfigPath(pathArg);
|
||||
const parsed = asRecord(Bun.YAML.parse(readFileSync(configPath, "utf8")) as unknown, configPath);
|
||||
@@ -1005,10 +1043,14 @@ function inspectSecrets(pg: PostgresHostConfig, materialize: boolean): SecretIns
|
||||
}
|
||||
|
||||
function ensureLocalSecretState(pg: PostgresHostConfig): { inspection: SecretInspection; summary: Record<string, unknown>; exports: Array<Record<string, unknown>> } {
|
||||
const inspection = inspectSecrets(pg, true);
|
||||
return buildLocalSecretState(pg, true);
|
||||
}
|
||||
|
||||
function buildLocalSecretState(pg: PostgresHostConfig, materialize: boolean): { inspection: SecretInspection; summary: Record<string, unknown>; exports: Array<Record<string, unknown>> } {
|
||||
const inspection = inspectSecrets(pg, materialize);
|
||||
if (!inspection.ok) throw new Error("required secret source keys are missing and cannot be generated from YAML");
|
||||
validateObjectSecretConsistency(pg, inspection);
|
||||
const exports = pg.exports.connectionStrings.map((item) => writeConnectionStringExport(pg, inspection, item));
|
||||
const exports = pg.exports.connectionStrings.map((item) => connectionStringExportState(pg, inspection, item, materialize));
|
||||
return {
|
||||
inspection,
|
||||
exports,
|
||||
@@ -1035,7 +1077,7 @@ function validateObjectSecretConsistency(pg: PostgresHostConfig, inspection: Sec
|
||||
}
|
||||
}
|
||||
|
||||
function writeConnectionStringExport(pg: PostgresHostConfig, inspection: SecretInspection, item: ConnectionStringExportConfig): Record<string, unknown> {
|
||||
function connectionStringExportState(pg: PostgresHostConfig, inspection: SecretInspection, item: ConnectionStringExportConfig, materialize: boolean): Record<string, unknown> {
|
||||
const source = inspection.materials.get(item.sourceSecretRef);
|
||||
if (source === undefined) throw new Error(`export source secret not found: ${item.sourceSecretRef}`);
|
||||
const rendered = renderConnectionString(item, source.values);
|
||||
@@ -1044,7 +1086,7 @@ function writeConnectionStringExport(pg: PostgresHostConfig, inspection: SecretI
|
||||
const existing = existsSync(targetPath) ? parseEnvFile(readFileSync(targetPath, "utf8")) : {};
|
||||
const before = existing[item.writeToSecretSource.key];
|
||||
const next = { ...existing, [item.writeToSecretSource.key]: rendered };
|
||||
writeEnvFile(targetPath, next);
|
||||
if (materialize) writeEnvFile(targetPath, next);
|
||||
return {
|
||||
name: item.name,
|
||||
sourceRef: item.sourceSecretRef,
|
||||
|
||||
Reference in New Issue
Block a user