diff --git a/config/platform-infra/sub2api-codex-pool.yaml b/config/platform-infra/sub2api-codex-pool.yaml index 1ae54eb4..df83ba05 100644 --- a/config/platform-infra/sub2api-codex-pool.yaml +++ b/config/platform-infra/sub2api-codex-pool.yaml @@ -212,7 +212,7 @@ sentinel: actions: enabled: true schedule: "*/1 * * * *" - image: python:3.12-alpine + image: docker.m.daocloud.io/library/python:3.12-alpine sdk: openaiPythonVersion: "2.41.1" serviceAccountName: sub2api-account-sentinel diff --git a/config/platform-infra/sub2api.yaml b/config/platform-infra/sub2api.yaml index 0d4b5577..9297cb49 100644 --- a/config/platform-infra/sub2api.yaml +++ b/config/platform-infra/sub2api.yaml @@ -359,7 +359,8 @@ runtime: mode: empty-dir sentinel: mode: singleton - enabledOnTargets: [] + enabledOnTargets: + - JD01 security: urlAllowlist: enabled: false diff --git a/scripts/src/platform-infra-sub2api-codex-sentinel.ts b/scripts/src/platform-infra-sub2api-codex-sentinel.ts index 299ac76c..6a2b7243 100644 --- a/scripts/src/platform-infra-sub2api-codex-sentinel.ts +++ b/scripts/src/platform-infra-sub2api-codex-sentinel.ts @@ -100,16 +100,11 @@ export interface CodexPoolSentinelManifestOptions { } export function codexPoolSentinelRuntimeImage(config: CodexPoolSentinelConfig): CodexPoolSentinelImageTarget { - const baseTag = config.image - .replace(/[^A-Za-z0-9_.-]+/gu, "-") - .replace(/^-+|-+$/gu, "") - .slice(0, 80) || "python"; - const tag = `${baseTag}-openai-${config.sdk.openaiPythonVersion}`; - const repository = "127.0.0.1:5000/platform-infra/sub2api-account-sentinel"; + const [repository, tag = "latest"] = config.image.split(":"); return { baseImage: config.image, - runtimeImage: `${repository}:${tag}`, - repository, + runtimeImage: config.image, + repository: repository ?? config.image, tag, }; } @@ -446,14 +441,27 @@ ${proxyEnv} export function sentinelContainerShellCommand(config: CodexPoolSentinelConfig): string { return [ "set -eu", - "python3 - <<'PY'", + `OPENAI_PYTHON_VERSION=${JSON.stringify(config.sdk.openaiPythonVersion)}`, + "if ! python3 - <<'PY'", "import importlib.metadata", - `expected = ${JSON.stringify(config.sdk.openaiPythonVersion)}`, + "import os", + "expected = os.environ['OPENAI_PYTHON_VERSION']", "try:", " current = importlib.metadata.version('openai')", "except importlib.metadata.PackageNotFoundError:", " current = None", "if current != expected:", + " raise SystemExit(1)", + "PY", + "then", + " python3 -m pip install --no-cache-dir \"openai==$OPENAI_PYTHON_VERSION\"", + "fi", + "python3 - <<'PY'", + "import importlib.metadata", + "import os", + "expected = os.environ['OPENAI_PYTHON_VERSION']", + "current = importlib.metadata.version('openai')", + "if current != expected:", " raise SystemExit(f'openai-python-version-mismatch expected={expected} current={current}')", "PY", "exec python3 /opt/sentinel/sentinel.py", diff --git a/scripts/src/platform-infra-sub2api-codex/actions.ts b/scripts/src/platform-infra-sub2api-codex/actions.ts index dbbb2527..fa35016c 100644 --- a/scripts/src/platform-infra-sub2api-codex/actions.ts +++ b/scripts/src/platform-infra-sub2api-codex/actions.ts @@ -21,7 +21,6 @@ import { import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets"; import { runSshCommandCapture, type SshCaptureResult } from "../ssh"; -import type { RemoteCodexPoolMode } from "./remote"; import type { CodexPoolConfig, ConfirmOptions, DisclosureOptions, SentinelImageOptions, SentinelProbeOptions, SentinelReportOptions, SyncOptions, TraceOptions } from "./types"; import { desiredAccountNames } from "./accounts"; import { collectCodexProfiles, readCodexPoolConfig } from "./config"; @@ -30,10 +29,10 @@ import { apiKeyPreview, codexConsumerBaseUrl, fetchPoolApiKey, probePublicModels import { manualBindingSourcePlan, poolTarget, prepareTargetPublicExposureSecret, resolvedManualAccountProtections, secretMaterialSummary, sentinelProfileSecrets, targetFrpPublicExposure, targetPublicExposureApplyScript, targetPublicExposureSummary } from "./public-exposure"; import { codexPoolConfigSummary, compactProfile, compactSentinelProbeResult, redactProfile, renderSub2ApiTempUnschedulableCredentials } from "./redaction"; import { boolField, capture, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote"; -import { cleanupProbesScript, sentinelImageBuildScript, sentinelImageStatusScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts"; +import { cleanupProbesScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts"; import { codexPoolSyncSummary, codexPoolValidationSummary, renderSentinelReport, renderTraceReport, renderedCliResult } from "./render"; import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId, targetFlag } from "./runtime-target"; -import { codexPoolConfigPath, sentinelImageDockerfilePath, serviceName, sub2apiConfigPath } from "./types"; +import { codexPoolConfigPath, serviceName, sub2apiConfigPath } from "./types"; export function codexPoolPlan(options?: DisclosureOptions): Record { const resolvedOptions = options ?? { full: false, raw: false, targetId: defaultCodexPoolRuntimeTargetId() }; @@ -242,6 +241,7 @@ export async function codexPoolSentinelImage(config: UniDeskConfig, options: Sen export async function runCodexPoolSentinelImage(config: UniDeskConfig, pool: CodexPoolConfig, options: SentinelImageOptions): Promise> { const runtimeTarget = codexPoolRuntimeTarget(options.targetId); const target = codexPoolSentinelRuntimeImage(pool.sentinel); + void config; if (!runtimeTarget.sentinelEnabled) { return { ok: true, @@ -256,40 +256,37 @@ export async function runCodexPoolSentinelImage(config: UniDeskConfig, pool: Cod valuesPrinted: false, }; } - if (options.action === "build" && options.dryRun) { + const summary = { + ok: true, + mode: options.action === "status" ? "base-image-runtime" : options.dryRun ? "dry-run-base-image-runtime" : "skipped-build-base-image-runtime", + target: { + id: runtimeTarget.id, + namespace: runtimeTarget.namespace, + }, + image: target.runtimeImage, + baseImage: target.baseImage, + openaiPythonVersion: pool.sentinel.sdk.openaiPythonVersion, + sdkInstall: "container-startup-pinned", + dockerRequired: false, + registryPushRequired: false, + mutation: false, + valuesPrinted: false, + }; + if (options.raw) { return { ok: true, action: "platform-infra-sub2api-codex-pool-sentinel-image", - mode: "dry-run", - image: target, - dockerfile: sentinelImageDockerfilePath, - mutation: false, - next: { - confirm: `bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build${targetFlag(runtimeTarget)} --confirm`, - }, - }; - } - const mode: RemoteCodexPoolMode = options.action === "status" ? "sentinel-image-status" : "sentinel-image-build"; - const script = options.action === "status" ? sentinelImageStatusScript(pool, runtimeTarget) : sentinelImageBuildScript(pool, runtimeTarget); - const result = await runRemoteCodexPoolScript(config, mode, script, runtimeTarget); - const parsed = parseJsonOutput(result.stdout); - if (options.raw) { - return { - ok: result.exitCode === 0 && boolField(parsed, "ok", false), - action: "platform-infra-sub2api-codex-pool-sentinel-image", mode: options.action, image: target, - remote: compactCapture(result, { full: true }), - parsed, + parsed: summary, }; } return { - ok: result.exitCode === 0 && boolField(parsed, "ok", false), + ok: true, action: "platform-infra-sub2api-codex-pool-sentinel-image", mode: options.action, image: target, - summary: parsed, - remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }), + summary, }; }