fix: reload AgentRun DB secrets from YAML
This commit is contained in:
@@ -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