feat: add platform infra codex pool config

This commit is contained in:
Codex
2026-06-09 03:04:44 +00:00
parent 97f19d462f
commit c586918dba
4 changed files with 693 additions and 31 deletions
+138 -28
View File
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
const g14K3sRoute = "G14:k3s";
@@ -11,10 +12,12 @@ const serviceName = "sub2api";
const serviceDns = `${serviceName}.${namespace}.svc.cluster.local:8080`;
const fieldManager = "unidesk-platform-infra";
const appSecretName = "sub2api-secrets";
const poolGroupName = "unidesk-codex-pool";
const poolApiKeyName = "unidesk-codex-pool-api-key";
const poolApiKeySecretName = "sub2api-codex-pool-api-key";
const poolApiKeySecretKey = "API_KEY";
const codexPoolConfigPath = rootPath("config", "platform-infra", "sub2api-codex-pool.yaml");
const defaultPoolGroupName = "unidesk-codex-pool";
const defaultPoolApiKeyName = "unidesk-codex-pool-api-key";
const defaultPoolApiKeySecretName = "sub2api-codex-pool-api-key";
const defaultPoolApiKeySecretKey = "API_KEY";
const defaultMinOwnerBalanceUsd = 1000;
interface DisclosureOptions {
full: boolean;
@@ -42,7 +45,16 @@ interface CodexProfile {
error: string | null;
}
interface CodexPoolConfig {
groupName: string;
apiKeyName: string;
apiKeySecretName: string;
apiKeySecretKey: string;
minOwnerBalanceUsd: number;
}
export function codexPoolHelp(): unknown {
const pool = readCodexPoolConfig();
return {
command: "platform-infra sub2api codex-pool plan|sync|validate",
output: "json",
@@ -56,9 +68,10 @@ export function codexPoolHelp(): unknown {
route: g14K3sRoute,
namespace,
serviceDns,
poolGroupName,
poolApiKeySecretName,
poolApiKeySecretKey,
configPath: codexPoolConfigPath,
poolGroupName: pool.groupName,
poolApiKeySecretName: pool.apiKeySecretName,
poolApiKeySecretKey: pool.apiKeySecretKey,
secretValuesPrinted: false,
},
};
@@ -97,6 +110,7 @@ function validateOptions(args: string[], booleanOptions: Set<string>): void {
}
function codexPoolPlan(): Record<string, unknown> {
const pool = readCodexPoolConfig();
const profiles = collectCodexProfiles();
const ok = profiles.length > 0 && profiles.every((profile) => profile.ok);
return {
@@ -109,11 +123,15 @@ function codexPoolPlan(): Record<string, unknown> {
valuesPrinted: false,
},
target: poolTarget(),
config: {
path: codexPoolConfigPath,
pool,
},
profiles: profiles.map(redactProfile),
decision: {
accountType: "openai/apikey",
grouping: `All discovered Codex profiles are bound to one Sub2API group named ${poolGroupName}.`,
unifiedApiKey: `The client-facing API_KEY is controlled by k3s Secret ${namespace}/${poolApiKeySecretName}.${poolApiKeySecretKey}.`,
grouping: `All discovered Codex profiles are bound to one Sub2API group named ${pool.groupName}.`,
unifiedApiKey: `The client-facing API_KEY is controlled by k3s Secret ${namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}.`,
idempotency: "sync reuses the group, account names, and k3s Secret when they already exist; credentials are updated from the current local Codex files.",
configPolicy: "UniDesk-owned durable configuration remains YAML-first; local ~/.codex files and runtime Secrets are not committed.",
},
@@ -124,6 +142,7 @@ function codexPoolPlan(): Record<string, unknown> {
}
async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promise<Record<string, unknown>> {
const pool = readCodexPoolConfig();
const profiles = collectCodexProfiles();
const planOk = profiles.length > 0 && profiles.every((profile) => profile.ok);
if (!options.confirm || !planOk) {
@@ -139,10 +158,11 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
const payload = {
pool: {
groupName: poolGroupName,
apiKeyName: poolApiKeyName,
apiKeySecretName: poolApiKeySecretName,
apiKeySecretKey: poolApiKeySecretKey,
groupName: pool.groupName,
apiKeyName: pool.apiKeyName,
apiKeySecretName: pool.apiKeySecretName,
apiKeySecretKey: pool.apiKeySecretKey,
minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
},
profiles: profiles.map((profile) => ({
profile: profile.profile,
@@ -158,7 +178,7 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
apiKeyFingerprint: fingerprint(profile.apiKey ?? ""),
})),
};
const result = await capture(config, g14K3sRoute, ["script"], syncScript(payload));
const result = await capture(config, g14K3sRoute, ["script"], syncScript(payload, pool));
const parsed = parseJsonOutput(result.stdout);
if (options.raw) {
return {
@@ -183,7 +203,8 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
}
async function codexPoolValidate(config: UniDeskConfig, options: DisclosureOptions): Promise<Record<string, unknown>> {
const result = await capture(config, g14K3sRoute, ["script"], validateScript());
const pool = readCodexPoolConfig();
const result = await capture(config, g14K3sRoute, ["script"], validateScript(pool));
const parsed = parseJsonOutput(result.stdout);
if (options.raw) {
return {
@@ -274,6 +295,35 @@ function collectCodexProfiles(): CodexProfile[] {
});
}
function readCodexPoolConfig(): CodexPoolConfig {
const defaults: CodexPoolConfig = {
groupName: defaultPoolGroupName,
apiKeyName: defaultPoolApiKeyName,
apiKeySecretName: defaultPoolApiKeySecretName,
apiKeySecretKey: defaultPoolApiKeySecretKey,
minOwnerBalanceUsd: defaultMinOwnerBalanceUsd,
};
if (!existsSync(codexPoolConfigPath)) return defaults;
const parsed = Bun.YAML.parse(readFileSync(codexPoolConfigPath, "utf8")) as unknown;
if (!isRecord(parsed)) throw new Error(`${codexPoolConfigPath} must contain a YAML object`);
const pool = parsed.pool;
if (!isRecord(pool)) throw new Error(`${codexPoolConfigPath}.pool must be a YAML object`);
const config: CodexPoolConfig = {
groupName: stringValue(pool.groupName) ?? defaults.groupName,
apiKeyName: stringValue(pool.apiKeyName) ?? defaults.apiKeyName,
apiKeySecretName: stringValue(pool.apiKeySecretName) ?? defaults.apiKeySecretName,
apiKeySecretKey: stringValue(pool.apiKeySecretKey) ?? defaults.apiKeySecretKey,
minOwnerBalanceUsd: numberValue(pool.minOwnerBalanceUsd) ?? defaults.minOwnerBalanceUsd,
};
validateKubernetesName(config.groupName, "pool.groupName", false);
validateKubernetesName(config.apiKeySecretName, "pool.apiKeySecretName", true);
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(config.apiKeySecretKey)) {
throw new Error(`${codexPoolConfigPath}.pool.apiKeySecretKey must be a valid secret key name`);
}
if (config.minOwnerBalanceUsd <= 0) throw new Error(`${codexPoolConfigPath}.pool.minOwnerBalanceUsd must be > 0`);
return config;
}
function readAuthAPIKey(authPath: string): { apiKey: string | null; shape: string } {
if (!existsSync(authPath)) return { apiKey: null, shape: "missing" };
const parsed = JSON.parse(readFileSync(authPath, "utf8")) as unknown;
@@ -321,15 +371,16 @@ function redactProfile(profile: CodexProfile): Record<string, unknown> {
};
}
function poolTarget(): Record<string, unknown> {
function poolTarget(pool = readCodexPoolConfig()): Record<string, unknown> {
return {
route: g14K3sRoute,
namespace,
service: serviceName,
serviceDns,
groupName: poolGroupName,
apiKeyName: poolApiKeyName,
apiKeySecret: `${namespace}/${poolApiKeySecretName}.${poolApiKeySecretKey}`,
configPath: codexPoolConfigPath,
groupName: pool.groupName,
apiKeyName: pool.apiKeyName,
apiKeySecret: `${namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}`,
valuesPrinted: false,
};
}
@@ -351,6 +402,20 @@ function stringValue(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function numberValue(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim().length > 0) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function validateKubernetesName(value: string, key: string, strictDns: boolean): void {
const pattern = strictDns ? /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u : /^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$/u;
if (!pattern.test(value)) throw new Error(`${codexPoolConfigPath}.${key} has an unsupported format`);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -359,16 +424,16 @@ function fingerprint(value: string): string {
return createHash("sha256").update(value).digest("hex").slice(0, 12);
}
function syncScript(payload: unknown): string {
function syncScript(payload: unknown, pool: CodexPoolConfig): string {
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
return remotePythonScript("sync", encoded);
return remotePythonScript("sync", encoded, pool);
}
function validateScript(): string {
return remotePythonScript("validate", "");
function validateScript(pool: CodexPoolConfig): string {
return remotePythonScript("validate", "", pool);
}
function remotePythonScript(mode: "sync" | "validate", encodedPayload: string): string {
function remotePythonScript(mode: "sync" | "validate", encodedPayload: string, pool: CodexPoolConfig): string {
return `
set -u
python3 - <<'PY'
@@ -386,10 +451,11 @@ SERVICE_NAME = "${serviceName}"
SERVICE_DNS = "${serviceDns}"
FIELD_MANAGER = "${fieldManager}"
APP_SECRET_NAME = "${appSecretName}"
POOL_GROUP_NAME = "${poolGroupName}"
POOL_API_KEY_NAME = "${poolApiKeyName}"
POOL_API_KEY_SECRET_NAME = "${poolApiKeySecretName}"
POOL_API_KEY_SECRET_KEY = "${poolApiKeySecretKey}"
POOL_GROUP_NAME = "${pool.groupName}"
POOL_API_KEY_NAME = "${pool.apiKeyName}"
POOL_API_KEY_SECRET_NAME = "${pool.apiKeySecretName}"
POOL_API_KEY_SECRET_KEY = "${pool.apiKeySecretKey}"
MIN_OWNER_BALANCE_USD = ${JSON.stringify(pool.minOwnerBalanceUsd)}
MODE = "${mode}"
PAYLOAD_B64 = "${encodedPayload}"
@@ -706,6 +772,38 @@ def ensure_sub2api_api_key(token, api_key, group_id):
"id": existing.get("id") if isinstance(existing, dict) else None,
"name": existing.get("name") if isinstance(existing, dict) else POOL_API_KEY_NAME,
"groupId": existing.get("group_id") if isinstance(existing, dict) else group_id,
"userId": existing.get("user_id") if isinstance(existing, dict) else None,
}
def get_admin_user(token, user_id):
data = ensure_success(curl_api("GET", f"/api/v1/admin/users/{user_id}", bearer=token), "get API key owner")
if not isinstance(data, dict):
raise RuntimeError("API key owner response is not an object")
return data
def ensure_pool_owner_balance(token, user_id):
user = get_admin_user(token, user_id)
current = float(user.get("balance") or 0)
if current >= MIN_OWNER_BALANCE_USD:
return {
"action": "kept-existing",
"userId": user_id,
"balanceBefore": current,
"balanceAfter": current,
"minimumBalanceUsd": MIN_OWNER_BALANCE_USD,
}
updated = ensure_success(curl_api("POST", f"/api/v1/admin/users/{user_id}/balance", bearer=token, payload={
"balance": MIN_OWNER_BALANCE_USD,
"operation": "set",
"notes": "UniDesk Sub2API Codex pool internal API key bootstrap balance.",
}), "set API key owner balance")
after = float(updated.get("balance") or MIN_OWNER_BALANCE_USD) if isinstance(updated, dict) else MIN_OWNER_BALANCE_USD
return {
"action": "set",
"userId": user_id,
"balanceBefore": current,
"balanceAfter": after,
"minimumBalanceUsd": MIN_OWNER_BALANCE_USD,
}
def validate_gateway(api_key):
@@ -744,6 +842,7 @@ def run_sync():
account_results = ensure_accounts(token, profiles, group_id)
api_key, secret_action, secret_apply_stdout = ensure_api_key_secret(group_id)
api_key_result = ensure_sub2api_api_key(token, api_key, group_id)
owner_balance = ensure_pool_owner_balance(token, api_key_result["userId"])
gateway = validate_gateway(api_key)
return {
"ok": gateway["ok"] is True,
@@ -768,9 +867,11 @@ def run_sync():
"sub2apiAction": api_key_result["action"],
"sub2apiId": api_key_result["id"],
"groupId": api_key_result["groupId"],
"userId": api_key_result["userId"],
"keyPreview": api_key_preview(api_key),
"valuesPrinted": False,
},
"ownerBalance": owner_balance,
"validation": {"gatewayModels": gateway},
}
@@ -778,6 +879,11 @@ def run_validate():
api_key = decode_secret_value(POOL_API_KEY_SECRET_NAME, POOL_API_KEY_SECRET_KEY)
if not api_key:
raise RuntimeError(f"{POOL_API_KEY_SECRET_NAME}.{POOL_API_KEY_SECRET_KEY} missing")
admin_email, token = login()
key_item = next((item for item in list_user_keys(token) if item.get("key") == api_key), None)
owner_balance = None
if key_item is not None and key_item.get("user_id") is not None:
owner_balance = ensure_pool_owner_balance(token, key_item["user_id"])
gateway = validate_gateway(api_key)
return {
"ok": gateway["ok"] is True,
@@ -785,11 +891,15 @@ def run_validate():
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False},
"apiKey": {
"secret": f"{NAMESPACE}/{POOL_API_KEY_SECRET_NAME}.{POOL_API_KEY_SECRET_KEY}",
"sub2apiId": key_item.get("id") if isinstance(key_item, dict) else None,
"userId": key_item.get("user_id") if isinstance(key_item, dict) else None,
"keyPreview": api_key_preview(api_key),
"valuesPrinted": False,
},
"ownerBalance": owner_balance,
"validation": {"gatewayModels": gateway},
}