mirror of
https://github.com/arjunkomath/openclaw-railway-template.git
synced 2026-08-14 00:48:11 +00:00
Merge pull request #61 from arjunkomath/staging
Update OpenClaw & improve gateway health reporting
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ RUN apt-get update \
|
||||
zip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN npm install -g openclaw@2026.4.21 clawhub@latest
|
||||
RUN npm install -g openclaw@2026.4.23 clawhub@latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@
|
||||
"scripts": {
|
||||
"dev": "node src/server.js",
|
||||
"start": "node src/server.js",
|
||||
"lint": "node -c src/server.js"
|
||||
"lint": "node -c src/server.js",
|
||||
"test": "node --test src/*.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.1.0",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export function canServeGatewayRequest({ configured, reachable }) {
|
||||
if (!configured) return false;
|
||||
return reachable;
|
||||
}
|
||||
|
||||
export function describeGatewayHealth({
|
||||
configured,
|
||||
hasProcessHandle,
|
||||
starting,
|
||||
reachable,
|
||||
}) {
|
||||
const serving = canServeGatewayRequest({
|
||||
configured,
|
||||
reachable,
|
||||
});
|
||||
return {
|
||||
gateway: !configured
|
||||
? "unconfigured"
|
||||
: serving
|
||||
? "ready"
|
||||
: "starting",
|
||||
gatewayRunning: hasProcessHandle && !starting,
|
||||
gatewayStarting: starting,
|
||||
gatewayReachable: reachable,
|
||||
statusCode: configured && !reachable && !starting ? 503 : 200,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
canServeGatewayRequest,
|
||||
describeGatewayHealth,
|
||||
} from "./gateway-readiness.js";
|
||||
|
||||
test("serves gateway requests when a detached gateway is reachable", () => {
|
||||
assert.equal(
|
||||
canServeGatewayRequest({
|
||||
configured: true,
|
||||
reachable: true,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("reports a reachable detached gateway as ready", () => {
|
||||
assert.deepEqual(
|
||||
describeGatewayHealth({
|
||||
configured: true,
|
||||
hasProcessHandle: false,
|
||||
starting: false,
|
||||
reachable: true,
|
||||
}),
|
||||
{
|
||||
gateway: "ready",
|
||||
gatewayRunning: false,
|
||||
gatewayStarting: false,
|
||||
gatewayReachable: true,
|
||||
statusCode: 200,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("does not serve gateway requests while configured gateway is unreachable", () => {
|
||||
assert.equal(
|
||||
canServeGatewayRequest({
|
||||
configured: true,
|
||||
reachable: false,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("does not treat a process handle as reachable gateway readiness", () => {
|
||||
assert.deepEqual(
|
||||
describeGatewayHealth({
|
||||
configured: true,
|
||||
hasProcessHandle: true,
|
||||
starting: false,
|
||||
reachable: false,
|
||||
}),
|
||||
{
|
||||
gateway: "starting",
|
||||
gatewayRunning: true,
|
||||
gatewayStarting: false,
|
||||
gatewayReachable: false,
|
||||
statusCode: 503,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("marks configured stopped gateway unhealthy for Railway recovery", () => {
|
||||
assert.deepEqual(
|
||||
describeGatewayHealth({
|
||||
configured: true,
|
||||
hasProcessHandle: false,
|
||||
starting: false,
|
||||
reachable: false,
|
||||
}),
|
||||
{
|
||||
gateway: "starting",
|
||||
gatewayRunning: false,
|
||||
gatewayStarting: false,
|
||||
gatewayReachable: false,
|
||||
statusCode: 503,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps starting gateway healthy during boot grace", () => {
|
||||
assert.deepEqual(
|
||||
describeGatewayHealth({
|
||||
configured: true,
|
||||
hasProcessHandle: false,
|
||||
starting: true,
|
||||
reachable: false,
|
||||
}),
|
||||
{
|
||||
gateway: "starting",
|
||||
gatewayRunning: false,
|
||||
gatewayStarting: true,
|
||||
gatewayReachable: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
);
|
||||
});
|
||||
+43
-26
@@ -8,6 +8,10 @@ import express from "express";
|
||||
import httpProxy from "http-proxy";
|
||||
import pty from "node-pty";
|
||||
import { WebSocketServer } from "ws";
|
||||
import {
|
||||
canServeGatewayRequest,
|
||||
describeGatewayHealth,
|
||||
} from "./gateway-readiness.js";
|
||||
|
||||
const PORT = Number.parseInt(process.env.PORT ?? "8080", 10);
|
||||
const STATE_DIR =
|
||||
@@ -185,24 +189,34 @@ function sleep(ms) {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
async function probeGatewayOnce() {
|
||||
async function probeGatewayOnce(opts = {}) {
|
||||
const endpoints = ["/openclaw", "/", "/health"];
|
||||
const timeoutMs = opts.timeoutMs ?? 2000;
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(`${GATEWAY_TARGET}${endpoint}`, {
|
||||
method: "GET",
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (res) {
|
||||
if (res.status < 500) {
|
||||
return { ok: true, endpoint };
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code !== "ECONNREFUSED" && err.cause?.code !== "ECONNREFUSED") {
|
||||
if (
|
||||
err.name !== "AbortError" &&
|
||||
err.code !== "ECONNREFUSED" &&
|
||||
err.cause?.code !== "ECONNREFUSED"
|
||||
) {
|
||||
const msg = err.code || err.message;
|
||||
if (msg !== "fetch failed" && msg !== "UND_ERR_CONNECT_TIMEOUT") {
|
||||
log.warn("gateway", `health check error: ${msg}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,36 +437,32 @@ app.get("/styles.css", (_req, res) => {
|
||||
});
|
||||
|
||||
app.get("/healthz", async (_req, res) => {
|
||||
let gateway = "unconfigured";
|
||||
if (isConfigured()) {
|
||||
gateway = isGatewayReady() ? "ready" : "starting";
|
||||
}
|
||||
res.json({ ok: true, gateway });
|
||||
const configured = isConfigured();
|
||||
const health = describeGatewayHealth({
|
||||
configured,
|
||||
hasProcessHandle: isGatewayReady(),
|
||||
starting: isGatewayStarting(),
|
||||
reachable: configured ? (await probeGatewayOnce()).ok : false,
|
||||
});
|
||||
res.json({ ok: true, gateway: health.gateway });
|
||||
});
|
||||
|
||||
app.get("/setup/healthz", async (_req, res) => {
|
||||
const configured = isConfigured();
|
||||
const gatewayRunning = isGatewayReady();
|
||||
const starting = isGatewayStarting();
|
||||
let gatewayReachable = false;
|
||||
const health = describeGatewayHealth({
|
||||
configured,
|
||||
hasProcessHandle: isGatewayReady(),
|
||||
starting: isGatewayStarting(),
|
||||
reachable: configured ? (await probeGatewayOnce()).ok : false,
|
||||
});
|
||||
|
||||
if (gatewayRunning) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 3000);
|
||||
const r = await fetch(`${GATEWAY_TARGET}/`, { signal: controller.signal });
|
||||
clearTimeout(timeout);
|
||||
gatewayReachable = r !== null;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
res.json({
|
||||
res.status(health.statusCode).json({
|
||||
ok: true,
|
||||
wrapper: true,
|
||||
configured,
|
||||
gatewayRunning,
|
||||
gatewayStarting: starting,
|
||||
gatewayReachable,
|
||||
gatewayRunning: health.gatewayRunning,
|
||||
gatewayStarting: health.gatewayStarting,
|
||||
gatewayReachable: health.gatewayReachable,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1421,15 +1431,22 @@ app.use(async (req, res) => {
|
||||
|
||||
if (isConfigured()) {
|
||||
if (!isGatewayReady()) {
|
||||
let gatewayReachable = false;
|
||||
try {
|
||||
await ensureGatewayRunning();
|
||||
gatewayReachable = (await probeGatewayOnce()).ok;
|
||||
} catch {
|
||||
return res
|
||||
.status(503)
|
||||
.sendFile(path.join(process.cwd(), "src", "public", "loading.html"));
|
||||
}
|
||||
|
||||
if (!isGatewayReady()) {
|
||||
if (
|
||||
!canServeGatewayRequest({
|
||||
configured: true,
|
||||
reachable: gatewayReachable,
|
||||
})
|
||||
) {
|
||||
return res
|
||||
.status(503)
|
||||
.sendFile(path.join(process.cwd(), "src", "public", "loading.html"));
|
||||
|
||||
Reference in New Issue
Block a user