Spaces:
Running
Running
File size: 6,081 Bytes
88d2f2a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | #!/usr/bin/env bash
# =============================================================================
# PolyglotAlpha β container entrypoint (W23, Hugging Face Spaces)
# =============================================================================
# Boots three processes inside one HF Spaces container:
# 1. uvicorn (FastAPI, 127.0.0.1:8000) β the backend
# 2. next start (Next.js prod, 127.0.0.1:3001) β the UI
# 3. nginx (0.0.0.0:7860) β the only port HF exposes externally
#
# A few practical concerns the script handles:
# * If the Space has persistent storage mounted at `/data`, copy the
# seeded SQLite DB there on first boot and re-point DATABASE_URL.
# * Wait for backend health before firing the pre-seed events so the
# leaderboard / events list aren't empty on the reviewer's first visit.
# * Trap SIGTERM/SIGINT so HF's graceful shutdown actually kills children
# (the default `wait` keeps the parent alive but leaves zombies if we
# don't propagate the signal).
# -----------------------------------------------------------------------------
set -euo pipefail
log() {
printf '[%s entrypoint] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >&2
}
# -----------------------------------------------------------------------------
# Persistent storage handling β HF Spaces "small persistent storage" tier
# mounts a writable /data volume that survives sleeps + redeploys. Without
# it, the SQLite DB lives in the container FS and resets on every redeploy
# (acceptable for a pure demo; cold starts still pre-seed below).
# -----------------------------------------------------------------------------
if [[ -d /data && -w /data ]]; then
log "persistent /data detected β relocating SQLite DB"
if [[ ! -f /data/polyglot_alpha.db ]]; then
if [[ -f /app/polyglot_alpha.db ]]; then
cp /app/polyglot_alpha.db /data/polyglot_alpha.db
log "seeded /data/polyglot_alpha.db from container image"
fi
fi
export DATABASE_URL="sqlite:////data/polyglot_alpha.db"
log "DATABASE_URL=${DATABASE_URL}"
else
log "no /data volume β using in-image SQLite (will reset on redeploy)"
fi
# -----------------------------------------------------------------------------
# 1. Backend
# -----------------------------------------------------------------------------
cd /app
log "starting uvicorn on 127.0.0.1:8000"
python -m uvicorn polyglot_alpha.api.main:app \
--host 127.0.0.1 \
--port 8000 \
--log-level info \
--no-access-log &
BACKEND_PID=$!
# -----------------------------------------------------------------------------
# 2. Frontend
# -----------------------------------------------------------------------------
log "starting Next.js on 127.0.0.1:3001"
(
cd /app/ui
exec npx --no-install next start -H 127.0.0.1 -p 3001
) &
FRONTEND_PID=$!
# -----------------------------------------------------------------------------
# Wait for backend health before firing the pre-seed lifecycle. We bound the
# wait at ~60 s so a wedged backend still surfaces (nginx will return 502 on
# /health and HF Spaces will show the failure clearly).
# -----------------------------------------------------------------------------
wait_for_backend() {
local attempts=0
while (( attempts < 60 )); do
if curl -fsS --max-time 2 http://127.0.0.1:8000/health > /dev/null 2>&1; then
log "backend healthy after ${attempts}s"
return 0
fi
sleep 1
attempts=$((attempts + 1))
done
log "backend never reached /health β proceeding anyway"
return 1
}
# -----------------------------------------------------------------------------
# Pre-seed events β fire 3 mock lifecycles so the leaderboard / events /
# history pages have something to show the first reviewer. We background
# this so nginx can come up immediately. The `|| true` ensures any single
# failure doesn't take down the whole entrypoint.
# -----------------------------------------------------------------------------
preseed_events() {
wait_for_backend || return 0
log "pre-seeding 3 mock events"
local i
for i in 1 2 3; do
curl -fsS -X POST http://127.0.0.1:8000/trigger/event \
-H "Content-Type: application/json" \
-d '{"mode":"mock"}' > /dev/null 2>&1 || \
log "pre-seed event ${i} failed (continuing)"
sleep 4
done
log "pre-seed complete"
}
# Run pre-seed in a `disown`-ed subshell so its natural exit (after 3
# events β 14s) does NOT trigger the `wait -n` teardown below. Only the
# 3 critical processes (backend, frontend, nginx) should bring down
# the container when they die.
( preseed_events ) &
disown $!
# -----------------------------------------------------------------------------
# 3. nginx β run in foreground as PID 1's last child so its exit takes down
# the container if proxy config is broken (fail-fast).
# -----------------------------------------------------------------------------
log "starting nginx on 0.0.0.0:7860"
nginx -g 'daemon off;' &
NGINX_PID=$!
# -----------------------------------------------------------------------------
# Signal handling β propagate SIGTERM/SIGINT to all children. HF Spaces
# sends SIGTERM with a 30s grace before SIGKILL.
# -----------------------------------------------------------------------------
shutdown() {
log "received shutdown signal β forwarding to children"
kill -TERM "$BACKEND_PID" "$FRONTEND_PID" "$NGINX_PID" 2>/dev/null || true
wait "$BACKEND_PID" "$FRONTEND_PID" "$NGINX_PID" 2>/dev/null || true
log "all children exited β bye"
exit 0
}
trap shutdown SIGTERM SIGINT
# Wait on the 3 critical PIDs explicitly β `wait -n` without args
# would catch ANY background job (including the pre-seed loop), so we
# enumerate just the long-running services. If backend/frontend/nginx
# dies, abort the container so failure surfaces in HF logs.
wait -n "$BACKEND_PID" "$FRONTEND_PID" "$NGINX_PID"
log "a critical child process exited β tearing down the rest"
shutdown
|