|
|
#!/usr/bin/env bash |
|
|
set -Eeuo pipefail |
|
|
|
|
|
DEV_MODE="${DEV_MODE:-false}" |
|
|
|
|
|
MAIN_HOST="${HOST:-0.0.0.0}" |
|
|
MAIN_PORT="${PORT:-7860}" |
|
|
|
|
|
MODEL_HOST="${MODEL_HOST:-0.0.0.0}" |
|
|
MODEL_PORT="${MODEL_PORT:-8001}" |
|
|
|
|
|
TAILWIND_BIN="/app/bin/tailwindcss" |
|
|
TAILWIND_IN="/app/public/styles/input.css" |
|
|
TAILWIND_OUT="/app/public/styles/output.css" |
|
|
|
|
|
log() { printf "%s %s\n" "$(date +'%Y-%m-%dT%H:%M:%S%z')" "$*"; } |
|
|
|
|
|
ensure_tailwind() { |
|
|
if [[ -x "${TAILWIND_BIN}" ]]; then |
|
|
log "Tailwind CSS found at ${TAILWIND_BIN}" |
|
|
return |
|
|
fi |
|
|
|
|
|
log "Tailwind CSS not found, downloading binary..." |
|
|
mkdir -p /app/bin |
|
|
|
|
|
local os arch |
|
|
os="$(uname -s | tr '[:upper:]' '[:lower:]')" |
|
|
arch="$(uname -m)" |
|
|
|
|
|
case "${os}" in |
|
|
darwin) os="macos" ;; |
|
|
linux) os="linux" ;; |
|
|
*) log "Unsupported OS: ${os}"; exit 1 ;; |
|
|
esac |
|
|
|
|
|
case "${arch}" in |
|
|
x86_64|amd64) arch="x64" ;; |
|
|
arm64|aarch64) arch="arm64" ;; |
|
|
*) log "Unsupported arch: ${arch}"; exit 1 ;; |
|
|
esac |
|
|
|
|
|
curl -sSLo "${TAILWIND_BIN}" \ |
|
|
"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-${os}-${arch}" |
|
|
chmod +x "${TAILWIND_BIN}" |
|
|
} |
|
|
|
|
|
build_tailwind() { |
|
|
if [[ "${DEV_MODE}" == "true" ]]; then |
|
|
log "Building Tailwind (initial)..." |
|
|
"${TAILWIND_BIN}" -i "${TAILWIND_IN}" -o "${TAILWIND_OUT}" |
|
|
log "Starting Tailwind in watch mode (dev)..." |
|
|
"${TAILWIND_BIN}" -i "${TAILWIND_IN}" -o "${TAILWIND_OUT}" --watch=always --poll & |
|
|
export TAILWIND_PID=$! |
|
|
else |
|
|
log "Building Tailwind (prod)..." |
|
|
"${TAILWIND_BIN}" -i "${TAILWIND_IN}" -o "${TAILWIND_OUT}" --minify |
|
|
fi |
|
|
} |
|
|
|
|
|
download_dataset() { |
|
|
|
|
|
if ! PYTHONPATH=/app poetry run python /app/scripts/download_dataset.py; then |
|
|
log "WARN: dataset download failed; continuing without cached dataset." |
|
|
fi |
|
|
} |
|
|
|
|
|
start_model_server() { |
|
|
|
|
|
if [[ "${DEV_MODE}" == "true" ]]; then |
|
|
log "DEV mode: NOT starting model server. Open VS Code and press F5 (Run and Debug) to launch it." |
|
|
return 0 |
|
|
fi |
|
|
|
|
|
if [[ -n "${MODEL__ITEM_MODEL_PATH:-}" || -n "${MODEL__SCALE_MODEL_PATH:-}" ]]; then |
|
|
log "Starting model server on ${MODEL_HOST}:${MODEL_PORT}..." |
|
|
|
|
|
poetry run uvicorn src.servers.model_server:app \ |
|
|
--host "${MODEL_HOST}" \ |
|
|
--port "${MODEL_PORT}" \ |
|
|
--workers "${MODEL_WORKERS:-1}" & |
|
|
export MODEL_PID=$! |
|
|
else |
|
|
log "Model paths not configured — NOT starting model server." |
|
|
fi |
|
|
} |
|
|
|
|
|
start_main_app() { |
|
|
if [[ "${DEV_MODE}" == "true" ]]; then |
|
|
log "DEV mode: NOT starting main FastAPI app. Open VS Code and press F5 (Run and Debug) to launch it." |
|
|
return 0 |
|
|
fi |
|
|
|
|
|
log "PROD mode: starting Gunicorn (Uvicorn worker) on ${MAIN_HOST}:${MAIN_PORT}..." |
|
|
exec poetry run gunicorn src.main:app \ |
|
|
-k uvicorn.workers.UvicornWorker \ |
|
|
-w "${WEB_CONCURRENCY:-2}" \ |
|
|
-b "${MAIN_HOST}:${MAIN_PORT}" |
|
|
} |
|
|
|
|
|
cleanup() { |
|
|
log "Shutting down..." |
|
|
|
|
|
if [[ -n "${TAILWIND_PID:-}" ]]; then |
|
|
log "Stopping Tailwind (PID ${TAILWIND_PID})" |
|
|
kill "${TAILWIND_PID}" 2>/dev/null || true |
|
|
fi |
|
|
if [[ -n "${MODEL_PID:-}" ]]; then |
|
|
log "Stopping model server (PID ${MODEL_PID})" |
|
|
kill "${MODEL_PID}" 2>/dev/null || true |
|
|
fi |
|
|
} |
|
|
|
|
|
trap cleanup SIGINT SIGTERM |
|
|
|
|
|
if [[ "${DEV_MODE}" == "true" ]]; then |
|
|
log "Starting services in DEVELOPMENT mode..." |
|
|
else |
|
|
log "Starting services in PRODUCTION mode..." |
|
|
fi |
|
|
|
|
|
ensure_tailwind |
|
|
download_dataset |
|
|
build_tailwind |
|
|
start_model_server |
|
|
start_main_app |
|
|
|
|
|
if [[ "${DEV_MODE}" == "true" ]]; then |
|
|
log "Dev services running. Press Ctrl+C to stop." |
|
|
wait |
|
|
fi |