diff --git a/Dockerfile b/Dockerfile index e54a575..bd68c10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,27 @@ -FROM node:20-slim +## ── Build stage ────────────────────────────── +FROM node:20-slim AS build WORKDIR /app -# Copy package files -COPY package*.json ./ +COPY package*.json tsconfig.json ./ +RUN npm ci + +COPY src/ ./src/ +RUN npm run build -# Install dependencies -RUN npm ci --only=production +## ── Runtime stage ──────────────────────────── +FROM node:20 -# Copy built files -COPY dist/ ./dist/ +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --omit=dev + +COPY --from=build /app/dist/ ./dist/ -# Set environment variables (override at runtime) ENV NODE_ENV=production -# Run the bot +# Cloud Run requires a listening port +EXPOSE 8080 + CMD ["node", "dist/index.js", "start"] diff --git a/src/index.ts b/src/index.ts index 0e3047f..ed393e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,8 +25,9 @@ import { pathToPixels, } from "./pathfinding.js"; import { resolveBotConfig, type BotConfig } from "./bot-config.js"; +import { startHealthServer } from "./server.js"; -// Resolve config from CLI args (--bot=clawd or --bot=gremlin) +// Resolve config from CLI args, BOT_NAME env var, or default to "clawd" const config = resolveBotConfig(); /** @@ -778,6 +779,15 @@ export default defineAgent({ // Run the agent when executed directly if (process.argv[1] === fileURLToPath(import.meta.url)) { + // Start HTTP health-check server for Cloud Run (no-op locally if PORT unset). + startHealthServer(); + + // Graceful shutdown on SIGTERM (sent by Cloud Run before stopping a container). + process.on("SIGTERM", () => { + console.log("[Bot] SIGTERM received, shutting down gracefully..."); + process.exit(0); + }); + cli.runApp( new WorkerOptions({ agent: fileURLToPath(import.meta.url), diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..12eba82 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,21 @@ +import { createServer } from "node:http"; + +/** + * Minimal HTTP health-check server for Cloud Run. + * + * Cloud Run requires a listening port to manage container lifecycle. + * This server responds 200 to all requests — used for both health checks + * and wake-up pings from the Cloud Function. + */ +export function startHealthServer(): void { + const port = parseInt(process.env.PORT || "8080", 10); + + const server = createServer((_req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("ok"); + }); + + server.listen(port, () => { + console.log(`[Health] Listening on port ${port}`); + }); +}