Files
srbhr--resume-matcher/docker/start.sh
T
wehub-resource-sync 5bdf4cc89a
Publish Docker Image / publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:36 +08:00

256 lines
8.1 KiB
Bash

#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
BOLD='\033[1m'
# Internal port configuration for single-port deployment.
FRONTEND_PORT="3000"
BACKEND_PORT="8000"
# Print banner
print_banner() {
echo -e "${CYAN}"
cat << 'EOF'
██████╗ ███████╗███████╗██╗ ██╗███╗ ███╗███████╗
██╔══██╗██╔════╝██╔════╝██║ ██║████╗ ████║██╔════╝
██████╔╝█████╗ ███████╗██║ ██║██╔████╔██║█████╗
██╔══██╗██╔══╝ ╚════██║██║ ██║██║╚██╔╝██║██╔══╝
██║ ██║███████╗███████║╚██████╔╝██║ ╚═╝ ██║███████╗
╚═╝ ╚═╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
███╗ ███╗ █████╗ ████████╗ ██████╗██╗ ██╗███████╗██████╗
████╗ ████║██╔══██╗╚══██╔══╝██╔════╝██║ ██║██╔════╝██╔══██╗
██╔████╔██║███████║ ██║ ██║ ███████║█████╗ ██████╔╝
██║╚██╔╝██║██╔══██║ ██║ ██║ ██╔══██║██╔══╝ ██╔══██╗
██║ ╚═╝ ██║██║ ██║ ██║ ╚██████╗██║ ██║███████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
EOF
echo -e "${NC}"
echo -e "${BOLD} Crazy Stuff with Resumes and Cover letters${NC}"
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
# Print status message
status() {
echo -e "${GREEN}[✓]${NC} $1" >&2
}
# Print info message
info() {
echo -e "${BLUE}[i]${NC} $1" >&2
}
# Print warning message
warn() {
echo -e "${YELLOW}[!]${NC} $1" >&2
}
# Print error message
error() {
echo -e "${RED}[✗]${NC} $1" >&2
}
# Docker-style secret loader: supports VAR or VAR_FILE
file_env() {
local var="$1"
local def="${2:-}"
local file_var="${var}_FILE"
if [ -n "${!var:-}" ] && [ -n "${!file_var:-}" ]; then
error "Both $var and $file_var are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ -n "${!var:-}" ]; then
val="${!var}"
elif [ -n "${!file_var:-}" ]; then
if [ ! -r "${!file_var}" ]; then
error "Cannot read ${!file_var} for $file_var"
exit 1
fi
val="$(< "${!file_var}")"
fi
export "$var"="$val"
unset "$file_var"
}
normalize_log_level() {
local value="${1^^}"
local fallback="${2}"
local name="${3}"
case "$value" in
CRITICAL|ERROR|WARNING|INFO|DEBUG)
echo "$value"
;;
*)
warn "Invalid ${name}='$1', using ${fallback}"
echo "$fallback"
;;
esac
}
# Exit code to propagate from failed child processes
EXIT_CODE=0
# Cleanup function for graceful shutdown
cleanup() {
# Prevent re-entry from signals during cleanup
trap '' SIGTERM SIGINT SIGQUIT
echo "" >&2
info "Shutting down Resume Matcher..."
# Kill frontend if running
if [ -n "$FRONTEND_PID" ] && kill -0 "$FRONTEND_PID" 2>/dev/null; then
kill "$FRONTEND_PID" 2>/dev/null || true
wait "$FRONTEND_PID" 2>/dev/null || true
fi
# Kill backend if running
if [ -n "$BACKEND_PID" ] && kill -0 "$BACKEND_PID" 2>/dev/null; then
kill "$BACKEND_PID" 2>/dev/null || true
wait "$BACKEND_PID" 2>/dev/null || true
fi
status "Shutdown complete"
exit "${EXIT_CODE}"
}
# Initialize PIDs so cleanup doesn't fail on early exit
BACKEND_PID=""
FRONTEND_PID=""
# Set up signal handlers
trap cleanup SIGTERM SIGINT SIGQUIT
# Print banner
print_banner
# Display routing configuration
info "Routing configuration:"
echo -e " Public port: ${BOLD}${FRONTEND_PORT}${NC}"
echo -e " Internal API: ${BOLD}${BACKEND_PORT}${NC} (proxied at /api)"
echo ""
# Resolve env vars and optional *_FILE secret mounts
info "Loading configuration from environment and *_FILE secrets..."
file_env "LOG_LEVEL" "INFO"
file_env "LOG_LLM" "WARNING"
file_env "LLM_PROVIDER" "openai"
# Only resolve optional LLM_* vars if they (or their *_FILE variants) are provided,
# so we don't override backend defaults with empty strings.
if [ -n "${LLM_MODEL:-}" ] || [ -n "${LLM_MODEL_FILE:-}" ]; then
file_env "LLM_MODEL"
fi
if [ -n "${LLM_API_KEY:-}" ] || [ -n "${LLM_API_KEY_FILE:-}" ]; then
file_env "LLM_API_KEY"
fi
if [ -n "${LLM_API_BASE:-}" ] || [ -n "${LLM_API_BASE_FILE:-}" ]; then
file_env "LLM_API_BASE"
fi
APP_LOG_LEVEL="$(normalize_log_level "${LOG_LEVEL}" "INFO" "LOG_LEVEL")"
LLM_LOG_LEVEL="$(normalize_log_level "${LOG_LLM}" "WARNING" "LOG_LLM")"
export LOG_LEVEL="${APP_LOG_LEVEL}"
export LOG_LLM="${LLM_LOG_LEVEL}"
UVICORN_LOG_LEVEL="$(echo "${APP_LOG_LEVEL}" | tr '[:upper:]' '[:lower:]')"
info "Application log level: ${BOLD}${LOG_LEVEL}${NC}"
info "LiteLLM log level: ${BOLD}${LOG_LLM}${NC}"
if [ "${LOG_LLM}" = "DEBUG" ]; then
warn "LOG_LLM=DEBUG may log API keys in plaintext. Do not use in production."
fi
status "Configuration loaded"
# Check and create data directory
info "Checking data directory..."
DATA_DIR="/app/backend/data"
if [ ! -d "$DATA_DIR" ]; then
mkdir -p "$DATA_DIR"
status "Created data directory: $DATA_DIR"
else
status "Data directory exists: $DATA_DIR"
fi
# Check for Playwright browsers
info "Checking Playwright browsers..."
if [ -d "/root/.cache/ms-playwright" ] || [ -d "/home/appuser/.cache/ms-playwright" ]; then
status "Playwright browsers found"
else
warn "Installing Playwright Chromium (this may take a moment)..."
python -m playwright install chromium || {
warn "Playwright install failed — PDF export may not work"
}
status "Playwright setup complete"
fi
# Start backend
echo ""
info "Starting backend server on internal port ${BACKEND_PORT}..."
cd /app/backend
trap '' SIGTERM SIGINT SIGQUIT
python -m uvicorn app.main:app --host 0.0.0.0 --port "${BACKEND_PORT}" --log-level "${UVICORN_LOG_LEVEL}" &
BACKEND_PID=$!
trap cleanup SIGTERM SIGINT SIGQUIT
# Wait for backend to be ready
info "Waiting for backend to be ready..."
for i in {1..30}; do
if curl -s "http://127.0.0.1:${BACKEND_PORT}/api/v1/health" > /dev/null 2>&1; then
status "Backend is ready (PID: $BACKEND_PID)"
break
fi
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
error "Backend process (PID: $BACKEND_PID) died during startup"
exit 1
fi
if [ $i -eq 30 ]; then
error "Backend failed to start within 30 seconds"
exit 1
fi
sleep 1
done
# Start frontend
echo ""
info "Starting frontend server on port ${FRONTEND_PORT}..."
cd /app/frontend
# Next.js uses PORT environment variable
export HOSTNAME="0.0.0.0"
export PORT="${FRONTEND_PORT}"
if [ ! -f "server.js" ]; then
error "Missing frontend standalone server.js. Rebuild the Docker image."
exit 1
fi
trap '' SIGTERM SIGINT SIGQUIT
node server.js "$@" &
FRONTEND_PID=$!
trap cleanup SIGTERM SIGINT SIGQUIT
status "Frontend is running (PID: $FRONTEND_PID)"
# Wait for either process to exit, but ignore errexit for this wait
set +e
wait -n "$BACKEND_PID" "$FRONTEND_PID" 2>/dev/null
EXIT_CODE=$?
set -e
warn "A process exited unexpectedly (exit code: ${EXIT_CODE}), shutting down..."
cleanup