mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-20 01:21:41 -06:00
2c3e537cb8
Three defects proven by running the shipped configs: Kubernetes probes checked only the status code against /startupz, but the pinned image predates that route and the Control UI answers unknown paths with a catch-all 200. A wedged pod was therefore marked Ready forever. Probes now assert the JSON probe contract and target routes the pinned image actually serves; verified in a kind cluster where the old command exits 0 on the missing route and the new one exits 1. render.yaml set no dockerCommand, so the image CMD ran without --allow-unconfigured and a fresh Render disk exited 78 with 'Missing config' before binding. Reproduced locally with Render's exact env. The Cloudflare Container readiness poll had the same route mismatch against operator-supplied official image digests; it now polls /healthz, which every published image serves. Also replaces an R2 verification step that could never fail: wrangler cannot list object keys, so the documented command 404'd into || true.
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { Container } from "@cloudflare/containers";
|
|
|
|
interface OpenClawContainerEnv {
|
|
ANTHROPIC_API_KEY?: string;
|
|
DISCORD_BOT_TOKEN?: string;
|
|
LITESTREAM_ACCESS_KEY_ID: string;
|
|
LITESTREAM_BUCKET: string;
|
|
LITESTREAM_ENDPOINT: string;
|
|
LITESTREAM_REGION: string;
|
|
LITESTREAM_SECRET_ACCESS_KEY: string;
|
|
OPENAI_API_KEY?: string;
|
|
OPENCLAW_GATEWAY_TOKEN: string;
|
|
OPENCLAW_WEBHOOK_ONLY: string;
|
|
SLACK_APP_TOKEN?: string;
|
|
SLACK_BOT_TOKEN?: string;
|
|
TELEGRAM_BOT_TOKEN?: string;
|
|
}
|
|
|
|
const OPTIONAL_SECRET_NAMES = [
|
|
"ANTHROPIC_API_KEY",
|
|
"DISCORD_BOT_TOKEN",
|
|
"OPENAI_API_KEY",
|
|
"SLACK_APP_TOKEN",
|
|
"SLACK_BOT_TOKEN",
|
|
"TELEGRAM_BOT_TOKEN",
|
|
] as const;
|
|
|
|
function buildContainerEnv(env: OpenClawContainerEnv): Record<string, string> {
|
|
const containerEnv: Record<string, string> = {
|
|
LITESTREAM_ACCESS_KEY_ID: env.LITESTREAM_ACCESS_KEY_ID,
|
|
LITESTREAM_BUCKET: env.LITESTREAM_BUCKET,
|
|
LITESTREAM_ENDPOINT: env.LITESTREAM_ENDPOINT,
|
|
LITESTREAM_REGION: env.LITESTREAM_REGION,
|
|
LITESTREAM_SECRET_ACCESS_KEY: env.LITESTREAM_SECRET_ACCESS_KEY,
|
|
OPENCLAW_GATEWAY_TOKEN: env.OPENCLAW_GATEWAY_TOKEN,
|
|
};
|
|
|
|
for (const [name, value] of Object.entries(containerEnv)) {
|
|
if (!value) {
|
|
throw new Error(`missing required Worker variable or secret: ${name}`);
|
|
}
|
|
}
|
|
|
|
for (const name of OPTIONAL_SECRET_NAMES) {
|
|
const value = env[name];
|
|
if (value) {
|
|
containerEnv[name] = value;
|
|
}
|
|
}
|
|
|
|
return containerEnv;
|
|
}
|
|
|
|
export class OpenClawContainer extends Container<OpenClawContainerEnv> {
|
|
override defaultPort = 8080;
|
|
// /healthz exists in every published OpenClaw image and answers as soon as the
|
|
// Gateway's listener is up, which is exactly what this readiness poll asks.
|
|
// Do not point this at a route the pinned image may not serve: the Control UI
|
|
// answers unknown paths with a catch-all 200, so a missing route would look
|
|
// permanently healthy instead of failing. /startupz additionally waits for
|
|
// startup work to finish and is the better signal once the derived image comes
|
|
// from a release that serves it.
|
|
override pingEndpoint = "localhost/healthz";
|
|
override sleepAfter = "10m";
|
|
|
|
private readonly webhookOnly: boolean;
|
|
|
|
constructor(ctx: unknown, env: OpenClawContainerEnv) {
|
|
super(ctx, env);
|
|
this.envVars = buildContainerEnv(env);
|
|
this.webhookOnly = env.OPENCLAW_WEBHOOK_ONLY === "true";
|
|
}
|
|
|
|
override async onActivityExpired(): Promise<void> {
|
|
// Socket channels need a continuously running process. Only an explicitly
|
|
// webhook-only installation may let the Container helper stop the instance.
|
|
if (this.webhookOnly) {
|
|
await super.onActivityExpired();
|
|
}
|
|
}
|
|
}
|