From e5a274d191e50e3940861a6cc2596a35271f1334 Mon Sep 17 00:00:00 2001 From: Adolfo Delorenzo Date: Tue, 3 Jun 2025 20:08:19 -0600 Subject: [PATCH] Fix TTS server status errors and startup warnings - Fixed 'app is not defined' errors by using current_app - Improved TTS health check to handle missing /health endpoint - Fixed database trigger creation to be idempotent - Added .env.example with all configuration options - Updated README with security configuration instructions --- .env.example | 79 ++++++++++++++++++++++----- README.md | 17 +++++- admin/__init__.py | 37 ++++++++++--- database.py | 7 ++- migrations/add_user_authentication.py | 5 ++ 5 files changed, 120 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index 3e91c8a..6629041 100644 --- a/.env.example +++ b/.env.example @@ -1,22 +1,73 @@ -# Example environment configuration for Talk2Me -# Copy this file to .env and update with your actual values +# Talk2Me Environment Configuration +# Copy this file to .env and fill in your values # Flask Configuration -SECRET_KEY=your-secret-key-here-change-this +FLASK_ENV=development +FLASK_SECRET_KEY=your-secret-key-here-change-in-production +FLASK_DEBUG=False -# Upload Configuration -UPLOAD_FOLDER=/path/to/secure/upload/folder +# Server Configuration +HOST=0.0.0.0 +PORT=5005 -# TTS Server Configuration -TTS_SERVER_URL=http://localhost:5050/v1/audio/speech +# Database Configuration +DATABASE_URL=postgresql://user:password@localhost:5432/talk2me +REDIS_URL=redis://localhost:6379/0 + +# Ollama Configuration +OLLAMA_BASE_URL=http://localhost:11434 +OLLAMA_MODEL=gemma2:9b +OLLAMA_LARGE_MODEL=gemma3:27b + +# TTS Configuration +TTS_SERVER_URL=http://localhost:8000 TTS_API_KEY=your-tts-api-key-here -# CORS Configuration (for production) -CORS_ORIGINS=https://yourdomain.com,https://app.yourdomain.com -ADMIN_CORS_ORIGINS=https://admin.yourdomain.com +# Security Configuration +JWT_SECRET_KEY=your-jwt-secret-key-here +JWT_ACCESS_TOKEN_EXPIRES=3600 +JWT_REFRESH_TOKEN_EXPIRES=2592000 -# Admin Token (for admin endpoints) -ADMIN_TOKEN=your-secure-admin-token-here +# Admin Configuration +ADMIN_USERNAME=admin +ADMIN_PASSWORD=change-this-password +ADMIN_EMAIL=admin@example.com -# Optional: GPU Configuration -# CUDA_VISIBLE_DEVICES=0 \ No newline at end of file +# Rate Limiting +RATE_LIMIT_PER_MINUTE=60 +RATE_LIMIT_PER_HOUR=1000 + +# Session Configuration +SESSION_LIFETIME=86400 +SESSION_CLEANUP_INTERVAL=3600 + +# Logging +LOG_LEVEL=INFO +LOG_FORMAT=json + +# CORS Configuration +CORS_ORIGINS=http://localhost:3000,http://localhost:5005 + +# Feature Flags +ENABLE_ANALYTICS=true +ENABLE_RATE_LIMITING=true +ENABLE_SESSION_MANAGEMENT=true +ENABLE_ERROR_TRACKING=true + +# Performance Settings +MAX_CONTENT_LENGTH=16777216 +REQUEST_TIMEOUT=300 +WHISPER_MODEL=base +WHISPER_DEVICE=auto + +# Email Configuration (Optional) +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your-email@example.com +SMTP_PASSWORD=your-email-password +SMTP_FROM=noreply@example.com + +# External Services (Optional) +SENTRY_DSN= +DATADOG_API_KEY= +NEWRELIC_LICENSE_KEY= \ No newline at end of file diff --git a/README.md b/README.md index 94f9387..dc13969 100644 --- a/README.md +++ b/README.md @@ -145,10 +145,17 @@ python manage_secrets.py rotate #### Using Environment Variables -Create a `.env` file: +Create a `.env` file by copying `.env.example`: + +```bash +cp .env.example .env +``` + +Key environment variables: ```env -# Core Configuration +# Core Configuration (REQUIRED for production) +FLASK_SECRET_KEY=your-secret-key-here-change-in-production # IMPORTANT: Set this for production! TTS_API_KEY=your-api-key-here TTS_SERVER_URL=http://localhost:5050/v1/audio/speech ADMIN_TOKEN=your-secure-admin-token @@ -169,6 +176,12 @@ GPU_MEMORY_THRESHOLD_MB=2048 MEMORY_CLEANUP_INTERVAL=30 ``` +**Important**: Always set `FLASK_SECRET_KEY` to a secure, random value in production. You can generate one using: + +```bash +python -c "import secrets; print(secrets.token_hex(32))" +``` + ### Advanced Configuration #### CORS Settings diff --git a/admin/__init__.py b/admin/__init__.py index 93a74d0..6a414c0 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -1,4 +1,4 @@ -from flask import Blueprint, request, jsonify, render_template, redirect, url_for, session +from flask import Blueprint, request, jsonify, render_template, redirect, url_for, session, current_app from functools import wraps import os import logging @@ -483,14 +483,26 @@ def check_system_health(): health['overall'] = 'degraded' # Check TTS Server - tts_server_url = app.config.get('TTS_SERVER_URL') + tts_server_url = current_app.config.get('TTS_SERVER_URL') if tts_server_url: try: import requests - response = requests.get(f"{tts_server_url}/health", timeout=2) + # Extract base URL from the speech endpoint + base_url = tts_server_url.rsplit('/v1/audio/speech', 1)[0] if '/v1/audio/speech' in tts_server_url else tts_server_url + health_url = f"{base_url}/health" if not tts_server_url.endswith('/health') else tts_server_url + response = requests.get(health_url, timeout=2) if response.status_code == 200: health['tts'] = 'healthy' health['tts_details'] = response.json() if response.headers.get('content-type') == 'application/json' else {} + elif response.status_code == 404: + # Try voices endpoint as fallback + voices_url = f"{base_url}/voices" if base_url else f"{tts_server_url.rsplit('/speech', 1)[0]}/voices" + voices_response = requests.get(voices_url, timeout=2) + if voices_response.status_code == 200: + health['tts'] = 'healthy' + else: + health['tts'] = 'unhealthy' + health['overall'] = 'degraded' else: health['tts'] = 'unhealthy' health['overall'] = 'degraded' @@ -522,8 +534,8 @@ def get_tts_status(): } # Check configuration - tts_server_url = app.config.get('TTS_SERVER_URL') - tts_api_key = app.config.get('TTS_API_KEY') + tts_server_url = current_app.config.get('TTS_SERVER_URL') + tts_api_key = current_app.config.get('TTS_API_KEY') if tts_server_url: tts_info['configured'] = True @@ -538,7 +550,11 @@ def get_tts_status(): headers['Authorization'] = f'Bearer {tts_api_key}' # Check health endpoint - response = requests.get(f"{tts_server_url}/health", headers=headers, timeout=3) + # Extract base URL from the speech endpoint + base_url = tts_server_url.rsplit('/v1/audio/speech', 1)[0] if '/v1/audio/speech' in tts_server_url else tts_server_url + health_url = f"{base_url}/health" if not tts_server_url.endswith('/health') else tts_server_url + + response = requests.get(health_url, headers=headers, timeout=3) if response.status_code == 200: tts_info['status'] = 'healthy' if response.headers.get('content-type') == 'application/json': @@ -549,11 +565,16 @@ def get_tts_status(): # Try to get voice list try: - voices_response = requests.get(f"{tts_server_url}/voices", headers=headers, timeout=3) + voices_url = f"{base_url}/voices" if base_url else f"{tts_server_url.rsplit('/speech', 1)[0]}/voices" + voices_response = requests.get(voices_url, headers=headers, timeout=3) if voices_response.status_code == 200 and voices_response.headers.get('content-type') == 'application/json': voices_data = voices_response.json() tts_info['details']['available_voices'] = voices_data.get('voices', []) tts_info['details']['voice_count'] = len(voices_data.get('voices', [])) + # If we can get voices, consider the server healthy even if health endpoint doesn't exist + if tts_info['status'] == 'unhealthy' and response.status_code == 404: + tts_info['status'] = 'healthy' + tts_info['details'].pop('error', None) except: pass @@ -636,7 +657,7 @@ def stream_updates(): time.sleep(1) - return app.response_class( + return current_app.response_class( generate(), mimetype='text/event-stream', headers={ diff --git a/database.py b/database.py index f77b351..97b4975 100644 --- a/database.py +++ b/database.py @@ -253,6 +253,11 @@ def init_db(app): $$ language 'plpgsql'; """)) + # Drop existing trigger if it exists and recreate it + db.session.execute(text(""" + DROP TRIGGER IF EXISTS update_user_preferences_updated_at ON user_preferences; + """)) + # Create trigger for user_preferences db.session.execute(text(""" CREATE TRIGGER update_user_preferences_updated_at @@ -263,6 +268,6 @@ def init_db(app): db.session.commit() except Exception as e: - # Triggers might already exist + # Log error but don't fail - database might not support triggers db.session.rollback() app.logger.debug(f"Database initialization note: {e}") \ No newline at end of file diff --git a/migrations/add_user_authentication.py b/migrations/add_user_authentication.py index 7c0118a..9d52f12 100644 --- a/migrations/add_user_authentication.py +++ b/migrations/add_user_authentication.py @@ -178,6 +178,11 @@ def upgrade(): $$ language 'plpgsql'; """) + # Drop existing trigger if it exists and recreate it + op.execute(""" + DROP TRIGGER IF EXISTS update_users_updated_at ON users; + """) + # Create trigger for users table op.execute(""" CREATE TRIGGER update_users_updated_at