From 3bd6213a8d00c9751feea0e379d15e3aeb941184 Mon Sep 17 00:00:00 2001 From: OwusuBlessing Date: Wed, 11 Jun 2025 17:40:17 +0100 Subject: [PATCH] prod deploy version --- app.py | 9 +- deploy.sh | 216 --------------------------------------------- gunicorn.pid | 1 + gunicorn_config.py | 77 ++++++++++++++++ server_deploy.sh | 215 ++++++++++++++++++++++++++++++++++++++++++++ start.sh | 7 ++ 6 files changed, 306 insertions(+), 219 deletions(-) delete mode 100644 deploy.sh create mode 100644 gunicorn.pid create mode 100644 gunicorn_config.py create mode 100644 server_deploy.sh create mode 100755 start.sh diff --git a/app.py b/app.py index 98b847f..4772c92 100644 --- a/app.py +++ b/app.py @@ -410,10 +410,13 @@ async def generate_quiz_endpoint( status_code=500, detail=f"Unexpected error during quiz generation: {str(e)}" ) + +@app.get("/health") +async def health_check(): + """Health check endpoint to verify the service is running.""" + return {"status": "healthy", "timestamp": datetime.now().isoformat()} - - - + @app.on_event("startup") async def startup_event(): """Initialize required components on startup""" diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index cceadfa..0000000 --- a/deploy.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/bin/bash - -# Configuration -SERVER_USER="your_username" -SERVER_IP="your_server_ip" -APP_DIR="/path/to/your/app" -PORT=5042 -PYTHON_VERSION="3.11" -WORKERS=4 -THREADS=2 -TIMEOUT=120 -MAX_REQUESTS=1000 -MAX_REQUESTS_JITTER=50 -DEBUG_MODE=false # Set to true for verbose output - -# Colors for output -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Logging function -log() { - local level=$1 - local message=$2 - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - - case $level in - "INFO") - echo -e "${BLUE}[$timestamp] INFO: $message${NC}" - ;; - "SUCCESS") - echo -e "${GREEN}[$timestamp] SUCCESS: $message${NC}" - ;; - "WARNING") - echo -e "${YELLOW}[$timestamp] WARNING: $message${NC}" - ;; - "ERROR") - echo -e "${RED}[$timestamp] ERROR: $message${NC}" - ;; - esac -} - -# Debug logging function -debug_log() { - if [ "$DEBUG_MODE" = true ]; then - echo -e "${YELLOW}[DEBUG] $1${NC}" - fi -} - -# Error handling -set -e -trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG -trap 'if [ $? -ne 0 ]; then log "ERROR" "Command failed: $last_command"; exit 1; fi' EXIT - -log "INFO" "Starting deployment process..." - -# Check if required packages are installed -log "INFO" "Checking required packages..." -if ! command -v ssh &> /dev/null; then - log "ERROR" "SSH is not installed. Please install it first." - exit 1 -fi - -# Create requirements.txt if it doesn't exist -if [ ! -f "requirements.txt" ]; then - log "INFO" "Creating requirements.txt..." - echo "fastapi==0.109.2 -uvicorn==0.27.1 -gunicorn==21.2.0 -python-dotenv==1.0.1 -langchain-openai==0.0.5 -requests==2.31.0 -PyPDF2==3.0.1" > requirements.txt - debug_log "Created requirements.txt with specific versions" -fi - -# Create gunicorn config file -log "INFO" "Creating Gunicorn configuration..." -cat > gunicorn_config.py << EOL -import multiprocessing -import os - -# Server socket -bind = "0.0.0.0:${PORT}" -backlog = 2048 - -# Worker processes -workers = ${WORKERS} -worker_class = "uvicorn.workers.UvicornWorker" -worker_connections = 1000 -timeout = ${TIMEOUT} -keepalive = 2 - -# Process naming -proc_name = "firefighter" -pythonpath = "." - -# Logging -accesslog = "logs/access.log" -errorlog = "logs/error.log" -loglevel = "info" - -# Server mechanics -daemon = False -pidfile = "gunicorn.pid" -umask = 0 -user = None -group = None -tmp_upload_dir = None - -# SSL -keyfile = None -certfile = None - -# Server hooks -def on_starting(server): - pass - -def on_reload(server): - pass - -def on_exit(server): - pass - -# Worker lifecycle -max_requests = ${MAX_REQUESTS} -max_requests_jitter = ${MAX_REQUESTS_JITTER} -graceful_timeout = 30 -preload_app = True - -# Debug -reload = False -reload_engine = "auto" -spew = False - -# Server mechanics -check_config = False -preload_app = True -EOL -debug_log "Created gunicorn_config.py with specified settings" - -# Copy files to server -log "INFO" "Copying files to server..." -debug_log "Using SCP to copy files to $SERVER_USER@$SERVER_IP:$APP_DIR/" -scp -r ./* $SERVER_USER@$SERVER_IP:$APP_DIR/ - -# SSH into server and set up environment -log "INFO" "Setting up environment on server..." -ssh $SERVER_USER@$SERVER_IP << EOF - set -e - cd $APP_DIR - - # Install Python 3.11 if not present - if ! command -v python${PYTHON_VERSION} &> /dev/null; then - echo "Installing Python ${PYTHON_VERSION}..." - sudo apt-get update - sudo apt-get install -y software-properties-common - sudo add-apt-repository -y ppa:deadsnakes/ppa - sudo apt-get update - sudo apt-get install -y python${PYTHON_VERSION} python${PYTHON_VERSION}-venv python${PYTHON_VERSION}-dev - fi - - # Create virtual environment if it doesn't exist - if [ ! -d "venv" ]; then - python${PYTHON_VERSION} -m venv venv - fi - - # Activate virtual environment and install dependencies - source venv/bin/activate - pip install --upgrade pip - pip install -r requirements.txt - - # Create systemd service file - sudo tee /etc/systemd/system/firefighter.service << 'EOL' -[Unit] -Description=Fire Fighter Interview API -After=network.target - -[Service] -User=$SERVER_USER -WorkingDirectory=$APP_DIR -Environment="PATH=$APP_DIR/venv/bin" -Environment="PYTHONPATH=$APP_DIR" -ExecStart=$APP_DIR/venv/bin/gunicorn -c gunicorn_config.py app:app -Restart=always -RestartSec=5 -StartLimitInterval=0 - -[Install] -WantedBy=multi-user.target -EOL - - # Create log directory - mkdir -p logs - - # Reload systemd and start service - sudo systemctl daemon-reload - sudo systemctl enable firefighter - sudo systemctl restart firefighter - - # Check service status - sudo systemctl status firefighter -EOF - -log "SUCCESS" "Deployment completed!" -log "INFO" "Your application should now be running at http://$SERVER_IP:$PORT" -log "INFO" "Check logs at $APP_DIR/logs/" - -# Print helpful commands -echo -e "\n${BLUE}Useful commands:${NC}" -echo "View service status: sudo systemctl status firefighter" -echo "View logs: sudo journalctl -u firefighter -f" -echo "Restart service: sudo systemctl restart firefighter" -echo "Stop service: sudo systemctl stop firefighter" \ No newline at end of file diff --git a/gunicorn.pid b/gunicorn.pid new file mode 100644 index 0000000..d1041cf --- /dev/null +++ b/gunicorn.pid @@ -0,0 +1 @@ +18759 diff --git a/gunicorn_config.py b/gunicorn_config.py new file mode 100644 index 0000000..d1446f5 --- /dev/null +++ b/gunicorn_config.py @@ -0,0 +1,77 @@ +import multiprocessing +import os + +# Server socket +bind = "0.0.0.0:5042" # Same port as in your app.py +backlog = 2048 + +# Worker processes +workers = multiprocessing.cpu_count() * 2 + 1 # Recommended formula for worker count +worker_class = "uvicorn.workers.UvicornWorker" # Required for FastAPI +worker_connections = 1000 +timeout = 30 +keepalive = 2 + +# Logging +accesslog = "logs/access.log" +errorlog = "logs/error.log" +loglevel = "info" + +# Process naming +proc_name = "fire_fighter_api" + +# SSL (uncomment and configure if using HTTPS) +# keyfile = "path/to/keyfile" +# certfile = "path/to/certfile" + +# Server mechanics +daemon = False +pidfile = "gunicorn.pid" +umask = 0 +user = None +group = None +tmp_upload_dir = None + +# Server hooks +def on_starting(server): + """ + Server startup hook + """ + # Create logs directory if it doesn't exist + os.makedirs("logs", exist_ok=True) + +def post_fork(server, worker): + """ + Worker initialization hook + """ + server.log.info("Worker spawned (pid: %s)", worker.pid) + +def pre_fork(server, worker): + """ + Pre-fork hook + """ + pass + +def pre_exec(server): + """ + Pre-exec hook + """ + server.log.info("Forked child, re-executing.") + +def when_ready(server): + """ + Server ready hook + """ + server.log.info("Server is ready. Spawning workers") + +def worker_int(worker): + """ + Worker interrupt hook + """ + worker.log.info("worker received INT or QUIT signal") + +def worker_abort(worker): + """ + Worker abort hook + """ + worker.log.info("worker received SIGABRT signal") \ No newline at end of file diff --git a/server_deploy.sh b/server_deploy.sh new file mode 100644 index 0000000..0bb1fe8 --- /dev/null +++ b/server_deploy.sh @@ -0,0 +1,215 @@ +#!/bin/bash + +# Configuration +REPO_URL="http://owusu:890eccfcea010beb94a0adba246aaf9258330b70@23.29.118.76:3000/owusu/ds-fire-fighter.git" +APP_DIR="/home/owusu/ds-fire-fighter" +BRANCH="main" +PYTHON_VERSION="3.11" +WORKERS=4 +THREADS=2 +TIMEOUT=120 +MAX_REQUESTS=1000 +MAX_REQUESTS_JITTER=50 +DEBUG_MODE=true + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Logging function +log() { + local level=$1 + local message=$2 + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + case $level in + "INFO") + echo -e "${BLUE}[$timestamp] INFO: $message${NC}" + ;; + "SUCCESS") + echo -e "${GREEN}[$timestamp] SUCCESS: $message${NC}" + ;; + "WARNING") + echo -e "${YELLOW}[$timestamp] WARNING: $message${NC}" + ;; + "ERROR") + echo -e "${RED}[$timestamp] ERROR: $message${NC}" + ;; + esac +} + +# Debug logging function +debug_log() { + if [ "$DEBUG_MODE" = true ]; then + echo -e "${YELLOW}[DEBUG] $1${NC}" + fi +} + +# Error handling +set -e +trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG +trap 'if [ $? -ne 0 ]; then log "ERROR" "Command failed: $last_command"; exit 1; fi' EXIT + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Function to install Python 3.11 +install_python() { + log "INFO" "Installing Python ${PYTHON_VERSION}..." + sudo apt-get update + sudo apt-get install -y software-properties-common + sudo add-apt-repository -y ppa:deadsnakes/ppa + sudo apt-get update + sudo apt-get install -y python${PYTHON_VERSION} python${PYTHON_VERSION}-venv python${PYTHON_VERSION}-dev +} + +# Function to setup git repository +setup_repo() { + if [ ! -d "$APP_DIR" ]; then + log "INFO" "Cloning repository..." + git clone $REPO_URL $APP_DIR + else + log "INFO" "Updating repository..." + cd $APP_DIR + git fetch origin + git reset --hard origin/$BRANCH + git clean -fd + fi +} + +# Function to setup virtual environment +setup_venv() { + log "INFO" "Setting up virtual environment..." + if [ ! -d "$APP_DIR/venv" ]; then + python${PYTHON_VERSION} -m venv $APP_DIR/venv + fi + source $APP_DIR/venv/bin/activate + pip install --upgrade pip + pip install -r $APP_DIR/requirements.txt +} + +# Function to create gunicorn config +create_gunicorn_config() { + log "INFO" "Creating Gunicorn configuration..." + cat > $APP_DIR/gunicorn_config.py << EOL +import multiprocessing +import os + +# Server socket +bind = "0.0.0.0:5042" +backlog = 2048 + +# Worker processes +workers = ${WORKERS} +worker_class = "uvicorn.workers.UvicornWorker" +worker_connections = 1000 +timeout = ${TIMEOUT} +keepalive = 2 + +# Process naming +proc_name = "firefighter" +pythonpath = "." + +# Logging +accesslog = "logs/access.log" +errorlog = "logs/error.log" +loglevel = "info" + +# Server mechanics +daemon = False +pidfile = "gunicorn.pid" +umask = 0 +user = None +group = None +tmp_upload_dir = None + +# Worker lifecycle +max_requests = ${MAX_REQUESTS} +max_requests_jitter = ${MAX_REQUESTS_JITTER} +graceful_timeout = 30 +preload_app = True + +# Debug +reload = False +reload_engine = "auto" +spew = False + +# Server mechanics +check_config = False +preload_app = True +EOL +} + +# Function to setup systemd service +setup_service() { + log "INFO" "Setting up systemd service..." + sudo tee /etc/systemd/system/firefighter.service << EOL +[Unit] +Description=Fire Fighter Interview API +After=network.target + +[Service] +User=$USER +WorkingDirectory=$APP_DIR +Environment="PATH=$APP_DIR/venv/bin" +Environment="PYTHONPATH=$APP_DIR" +ExecStart=$APP_DIR/start.sh +Restart=always +RestartSec=5 +StartLimitInterval=0 + +[Install] +WantedBy=multi-user.target +EOL + + sudo systemctl daemon-reload + sudo systemctl enable firefighter +} + +# Main deployment process +main() { + log "INFO" "Starting deployment process..." + + # Check and install Python if needed + if ! command_exists python${PYTHON_VERSION}; then + install_python + fi + + # Setup repository + setup_repo + + # Create logs directory + mkdir -p $APP_DIR/logs + + # Setup virtual environment and install dependencies + setup_venv + + # Create gunicorn config + create_gunicorn_config + + # Setup and start service + setup_service + sudo systemctl restart firefighter + + log "SUCCESS" "Deployment completed!" + log "INFO" "Your application should now be running at http://localhost:5042" + + # Print helpful commands + echo -e "\n${BLUE}Useful commands:${NC}" + echo "View service status: sudo systemctl status firefighter" + echo "View logs: sudo journalctl -u firefighter -f" + echo "View application logs: tail -f $APP_DIR/logs/access.log" + echo "View error logs: tail -f $APP_DIR/logs/error.log" + echo "Restart service: sudo systemctl restart firefighter" + echo "Stop service: sudo systemctl stop firefighter" + echo "Start service: sudo systemctl start firefighter" + echo "Deploy new version: ./server_deploy.sh" +} + +# Run main function +main \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..225ce76 --- /dev/null +++ b/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Create logs directory if it doesn't exist +mkdir -p logs + +# Start Gunicorn with the configuration +gunicorn -c gunicorn_config.py app:app \ No newline at end of file