feat: add platform secret distribution cli

This commit is contained in:
Codex
2026-06-13 02:49:51 +00:00
parent 0c4a04a9dc
commit c08f307441
9 changed files with 970 additions and 52 deletions
+11 -24
View File
@@ -1,6 +1,6 @@
import { createHash, randomBytes } from "node:crypto";
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, isAbsolute, join } from "node:path";
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { isAbsolute, join } from "node:path";
import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { startJob } from "./jobs";
@@ -97,7 +97,7 @@ interface SecretMaterial {
dbSourcePath: string;
appSourceRef: string;
appSourcePath: string;
action: "create" | "update" | "none";
action: "read";
values: {
dbUser: string;
dbPassword: string;
@@ -231,6 +231,7 @@ function plan(options: CommonOptions): Record<string, unknown> {
},
next: {
postgres: "bun scripts/cli.ts platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm",
secrets: "bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope platform-infra --confirm",
dryRun: `bun scripts/cli.ts platform-infra n8n apply --target ${target.id} --dry-run`,
apply: `bun scripts/cli.ts platform-infra n8n apply --target ${target.id} --confirm`,
status: `bun scripts/cli.ts platform-infra n8n status --target ${target.id}`,
@@ -303,6 +304,7 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
remote: parsed ?? compactCapture(result, { full: true }),
pk01Caddy: caddy,
next: {
secrets: "bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope platform-infra --confirm",
status: `bun scripts/cli.ts platform-infra n8n status --target ${target.id}`,
validate: `bun scripts/cli.ts platform-infra n8n validate --target ${target.id}`,
},
@@ -824,20 +826,17 @@ function prepareSecretMaterial(n8n: N8nConfig): SecretMaterial {
if (dbUser !== n8n.runtime.database.user) throw new Error(`${n8n.runtime.database.sourceRef}.${n8n.runtime.database.sourceKeys.user} does not match n8n runtime.database.user`);
if (dbName !== n8n.runtime.database.dbName) throw new Error(`${n8n.runtime.database.sourceRef}.${n8n.runtime.database.sourceKeys.dbName} does not match n8n runtime.database.dbName`);
const appSourcePath = join(root, n8n.runtime.secrets.appSourceRef);
const existedBefore = existsSync(appSourcePath);
const existing = existedBefore ? parseEnvFile(readTextFile(appSourcePath)) : {};
const next = { ...existing };
if (!existsSync(appSourcePath)) throw new Error(`n8n app secret source ${redactRepoPath(appSourcePath)} is missing; run bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope platform-infra --confirm first`);
const appValues = parseEnvFile(readTextFile(appSourcePath));
const encryptionKeyName = n8n.runtime.secrets.encryptionKey;
if (next[encryptionKeyName] === undefined || next[encryptionKeyName].length === 0) next[encryptionKeyName] = randomBytes(32).toString("base64url");
const action = !existedBefore ? "create" : existing[encryptionKeyName] !== next[encryptionKeyName] ? "update" : "none";
if (action !== "none") writeEnvFile(appSourcePath, next);
const values = { dbUser, dbPassword, dbName, encryptionKey: next[encryptionKeyName] };
const encryptionKey = requiredEnvValue(appValues, encryptionKeyName, n8n.runtime.secrets.appSourceRef);
const values = { dbUser, dbPassword, dbName, encryptionKey };
return {
dbSourceRef: n8n.runtime.database.sourceRef,
dbSourcePath: redactRepoPath(dbSourcePath),
appSourceRef: n8n.runtime.secrets.appSourceRef,
appSourcePath: redactRepoPath(appSourcePath),
action,
action: "read",
values,
fingerprint: fingerprintValues({ dbPassword, encryptionKey: values.encryptionKey }, ["dbPassword", "encryptionKey"]),
valuesPrinted: false,
@@ -1063,18 +1062,6 @@ function unquoteEnvValue(value: string): string {
return value;
}
function writeEnvFile(path: string, values: Record<string, string>): void {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
const lines = Object.keys(values).sort().map((key) => `${key}=${quoteEnv(values[key])}`);
writeFileSync(path, `${lines.join("\n")}\n`, { encoding: "utf8", mode: 0o600 });
chmodSync(path, 0o600);
}
function quoteEnv(value: string): string {
if (/^[A-Za-z0-9_./:@%?&=+-]+$/u.test(value)) return value;
return `'${value.replaceAll("'", "'\"'\"'")}'`;
}
function requiredEnvValue(values: Record<string, string>, key: string, sourceRef: string): string {
const value = values[key];
if (value === undefined || value.length === 0) throw new Error(`${sourceRef} is missing required key ${key}`);