chore: make sub2api d601 primary

This commit is contained in:
Codex
2026-06-13 04:01:31 +00:00
parent c29ca99a50
commit 5a7afb5236
6 changed files with 331 additions and 71 deletions
+44 -22
View File
@@ -17,7 +17,8 @@ import {
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
const g14K3sRoute = "G14:k3s";
const defaultTargetId = "G14";
const defaultTargetId = "D601";
const defaultTargetRoute = "D601:k3s";
const namespace = "platform-infra";
const serviceName = "sub2api";
const serviceDns = `${serviceName}.${namespace}.svc.cluster.local:8080`;
@@ -79,6 +80,7 @@ interface CodexPoolRuntimeTarget {
namespace: string;
serviceName: string;
serviceDns: string;
publicBaseUrl: string | null;
appSecretName: string;
egressProxy: {
enabled: boolean;
@@ -549,7 +551,7 @@ function codexPoolRuntimeTarget(targetId: string = defaultTargetId): CodexPoolRu
const raw = parsed.targets.find((item) => isRecord(item) && String(item.id ?? "").toLowerCase() === targetId.toLowerCase());
if (!isRecord(raw)) throw new Error(`${sub2apiConfigPath}.targets does not contain target ${targetId}`);
const id = stringValue(raw.id) ?? targetId;
const route = stringValue(raw.route) ?? (id === defaultTargetId ? g14K3sRoute : "");
const route = stringValue(raw.route) ?? (id === defaultTargetId ? defaultTargetRoute : "");
const targetNamespace = stringValue(raw.namespace) ?? namespace;
if (route.length === 0) throw new Error(`${sub2apiConfigPath}.targets[${id}].route is required`);
validateKubernetesName(targetNamespace, `${sub2apiConfigPath}.targets[${id}].namespace`, true);
@@ -571,12 +573,19 @@ function codexPoolRuntimeTarget(targetId: string = defaultTargetId): CodexPoolRu
};
}
let publicBaseUrl: string | null = null;
if (isRecord(raw.publicExposure) && raw.publicExposure.enabled === true) {
publicBaseUrl = normalizeBaseUrl(stringValue(raw.publicExposure.publicBaseUrl));
if (publicBaseUrl === null) throw new Error(`${sub2apiConfigPath}.targets[${id}].publicExposure.publicBaseUrl must be a valid http(s) URL when target public exposure is enabled`);
}
return {
id,
route,
namespace: targetNamespace,
serviceName,
serviceDns: `${serviceName}.${targetNamespace}.svc.cluster.local:8080`,
publicBaseUrl,
appSecretName,
egressProxy,
};
@@ -591,6 +600,7 @@ function codexPoolPlan(options: DisclosureOptions = { full: false, raw: false, t
const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
const profiles = collectCodexProfiles();
const ok = profiles.length > 0 && profiles.every((profile) => profile.ok);
const consumerBaseUrl = codexConsumerBaseUrl(pool, runtimeTarget);
return {
ok,
action: "platform-infra-sub2api-codex-pool-plan",
@@ -614,8 +624,10 @@ function codexPoolPlan(options: DisclosureOptions = { full: false, raw: false, t
? `Account sentinel is enabled as k8s CronJob ${runtimeTarget.namespace}/${pool.sentinel.cronJobName}; actions.enabled=${pool.sentinel.actions.enabled}.`
: "Account sentinel monitoring is disabled by YAML.",
publicExposure: pool.publicExposure.enabled
? `Default Codex consumers use ${codexConsumerBaseUrl(pool)}; bounded master-local probes may use ${pool.publicExposure.masterBaseUrl}. FRP proxy ${pool.publicExposure.proxyName} maps public ${pool.publicExposure.publicBaseUrl} to ${pool.publicExposure.localIP}:${pool.publicExposure.localPort}.`
: "Public FRP exposure is disabled by YAML.",
? `Default Codex consumers use ${consumerBaseUrl}; bounded master-local probes may use ${pool.publicExposure.masterBaseUrl}. Legacy Codex-pool FRP proxy ${pool.publicExposure.proxyName} maps public ${pool.publicExposure.publicBaseUrl} to ${pool.publicExposure.localIP}:${pool.publicExposure.localPort}.`
: runtimeTarget.publicBaseUrl === null
? "Public FRP exposure is disabled by YAML."
: `Legacy Codex-pool FRP exposure is disabled by YAML; Codex consumers for target ${runtimeTarget.id} use target-level public exposure ${consumerBaseUrl}.`,
idempotency: "sync reuses the group, account names, and k3s Secret when they already exist; credentials are updated from the current local Codex files; managed accounts missing from YAML are preserved unless --prune-removed is explicitly provided.",
configPolicy: "UniDesk-owned durable configuration remains YAML-first; local ~/.codex files and runtime Secrets are not committed.",
},
@@ -994,6 +1006,8 @@ async function codexPoolExpose(config: UniDeskConfig, options: ConfirmOptions):
async function codexPoolConfigureLocal(config: UniDeskConfig, options: ConfirmOptions): Promise<Record<string, unknown>> {
const pool = readCodexPoolConfig();
const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
const consumerExposureAvailable = pool.publicExposure.enabled || runtimeTarget.publicBaseUrl !== null;
const codexDir = join(homedir(), ".codex");
const configPath = join(codexDir, "config.toml");
const authPath = join(codexDir, "auth.json");
@@ -1010,7 +1024,7 @@ async function codexPoolConfigureLocal(config: UniDeskConfig, options: ConfirmOp
authPath,
backupConfigPath,
backupAuthPath,
baseUrl: codexConsumerBaseUrl(pool),
baseUrl: consumerExposureAvailable ? codexConsumerBaseUrl(pool, runtimeTarget) : null,
providerName: pool.localCodex.providerName,
wireApi: pool.localCodex.wireApi,
modelContextWindow: pool.localCodex.modelContextWindow,
@@ -1021,12 +1035,12 @@ async function codexPoolConfigureLocal(config: UniDeskConfig, options: ConfirmOp
valuesPrinted: false,
},
next: {
confirm: "bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm",
confirm: `bun scripts/cli.ts platform-infra sub2api codex-pool configure-local${targetFlag(runtimeTarget)} --confirm`,
},
};
}
if (!pool.publicExposure.enabled) throw new Error(`${codexPoolConfigPath}.publicExposure.enabled must be true before configure-local`);
const keyResult = await fetchPoolApiKey(config, pool);
if (!consumerExposureAvailable) throw new Error(`${codexPoolConfigPath}.publicExposure.enabled is false and ${sub2apiConfigPath}.targets[${runtimeTarget.id}].publicExposure.enabled is not true; configure-local needs one consumer URL`);
const keyResult = await fetchPoolApiKey(config, pool, runtimeTarget);
if (keyResult.apiKey === null) {
return {
ok: false,
@@ -1035,8 +1049,8 @@ async function codexPoolConfigureLocal(config: UniDeskConfig, options: ConfirmOp
valuesPrinted: false,
};
}
const writeResult = writeLocalCodexConfig(pool, keyResult.apiKey);
const validateResult = await validatePublicGatewayWithKey(pool, keyResult.apiKey);
const writeResult = writeLocalCodexConfig(pool, keyResult.apiKey, runtimeTarget);
const validateResult = await validatePublicGatewayWithKey(pool, keyResult.apiKey, runtimeTarget);
return {
ok: writeResult.ok && validateResult.ok,
action: "platform-infra-sub2api-codex-pool-configure-local",
@@ -1044,7 +1058,7 @@ async function codexPoolConfigureLocal(config: UniDeskConfig, options: ConfirmOp
local: writeResult,
validation: validateResult,
apiKey: {
secret: `${namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}`,
secret: `${runtimeTarget.namespace}/${pool.apiKeySecretName}.${pool.apiKeySecretKey}`,
keyPreview: apiKeyPreview(keyResult.apiKey),
apiKeyFingerprint: fingerprint(keyResult.apiKey),
valuesPrinted: false,
@@ -2633,6 +2647,8 @@ function poolTarget(pool = readCodexPoolConfig(), target = codexPoolRuntimeTarge
namespace: target.namespace,
service: serviceName,
serviceDns: target.serviceDns,
publicBaseUrl: target.publicBaseUrl,
consumerBaseUrl: pool.publicExposure.enabled || target.publicBaseUrl !== null ? codexConsumerBaseUrl(pool, target) : null,
configPath: codexPoolConfigPath,
groupName: pool.groupName,
apiKeyName: pool.apiKeyName,
@@ -3070,7 +3086,7 @@ kubectl -n ${target.namespace} get secret ${pool.apiKeySecretName} -o json
}
}
function writeLocalCodexConfig(pool: CodexPoolConfig, apiKey: string): Record<string, unknown> {
function writeLocalCodexConfig(pool: CodexPoolConfig, apiKey: string, target = codexPoolRuntimeTarget(defaultTargetId)): Record<string, unknown> {
const codexDir = join(homedir(), ".codex");
mkdirSync(codexDir, { recursive: true, mode: 0o700 });
const configPath = join(codexDir, "config.toml");
@@ -3082,7 +3098,7 @@ function writeLocalCodexConfig(pool: CodexPoolConfig, apiKey: string): Record<st
const configBackupAction = copyIfMissing(configPath, backupConfigPath);
const authBackupAction = copyIfMissing(authPath, backupAuthPath);
const currentToml = readFileSync(configPath, "utf8");
const nextToml = updateCodexConfigToml(currentToml, pool);
const nextToml = updateCodexConfigToml(currentToml, pool, target);
writeFileSync(configPath, nextToml, { encoding: "utf8", mode: 0o600 });
chmodSync(configPath, 0o600);
writeFileSync(authPath, `${JSON.stringify({ OPENAI_API_KEY: apiKey }, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
@@ -3110,7 +3126,7 @@ function writeLocalCodexConfig(pool: CodexPoolConfig, apiKey: string): Record<st
},
provider: {
name: pool.localCodex.providerName,
baseUrl: codexConsumerBaseUrl(pool),
baseUrl: codexConsumerBaseUrl(pool, target),
wireApi: pool.localCodex.wireApi,
modelContextWindow: pool.localCodex.modelContextWindow,
modelAutoCompactTokenLimit: pool.localCodex.modelAutoCompactTokenLimit,
@@ -3128,10 +3144,10 @@ function copyIfMissing(source: string, target: string): "created" | "kept-existi
return "created";
}
function updateCodexConfigToml(current: string, pool: CodexPoolConfig): string {
function updateCodexConfigToml(current: string, pool: CodexPoolConfig, target = codexPoolRuntimeTarget(defaultTargetId)): string {
return renderCodexLocalConsumerToml(current, {
providerName: pool.localCodex.providerName,
baseUrl: codexConsumerBaseUrl(pool),
baseUrl: codexConsumerBaseUrl(pool, target),
wireApi: pool.localCodex.wireApi,
modelContextWindow: pool.localCodex.modelContextWindow,
modelAutoCompactTokenLimit: pool.localCodex.modelAutoCompactTokenLimit,
@@ -3140,8 +3156,9 @@ function updateCodexConfigToml(current: string, pool: CodexPoolConfig): string {
});
}
function codexConsumerBaseUrl(pool: CodexPoolConfig): string {
return `${pool.publicExposure.publicBaseUrl.replace(/\/+$/u, "")}/`;
function codexConsumerBaseUrl(pool: CodexPoolConfig, target = codexPoolRuntimeTarget(defaultTargetId)): string {
const baseUrl = target.publicBaseUrl ?? pool.publicExposure.publicBaseUrl;
return `${baseUrl.replace(/\/+$/u, "")}/`;
}
export function renderCodexLocalConsumerToml(current: string, options: CodexLocalConsumerTomlOptions): string {
@@ -3237,8 +3254,9 @@ function upsertTomlSectionBoolean(text: string, sectionName: string, key: string
return [...lines.slice(0, start + 1), assignment, ...lines.slice(start + 1)].join("\n");
}
async function validatePublicGatewayWithKey(pool: CodexPoolConfig, apiKey: string): Promise<Record<string, unknown>> {
const probe = await probePublicModels(pool, "with-api-key", apiKey, "public");
async function validatePublicGatewayWithKey(pool: CodexPoolConfig, apiKey: string, target = codexPoolRuntimeTarget(defaultTargetId)): Promise<Record<string, unknown>> {
const probeBase = target.publicBaseUrl === null ? "public" : "target-public";
const probe = await probePublicModels(pool, "with-api-key", apiKey, probeBase, target);
return {
...probe,
ok: probe.ok === true,
@@ -3248,8 +3266,12 @@ async function validatePublicGatewayWithKey(pool: CodexPoolConfig, apiKey: strin
};
}
async function probePublicModels(pool: CodexPoolConfig, mode: "with-api-key" | "without-api-key", apiKey?: string, base: "master" | "public" = "master"): Promise<Record<string, unknown>> {
const baseUrl = base === "public" ? pool.publicExposure.publicBaseUrl : pool.publicExposure.masterBaseUrl;
async function probePublicModels(pool: CodexPoolConfig, mode: "with-api-key" | "without-api-key", apiKey?: string, base: "master" | "public" | "target-public" = "master", target = codexPoolRuntimeTarget(defaultTargetId)): Promise<Record<string, unknown>> {
const baseUrl = base === "target-public"
? (target.publicBaseUrl ?? pool.publicExposure.publicBaseUrl)
: base === "public"
? pool.publicExposure.publicBaseUrl
: pool.publicExposure.masterBaseUrl;
const url = `${baseUrl.replace(/\/+$/u, "")}/v1/models`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);