diff --git a/scripts/post_deploy.py b/scripts/post_deploy.py index 202d7ebeb..72e8d0a67 100644 --- a/scripts/post_deploy.py +++ b/scripts/post_deploy.py @@ -244,8 +244,8 @@ def get_api_headers(config: ResourceConfig) -> Dict[str, str]: return headers -async def check_admin_api_health(config: ResourceConfig, max_retries: int = 5, retry_delay: int = 10) -> bool: - """Check if the admin API is available with retry logic for cold starts.""" +async def check_admin_api_health(config: ResourceConfig, max_retries: int = 8, retry_delay: int = 20) -> bool: + """Check if the admin API is available with retry logic for cold starts and container restarts.""" print_step("Checking admin API health...") async with httpx.AsyncClient(timeout=30.0) as client: @@ -256,8 +256,32 @@ async def check_admin_api_health(config: ResourceConfig, max_retries: int = 5, r headers=get_api_headers(config) ) if response.status_code == 200: - print_success("Admin API is healthy") - return True + # Validate the response is actually from the backend admin API, + # not the frontend catch-all serving index.html + try: + data = response.json() + if data.get("status") == "healthy": + print_success("Admin API is healthy") + return True + else: + print_warning( + f"Attempt {attempt}/{max_retries}: Admin API returned unexpected JSON: {data}" + ) + except (ValueError, json.JSONDecodeError): + # Response is not JSON. + # This means the API proxy is not forwarding requests to the backend. + content_preview = response.text[:200] + is_html = " { - // Disable buffering for streaming responses - if (proxyRes.headers['content-type']?.includes('text/event-stream')) { - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('X-Accel-Buffering', 'no'); - res.setHeader('Connection', 'keep-alive'); - res.flushHeaders(); + const apiProxy = createProxyMiddleware({ + target: BACKEND_URL, + changeOrigin: true, + pathRewrite: { + '^/': '/api/' + }, + agent: httpAgent, + // Increase timeout for long-running requests (10 minutes) + proxyTimeout: 600000, + timeout: 600000, + // Support streaming responses (SSE) + onProxyRes: (proxyRes, req, res) => { + // Disable buffering for streaming responses + if (proxyRes.headers['content-type']?.includes('text/event-stream')) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('X-Accel-Buffering', 'no'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); + } + // Log response for debugging + console.log(`Proxy response: ${req.method} ${req.path} -> ${proxyRes.statusCode}`); + }, + onProxyReq: (proxyReq, req, res) => { + // Log request for debugging + console.log(`Proxy request: ${req.method} ${req.path}`); + }, + onError: (err, req, res) => { + console.error('Proxy error:', err.message); + if (!res.headersSent) { + res.status(502).json({ error: 'Backend service unavailable', details: err.message }); + } } - // Log response for debugging - console.log(`Proxy response: ${req.method} ${req.path} -> ${proxyRes.statusCode}`); - }, - onProxyReq: (proxyReq, req, res) => { - // Log request for debugging - console.log(`Proxy request: ${req.method} ${req.path}`); - }, - onError: (err, req, res) => { - console.error('Proxy error:', err.message); - if (!res.headersSent) { - res.status(502).json({ error: 'Backend service unavailable', details: err.message }); - } - } -})); + }); + app.use('/api', apiProxy); +} else { + // When BACKEND_URL is not set, return a clear error for API requests + // instead of letting them fall through to the SPA catch-all (which returns index.html with 200) + app.use('/api', (req, res) => { + res.status(503).json({ + error: 'Backend not configured', + message: 'BACKEND_URL environment variable is not set. The API proxy is not available.', + }); + }); } + // Serve static files from the build directory app.use(express.static(path.join(__dirname, 'static')));