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
This commit is contained in:
Adolfo Delorenzo 2025-06-03 20:08:19 -06:00
parent c97d025acb
commit e5a274d191
5 changed files with 120 additions and 25 deletions

View File

@ -1,22 +1,73 @@
# Example environment configuration for Talk2Me # Talk2Me Environment Configuration
# Copy this file to .env and update with your actual values # Copy this file to .env and fill in your values
# Flask Configuration # 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 # Server Configuration
UPLOAD_FOLDER=/path/to/secure/upload/folder HOST=0.0.0.0
PORT=5005
# TTS Server Configuration # Database Configuration
TTS_SERVER_URL=http://localhost:5050/v1/audio/speech 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 TTS_API_KEY=your-tts-api-key-here
# CORS Configuration (for production) # Security Configuration
CORS_ORIGINS=https://yourdomain.com,https://app.yourdomain.com JWT_SECRET_KEY=your-jwt-secret-key-here
ADMIN_CORS_ORIGINS=https://admin.yourdomain.com JWT_ACCESS_TOKEN_EXPIRES=3600
JWT_REFRESH_TOKEN_EXPIRES=2592000
# Admin Token (for admin endpoints) # Admin Configuration
ADMIN_TOKEN=your-secure-admin-token-here ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-this-password
ADMIN_EMAIL=admin@example.com
# Optional: GPU Configuration # Rate Limiting
# CUDA_VISIBLE_DEVICES=0 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=

View File

@ -145,10 +145,17 @@ python manage_secrets.py rotate
#### Using Environment Variables #### Using Environment Variables
Create a `.env` file: Create a `.env` file by copying `.env.example`:
```bash
cp .env.example .env
```
Key environment variables:
```env ```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_API_KEY=your-api-key-here
TTS_SERVER_URL=http://localhost:5050/v1/audio/speech TTS_SERVER_URL=http://localhost:5050/v1/audio/speech
ADMIN_TOKEN=your-secure-admin-token ADMIN_TOKEN=your-secure-admin-token
@ -169,6 +176,12 @@ GPU_MEMORY_THRESHOLD_MB=2048
MEMORY_CLEANUP_INTERVAL=30 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 ### Advanced Configuration
#### CORS Settings #### CORS Settings

View File

@ -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 from functools import wraps
import os import os
import logging import logging
@ -483,14 +483,26 @@ def check_system_health():
health['overall'] = 'degraded' health['overall'] = 'degraded'
# Check TTS Server # 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: if tts_server_url:
try: try:
import requests 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: if response.status_code == 200:
health['tts'] = 'healthy' health['tts'] = 'healthy'
health['tts_details'] = response.json() if response.headers.get('content-type') == 'application/json' else {} 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: else:
health['tts'] = 'unhealthy' health['tts'] = 'unhealthy'
health['overall'] = 'degraded' health['overall'] = 'degraded'
@ -522,8 +534,8 @@ def get_tts_status():
} }
# Check configuration # Check configuration
tts_server_url = app.config.get('TTS_SERVER_URL') tts_server_url = current_app.config.get('TTS_SERVER_URL')
tts_api_key = app.config.get('TTS_API_KEY') tts_api_key = current_app.config.get('TTS_API_KEY')
if tts_server_url: if tts_server_url:
tts_info['configured'] = True tts_info['configured'] = True
@ -538,7 +550,11 @@ def get_tts_status():
headers['Authorization'] = f'Bearer {tts_api_key}' headers['Authorization'] = f'Bearer {tts_api_key}'
# Check health endpoint # 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: if response.status_code == 200:
tts_info['status'] = 'healthy' tts_info['status'] = 'healthy'
if response.headers.get('content-type') == 'application/json': if response.headers.get('content-type') == 'application/json':
@ -549,11 +565,16 @@ def get_tts_status():
# Try to get voice list # Try to get voice list
try: 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': if voices_response.status_code == 200 and voices_response.headers.get('content-type') == 'application/json':
voices_data = voices_response.json() voices_data = voices_response.json()
tts_info['details']['available_voices'] = voices_data.get('voices', []) tts_info['details']['available_voices'] = voices_data.get('voices', [])
tts_info['details']['voice_count'] = len(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: except:
pass pass
@ -636,7 +657,7 @@ def stream_updates():
time.sleep(1) time.sleep(1)
return app.response_class( return current_app.response_class(
generate(), generate(),
mimetype='text/event-stream', mimetype='text/event-stream',
headers={ headers={

View File

@ -253,6 +253,11 @@ def init_db(app):
$$ language 'plpgsql'; $$ 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 # Create trigger for user_preferences
db.session.execute(text(""" db.session.execute(text("""
CREATE TRIGGER update_user_preferences_updated_at CREATE TRIGGER update_user_preferences_updated_at
@ -263,6 +268,6 @@ def init_db(app):
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
# Triggers might already exist # Log error but don't fail - database might not support triggers
db.session.rollback() db.session.rollback()
app.logger.debug(f"Database initialization note: {e}") app.logger.debug(f"Database initialization note: {e}")

View File

@ -178,6 +178,11 @@ def upgrade():
$$ language 'plpgsql'; $$ 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 # Create trigger for users table
op.execute(""" op.execute("""
CREATE TRIGGER update_users_updated_at CREATE TRIGGER update_users_updated_at