#!/bin/bash # ── Hermes Workspace Combined Entrypoint ── # Waits for the Hermes gateway container (hermes:8642) to become healthy, # then starts the Hermes Workspace web UI in the foreground. # Supports graceful shutdown via SIGTERM/SIGINT. # ────────────────────────────────────────── set -euo pipefail # ── Configuration ────────────────────────────────────────────── GATEWAY_HOST="${GATEWAY_HOST:-hermes}" GATEWAY_PORT="${GATEWAY_PORT:-8642}" GATEWAY_URL="http://${GATEWAY_HOST}:${GATEWAY_PORT}" HEALTH_ENDPOINT="${HEALTH_ENDPOINT:-/health}" MAX_RETRIES="${HEALTH_MAX_RETRIES:-60}" RETRY_INTERVAL="${HEALTH_RETRY_INTERVAL:-2}" WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}" WORKSPACE_ENTRY="${WORKSPACE_ENTRY:-server-entry.js}" PID_FILE="${PID_FILE:-/tmp/workspace.pid}" # ── Logging ──────────────────────────────────────────────────── log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*"; } log_warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $*"; } log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*"; } # ── Graceful Shutdown ────────────────────────────────────────── _workspace_pid="" _shutting_down=false cleanup() { if [ "$_shutting_down" = true ]; then return fi _shutting_down=true log_info "Shutdown signal received, cleaning up..." # Stop workspace process if running if [ -n "$_workspace_pid" ] && kill -0 "$_workspace_pid" 2>/dev/null; then log_info "Stopping workspace (PID: $_workspace_pid)..." kill -TERM "$_workspace_pid" 2>/dev/null || true # Give it time to shut down gracefully local wait_sec=10 while kill -0 "$_workspace_pid" 2>/dev/null && [ "$wait_sec" -gt 0 ]; do sleep 1 wait_sec=$((wait_sec - 1)) done # Force kill if still running if kill -0 "$_workspace_pid" 2>/dev/null; then log_warn "Workspace did not shut down gracefully, force killing..." kill -KILL "$_workspace_pid" 2>/dev/null || true fi fi # Clean up PID file [ -f "$PID_FILE" ] && rm -f "$PID_FILE" log_info "Shutdown complete." exit 0 } # Trap termination signals for graceful shutdown trap cleanup SIGTERM SIGINT # ── Gateway Health Check ─────────────────────────────────────── wait_for_gateway() { local url="${GATEWAY_URL}${HEALTH_ENDPOINT}" local retries="$MAX_RETRIES" local interval="$RETRY_INTERVAL" local attempt=0 log_info "Waiting for Hermes gateway at ${GATEWAY_URL}..." log_info "Max retries: ${retries}, interval: ${interval}s" while [ "$attempt" -lt "$retries" ]; do attempt=$((attempt + 1)) if curl -fsS "${url}" >/dev/null 2>&1; then log_info "Gateway is healthy after ${attempt} attempt(s) (${GATEWAY_URL})" return 0 fi if [ "$attempt" -lt "$retries" ]; then log_info "Gateway not ready yet (attempt ${attempt}/${retries}), retrying in ${interval}s..." sleep "$interval" fi done log_error "Gateway did not become healthy after ${retries} attempts (${retries * interval}s)" return 1 } # ── Workspace Startup ────────────────────────────────────────── start_workspace() { local entry="${WORKSPACE_DIR}/${WORKSPACE_ENTRY}" if [ ! -d "$WORKSPACE_DIR" ]; then log_error "Workspace directory not found: ${WORKSPACE_DIR}" return 1 fi if [ ! -f "$entry" ]; then log_error "Workspace entry point not found: ${entry}" return 1 fi log_info "Starting Hermes Workspace web UI..." log_info " Directory: ${WORKSPACE_DIR}" log_info " Entry: ${entry}" cd "$WORKSPACE_DIR" # Start workspace in background so we can trap signals exec node --max-old-space-size=2048 "${entry}" & _workspace_pid=$! echo "$_workspace_pid" > "$PID_FILE" log_info "Workspace started (PID: ${_workspace_pid})" # Wait for workspace process wait "$_workspace_pid" local exit_code=$? log_info "Workspace exited with code ${exit_code}" return "$exit_code" } # ── Main ─────────────────────────────────────────────────────── main() { log_info "=== Hermes Workspace Combined Entrypoint ===" log_info "Gateway: ${GATEWAY_URL}" log_info "Workspace: ${WORKSPACE_DIR}/${WORKSPACE_ENTRY}" log_info "PID file: ${PID_FILE}" # Wait for gateway to be healthy if ! wait_for_gateway; then log_warn "Proceeding without confirmed gateway health..." fi # Start the workspace start_workspace local exit_code=$? log_info "Entrypoint exiting with code ${exit_code}" return "$exit_code" } # Run main; exit with its return code main "$@"