feat: route code queue egress through provider gateway
This commit is contained in:
@@ -40,6 +40,17 @@ services:
|
||||
CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID: "${CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID:-D601}"
|
||||
CODE_QUEUE_DEV_CONTAINER_IMAGE: "${CODE_QUEUE_DEV_CONTAINER_IMAGE:-}"
|
||||
CODE_QUEUE_DEV_CONTAINER_WORKDIR: "${CODE_QUEUE_DEV_CONTAINER_WORKDIR:-/home/ubuntu}"
|
||||
CODE_QUEUE_EGRESS_PROXY_ENABLED: "${CODE_QUEUE_EGRESS_PROXY_ENABLED:-true}"
|
||||
CODE_QUEUE_EGRESS_PROXY_URL: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
CODE_QUEUE_EGRESS_PROXY_NO_PROXY: "${CODE_QUEUE_EGRESS_PROXY_NO_PROXY:-localhost,127.0.0.1,::1,host.docker.internal,unidesk-provider-gateway-D601,74.48.78.17,backend-core,oa-event-flow,database}"
|
||||
HTTP_PROXY: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
HTTPS_PROXY: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
ALL_PROXY: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
http_proxy: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
https_proxy: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
all_proxy: "${CODE_QUEUE_EGRESS_PROXY_URL:-http://unidesk-provider-gateway-D601:18789}"
|
||||
NO_PROXY: "${CODE_QUEUE_EGRESS_PROXY_NO_PROXY:-localhost,127.0.0.1,::1,host.docker.internal,unidesk-provider-gateway-D601,74.48.78.17,backend-core,oa-event-flow,database}"
|
||||
no_proxy: "${CODE_QUEUE_EGRESS_PROXY_NO_PROXY:-localhost,127.0.0.1,::1,host.docker.internal,unidesk-provider-gateway-D601,74.48.78.17,backend-core,oa-event-flow,database}"
|
||||
CODE_QUEUE_WINDOWS_NATIVE_CODEX_DEFAULT_WORKDIR: "${CODE_QUEUE_WINDOWS_NATIVE_CODEX_DEFAULT_WORKDIR:-/mnt/f/Work/ConStart}"
|
||||
CODE_QUEUE_WINDOWS_NATIVE_CODEX_BRIDGE_DIR: "${CODE_QUEUE_WINDOWS_NATIVE_CODEX_BRIDGE_DIR:-/home/ubuntu/.unidesk/code-queue/windows-native-codex}"
|
||||
CODE_QUEUE_WINDOWS_NATIVE_CODEX_COMMAND: "${CODE_QUEUE_WINDOWS_NATIVE_CODEX_COMMAND:-codex app-server --listen stdio://}"
|
||||
@@ -66,8 +77,16 @@ services:
|
||||
- ${CODE_QUEUE_STATE_DIR:-../../../../.state/code-queue}:/var/lib/unidesk/code-queue
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- default
|
||||
- provider-gateway
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS --max-time 2 http://127.0.0.1:4222/health >/dev/null"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
networks:
|
||||
provider-gateway:
|
||||
external: true
|
||||
name: ${CODE_QUEUE_PROVIDER_GATEWAY_NETWORK:-unidesk-provider-d601_default}
|
||||
|
||||
@@ -169,6 +169,18 @@ const config = readConfig();
|
||||
|
||||
const logger = createLogger("code-queue", config.logFile);
|
||||
configureOaEvents({ baseUrl: config.oaEventFlowBaseUrl, logger, nowIso });
|
||||
const providerGatewayEgressProxy = configureProviderGatewayEgressProxy();
|
||||
if (providerGatewayEgressProxy.enabled) {
|
||||
const proxyEnv = providerGatewayEgressProxy.proxyUrl;
|
||||
process.env.HTTP_PROXY = proxyEnv;
|
||||
process.env.HTTPS_PROXY = proxyEnv;
|
||||
process.env.http_proxy = proxyEnv;
|
||||
process.env.https_proxy = proxyEnv;
|
||||
process.env.ALL_PROXY = proxyEnv;
|
||||
process.env.all_proxy = proxyEnv;
|
||||
process.env.NO_PROXY = providerGatewayEgressProxy.noProxy;
|
||||
process.env.no_proxy = providerGatewayEgressProxy.noProxy;
|
||||
}
|
||||
const state = emptyState();
|
||||
let processing = false;
|
||||
const processingQueues = new Set<string>();
|
||||
@@ -283,6 +295,9 @@ function readConfig(): RuntimeConfig {
|
||||
const mainProviderId = envString("CODE_QUEUE_MAIN_PROVIDER_ID", "D601");
|
||||
const devContainerDefaultProviderId = envString("CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID", "D601");
|
||||
const remoteDefaultWorkdir = envString("CODE_QUEUE_REMOTE_WORKDIR", "/home/ubuntu");
|
||||
const defaultWorkdir = envString("CODE_QUEUE_WORKDIR", "/workspace");
|
||||
const devContainerMasterHost = envString("CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", "74.48.78.17");
|
||||
const defaultProviderGatewayProxyHost = `unidesk-provider-gateway-${mainProviderId}`;
|
||||
const executionProviderIds = Array.from(new Set([
|
||||
mainProviderId,
|
||||
...envList("CODE_QUEUE_EXECUTION_PROVIDER_IDS", [devContainerDefaultProviderId]),
|
||||
@@ -293,7 +308,7 @@ function readConfig(): RuntimeConfig {
|
||||
dataDir,
|
||||
outputArchiveDir: envString("CODE_QUEUE_OUTPUT_ARCHIVE_DIR", resolve(dataDir, "output-archive")),
|
||||
logFile: envString("LOG_FILE", "/var/log/unidesk/code-queue.jsonl"),
|
||||
defaultWorkdir: envString("CODE_QUEUE_WORKDIR", "/workspace"),
|
||||
defaultWorkdir,
|
||||
mainProviderId,
|
||||
remoteDefaultWorkdir,
|
||||
executionProviderIds,
|
||||
@@ -339,7 +354,21 @@ function readConfig(): RuntimeConfig {
|
||||
maxInMemoryOutputRecords: envNonNegativeNumber("CODE_QUEUE_IN_MEMORY_OUTPUT_RECORDS", 10),
|
||||
maxInMemoryEventRecords: envNonNegativeNumber("CODE_QUEUE_IN_MEMORY_EVENT_RECORDS", 10),
|
||||
maxActiveQueues: Math.max(0, Math.min(32, envNumber("CODE_QUEUE_MAX_ACTIVE_QUEUES", 0))),
|
||||
devContainerMasterHost: envString("CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", "74.48.78.17"),
|
||||
egressProxyEnabled: envBool("CODE_QUEUE_EGRESS_PROXY_ENABLED", true),
|
||||
egressProxyUrl: envString("CODE_QUEUE_EGRESS_PROXY_URL", `http://${defaultProviderGatewayProxyHost}:18789`).replace(/\/+$/u, ""),
|
||||
egressProxyNoProxy: envString("CODE_QUEUE_EGRESS_PROXY_NO_PROXY", [
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"::1",
|
||||
"host.docker.internal",
|
||||
defaultProviderGatewayProxyHost,
|
||||
devContainerMasterHost,
|
||||
"74.48.78.17",
|
||||
"backend-core",
|
||||
"oa-event-flow",
|
||||
"database",
|
||||
].join(",")),
|
||||
devContainerMasterHost,
|
||||
devContainerDefaultProviderId,
|
||||
devContainerImage: envString("CODE_QUEUE_DEV_CONTAINER_IMAGE", ""),
|
||||
devContainerWorkdir: envString("CODE_QUEUE_DEV_CONTAINER_WORKDIR", remoteDefaultWorkdir),
|
||||
@@ -351,6 +380,48 @@ function readConfig(): RuntimeConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function configureProviderGatewayEgressProxy(): { enabled: boolean; proxyUrl: string; noProxy: string; channel: string } {
|
||||
if (!config.egressProxyEnabled) {
|
||||
return { enabled: false, proxyUrl: "", noProxy: config.egressProxyNoProxy, channel: "provider-gateway" };
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
proxyUrl: config.egressProxyUrl,
|
||||
noProxy: config.egressProxyNoProxy,
|
||||
channel: "provider-gateway",
|
||||
};
|
||||
}
|
||||
|
||||
async function providerGatewayEgressProxyStatus(): Promise<JsonValue> {
|
||||
if (!providerGatewayEgressProxy.enabled) return { enabled: false, channel: "provider-gateway" };
|
||||
const base = {
|
||||
enabled: true,
|
||||
channel: providerGatewayEgressProxy.channel,
|
||||
proxyUrl: providerGatewayEgressProxy.proxyUrl,
|
||||
noProxy: providerGatewayEgressProxy.noProxy,
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 600);
|
||||
try {
|
||||
const url = new URL(providerGatewayEgressProxy.proxyUrl);
|
||||
url.pathname = "/__unidesk/egress-proxy/health";
|
||||
url.search = "";
|
||||
const response = await fetch(url, { signal: controller.signal });
|
||||
const bodyText = await response.text();
|
||||
let upstream: JsonValue = bodyText.slice(0, 1000);
|
||||
try {
|
||||
upstream = JSON.parse(bodyText) as JsonValue;
|
||||
} catch {
|
||||
// Keep the bounded body text as evidence if the proxy returned a non-JSON failure page.
|
||||
}
|
||||
return { ...base, connected: response.ok, status: response.status, upstream };
|
||||
} catch (error) {
|
||||
return { ...base, connected: false, error: error instanceof Error ? error.message : String(error) };
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function createLogger(service: string, logFile: string) {
|
||||
const writer = createHourlyJsonlWriter({
|
||||
baseLogFile: logFile,
|
||||
@@ -3689,7 +3760,13 @@ async function route(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url);
|
||||
if (req.method === "OPTIONS") return jsonResponse({ ok: true });
|
||||
try {
|
||||
if (url.pathname === "/" || url.pathname === "/health") return jsonResponse({ ok: true, service: "code-queue", queue: await queueSummaryForHealth(false), startedAt: serviceStartedAt });
|
||||
if (url.pathname === "/" || url.pathname === "/health") return jsonResponse({
|
||||
ok: true,
|
||||
service: "code-queue",
|
||||
queue: await queueSummaryForHealth(false),
|
||||
egressProxy: await providerGatewayEgressProxyStatus(),
|
||||
startedAt: serviceStartedAt,
|
||||
});
|
||||
if (url.pathname === "/logs") return jsonResponse({ ok: true, logs: recentLogs.slice(-parseLimit(url)) });
|
||||
if (url.pathname === "/api/events" && req.method === "GET") return jsonResponse({ ok: false, error: "Code Queue private SSE was removed; subscribe to oa-event-flow /api/events/stream with service:code-queue tags." }, 410);
|
||||
if (url.pathname === "/api/dev-ready" && req.method === "GET") return jsonResponse({ ok: true, devReady: collectDevReady() });
|
||||
|
||||
@@ -80,6 +80,9 @@ export interface RuntimeConfig {
|
||||
maxInMemoryOutputRecords: number;
|
||||
maxInMemoryEventRecords: number;
|
||||
maxActiveQueues: number;
|
||||
egressProxyEnabled: boolean;
|
||||
egressProxyUrl: string;
|
||||
egressProxyNoProxy: string;
|
||||
devContainerMasterHost: string;
|
||||
devContainerDefaultProviderId: string;
|
||||
devContainerImage: string;
|
||||
|
||||
Reference in New Issue
Block a user