feat: add docker-compose deployment and harden gateway startup

- add docker-compose setup with hermes-agent + hermes-webui

- make runtime config env-driven (compose vars + HERMES_BIN)

- improve gateway startup/restart resilience in docker

- make base image configurable via BASE_IMAGE/HERMES_AGENT_IMAGE

Closes https://github.com/EKKOLearnAI/hermes-web-ui/issues/14
This commit is contained in:
P2K0
2026-04-17 03:13:24 +08:00
parent b8d121ed79
commit f0d1d2e16c
10 changed files with 290 additions and 28 deletions
@@ -1,6 +1,32 @@
import type { Context } from 'koa'
import { config } from '../../config'
function isTransientGatewayError(err: any): boolean {
const msg = String(err?.message || '')
const causeCode = String(err?.cause?.code || '')
return (
causeCode === 'ECONNREFUSED' ||
causeCode === 'ECONNRESET' ||
/ECONNREFUSED|ECONNRESET|fetch failed|socket hang up/i.test(msg)
)
}
async function waitForGatewayReady(upstream: string, timeoutMs: number = 5000): Promise<boolean> {
const deadline = Date.now() + timeoutMs
const healthUrl = `${upstream}/health`
while (Date.now() < deadline) {
try {
const res = await fetch(healthUrl, {
method: 'GET',
signal: AbortSignal.timeout(1200),
})
if (res.ok) return true
} catch { }
await new Promise(resolve => setTimeout(resolve, 250))
}
return false
}
export async function proxy(ctx: Context) {
const upstream = config.upstream.replace(/\/$/, '')
// Rewrite path for upstream gateway:
@@ -36,11 +62,23 @@ export async function proxy(ctx: Context) {
body = (ctx as any).request.rawBody as string | undefined
}
const res = await fetch(url, {
const requestInit: RequestInit = {
method: ctx.req.method,
headers,
body,
})
}
let res: Response
try {
res = await fetch(url, requestInit)
} catch (err: any) {
// Gateway may be restarting; wait briefly and retry once.
if (isTransientGatewayError(err) && await waitForGatewayReady(upstream)) {
res = await fetch(url, requestInit)
} else {
throw err
}
}
// Set response headers
const resHeaders: Record<string, string> = {}