Publish Image / publish (push) Successful in 36s
Replace the volatile in-memory Map/Set queue with BullMQ backed by Redis for persistence across restarts, automatic retry with exponential backoff, and non-blocking workspace cleanup. - Two queues: workspace-create (concurrency-limited) and workspace-cleanup (with polling instead of sleep-based stop→delete) - Active workspaces tracked in Redis hash for dedup across restarts - Stale sweep every 10 minutes catches orphaned workspaces - Graceful shutdown on SIGTERM/SIGINT - Replace node-cron with BullMQ repeatable jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
export interface Config {
|
|
/** Port for the HTTP server */
|
|
port: number;
|
|
/** Coder API base URL (e.g. https://coder.samson.media) */
|
|
coderUrl: string;
|
|
/** Coder API session token */
|
|
coderToken: string;
|
|
/** Coder template ID for workspaces */
|
|
coderTemplateId: string;
|
|
/** Gitea API token for the dev bot account */
|
|
giteaDevToken: string;
|
|
/** Gitea API token for the review bot account */
|
|
giteaReviewToken: string;
|
|
/** Anthropic API key passed to Claude Code in workspaces (fallback) */
|
|
anthropicApiKey: string;
|
|
/** Claude Code OAuth token for Max subscription (preferred) */
|
|
claudeOauthToken: string;
|
|
/** Username of the dev bot (to filter out self-replies) */
|
|
botDevUsername: string;
|
|
/** Username of the review bot (to filter out self-replies) */
|
|
botReviewUsername: string;
|
|
/** Gitea base URL (e.g. https://gitea.samson.media) */
|
|
giteaUrl: string;
|
|
/** Base URL for task-complete callbacks (e.g. https://sdlc.samson.media) */
|
|
callbackUrl: string;
|
|
/** Optional webhook secret for verifying Gitea signatures */
|
|
webhookSecret?: string;
|
|
/** Redis connection URL (e.g. redis://redis:6379) */
|
|
redisUrl: string;
|
|
/** Max concurrent workspace creations (default: 2) */
|
|
queueConcurrency: number;
|
|
/** Max concurrent cleanup jobs (default: 5) */
|
|
cleanupConcurrency: number;
|
|
/** Minutes before a workspace is considered stale (default: 120) */
|
|
staleWorkspaceMinutes: number;
|
|
}
|
|
|
|
function required(name: string): string {
|
|
const value = process.env[name];
|
|
if (!value) {
|
|
throw new Error(`Missing required environment variable: ${name}`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function loadConfig(): Config {
|
|
return {
|
|
port: parseInt(process.env.PORT || "3000", 10),
|
|
coderUrl: required("CODER_URL"),
|
|
coderToken: required("CODER_TOKEN"),
|
|
coderTemplateId: required("CODER_TEMPLATE_ID"),
|
|
giteaDevToken: required("GITEA_DEV_TOKEN"),
|
|
giteaReviewToken: required("GITEA_REVIEW_TOKEN"),
|
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY || "",
|
|
claudeOauthToken: process.env.CLAUDE_OAUTH_TOKEN || "",
|
|
botDevUsername: process.env.BOT_DEV_USERNAME || "claude-dev",
|
|
botReviewUsername: process.env.BOT_REVIEW_USERNAME || "claude-review",
|
|
giteaUrl: process.env.GITEA_URL || "https://gitea.samson.media",
|
|
callbackUrl: required("CALLBACK_URL"),
|
|
webhookSecret: process.env.WEBHOOK_SECRET,
|
|
redisUrl: required("REDIS_URL"),
|
|
queueConcurrency: parseInt(process.env.QUEUE_CONCURRENCY || "2", 10),
|
|
cleanupConcurrency: parseInt(process.env.CLEANUP_CONCURRENCY || "5", 10),
|
|
staleWorkspaceMinutes: parseInt(process.env.STALE_WORKSPACE_MINUTES || "120", 10),
|
|
};
|
|
}
|