This adds a complete production deployment setup using Gunicorn as the WSGI server, replacing Flask's development server. Key components: - Gunicorn configuration with optimized worker settings - Support for sync, threaded, and async (gevent) workers - Automatic worker recycling to prevent memory leaks - Increased timeouts for audio processing - Production-ready logging and monitoring Deployment options: 1. Docker/Docker Compose for containerized deployment 2. Systemd service for traditional deployment 3. Nginx reverse proxy configuration 4. SSL/TLS support Production features: - wsgi.py entry point for WSGI servers - gunicorn_config.py with production settings - Dockerfile with multi-stage build - docker-compose.yml with full stack (Redis, PostgreSQL) - nginx.conf with caching and security headers - systemd service with security hardening - deploy.sh automated deployment script Configuration: - .env.production template with all settings - Support for environment-based configuration - Separate requirements-prod.txt - Prometheus metrics endpoint (/metrics) Monitoring: - Health check endpoints for liveness/readiness - Prometheus-compatible metrics - Structured logging - Memory usage tracking - Request counting Security: - Non-root user in Docker - Systemd security restrictions - Nginx security headers - File permission hardening - Resource limits Documentation: - Comprehensive PRODUCTION_DEPLOYMENT.md - Scaling strategies - Performance tuning guide - Troubleshooting section Also fixed memory_manager.py GC stats collection error. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
208 lines
5.2 KiB
Bash
Executable File
208 lines
5.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Production deployment script for Talk2Me
|
|
|
|
set -e # Exit on error
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
APP_NAME="talk2me"
|
|
APP_USER="talk2me"
|
|
APP_DIR="/opt/talk2me"
|
|
VENV_DIR="$APP_DIR/venv"
|
|
LOG_DIR="/var/log/talk2me"
|
|
PID_FILE="/var/run/talk2me.pid"
|
|
WORKERS=${WORKERS:-4}
|
|
|
|
# Functions
|
|
print_status() {
|
|
echo -e "${GREEN}[INFO]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
# Check if running as root
|
|
if [[ $EUID -ne 0 ]]; then
|
|
print_error "This script must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
# Create application user if doesn't exist
|
|
if ! id "$APP_USER" &>/dev/null; then
|
|
print_status "Creating application user: $APP_USER"
|
|
useradd -m -s /bin/bash $APP_USER
|
|
fi
|
|
|
|
# Create directories
|
|
print_status "Creating application directories"
|
|
mkdir -p $APP_DIR $LOG_DIR
|
|
chown -R $APP_USER:$APP_USER $APP_DIR $LOG_DIR
|
|
|
|
# Copy application files
|
|
print_status "Copying application files"
|
|
rsync -av --exclude='venv' --exclude='__pycache__' --exclude='*.pyc' \
|
|
--exclude='logs' --exclude='.git' --exclude='node_modules' \
|
|
./ $APP_DIR/
|
|
|
|
# Create virtual environment
|
|
print_status "Setting up Python virtual environment"
|
|
su - $APP_USER -c "cd $APP_DIR && python3 -m venv $VENV_DIR"
|
|
|
|
# Install dependencies
|
|
print_status "Installing Python dependencies"
|
|
su - $APP_USER -c "cd $APP_DIR && $VENV_DIR/bin/pip install --upgrade pip"
|
|
su - $APP_USER -c "cd $APP_DIR && $VENV_DIR/bin/pip install -r requirements-prod.txt"
|
|
|
|
# Install Whisper model
|
|
print_status "Downloading Whisper model (this may take a while)"
|
|
su - $APP_USER -c "cd $APP_DIR && $VENV_DIR/bin/python -c 'import whisper; whisper.load_model(\"base\")'"
|
|
|
|
# Build frontend assets
|
|
if [ -f "package.json" ]; then
|
|
print_status "Building frontend assets"
|
|
cd $APP_DIR
|
|
npm install
|
|
npm run build
|
|
fi
|
|
|
|
# Create systemd service
|
|
print_status "Creating systemd service"
|
|
cat > /etc/systemd/system/talk2me.service <<EOF
|
|
[Unit]
|
|
Description=Talk2Me Translation Service
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=notify
|
|
User=$APP_USER
|
|
Group=$APP_USER
|
|
WorkingDirectory=$APP_DIR
|
|
Environment="PATH=$VENV_DIR/bin"
|
|
Environment="FLASK_ENV=production"
|
|
Environment="UPLOAD_FOLDER=/tmp/talk2me_uploads"
|
|
Environment="LOGS_DIR=$LOG_DIR"
|
|
ExecStart=$VENV_DIR/bin/gunicorn --config gunicorn_config.py wsgi:application
|
|
ExecReload=/bin/kill -s HUP \$MAINPID
|
|
KillMode=mixed
|
|
TimeoutStopSec=5
|
|
Restart=always
|
|
RestartSec=10
|
|
|
|
# Security settings
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=$LOG_DIR /tmp
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Create nginx configuration
|
|
print_status "Creating nginx configuration"
|
|
cat > /etc/nginx/sites-available/talk2me <<EOF
|
|
server {
|
|
listen 80;
|
|
server_name _; # Replace with your domain
|
|
|
|
# Security headers
|
|
add_header X-Content-Type-Options nosniff;
|
|
add_header X-Frame-Options DENY;
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
|
|
|
# File upload size limit
|
|
client_max_body_size 50M;
|
|
client_body_buffer_size 1M;
|
|
|
|
# Timeouts for long audio processing
|
|
proxy_connect_timeout 120s;
|
|
proxy_send_timeout 120s;
|
|
proxy_read_timeout 120s;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5005;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
proxy_cache_bypass \$http_upgrade;
|
|
|
|
# Don't buffer responses
|
|
proxy_buffering off;
|
|
|
|
# WebSocket support
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
|
|
location /static {
|
|
alias $APP_DIR/static;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# Health check endpoint
|
|
location /health {
|
|
proxy_pass http://127.0.0.1:5005/health;
|
|
access_log off;
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Enable nginx site
|
|
if [ -f /etc/nginx/sites-enabled/default ]; then
|
|
rm /etc/nginx/sites-enabled/default
|
|
fi
|
|
ln -sf /etc/nginx/sites-available/talk2me /etc/nginx/sites-enabled/
|
|
|
|
# Set permissions
|
|
chown -R $APP_USER:$APP_USER $APP_DIR
|
|
|
|
# Reload systemd
|
|
print_status "Reloading systemd"
|
|
systemctl daemon-reload
|
|
|
|
# Start services
|
|
print_status "Starting services"
|
|
systemctl enable talk2me
|
|
systemctl restart talk2me
|
|
systemctl restart nginx
|
|
|
|
# Wait for service to start
|
|
sleep 5
|
|
|
|
# Check service status
|
|
if systemctl is-active --quiet talk2me; then
|
|
print_status "Talk2Me service is running"
|
|
else
|
|
print_error "Talk2Me service failed to start"
|
|
journalctl -u talk2me -n 50
|
|
exit 1
|
|
fi
|
|
|
|
# Test health endpoint
|
|
if curl -s http://localhost:5005/health | grep -q "healthy"; then
|
|
print_status "Health check passed"
|
|
else
|
|
print_error "Health check failed"
|
|
exit 1
|
|
fi
|
|
|
|
print_status "Deployment complete!"
|
|
print_status "Talk2Me is now running at http://$(hostname -I | awk '{print $1}')"
|
|
print_status "Check logs at: $LOG_DIR"
|
|
print_status "Service status: systemctl status talk2me" |