fix: reload AgentRun DB secrets from YAML

This commit is contained in:
Codex
2026-06-13 07:10:20 +00:00
parent e78cc9a16d
commit 7ba28c8f40
9 changed files with 158 additions and 11 deletions
+48 -6
View File
@@ -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,