# Environment Variables Reference

Complete reference for all environment variables used in ThrillWiki.

## Quick Start

1. Copy the example file: `cp .env.example .env`
2. Edit `.env` with your values
3. Run validation: `python manage.py validate_settings`

## Required Variables

These must be set in all environments.

| Variable | Type | Description |
|----------|------|-------------|
| `SECRET_KEY` | string | Django secret key (minimum 50 characters) |
| `DATABASE_URL` | url | Database connection URL |

## Production-Required Variables

These variables **must** be explicitly set for production deployments. The application will not function correctly in production without them.

> **Important:** See [`.env.example`](../../.env.example) for example values and [`PRODUCTION_CHECKLIST.md`](../PRODUCTION_CHECKLIST.md) for deployment verification steps.

| Variable | Type | Format/Example | Description |
|----------|------|----------------|-------------|
| `DEBUG` | bool | `False` | **Must be `False` in production.** Enables detailed error pages and debug toolbar when `True`. |
| `DJANGO_SETTINGS_MODULE` | string | `config.django.production` | **Must use `config.django.production` for production.** Controls which settings module Django loads. |
| `ALLOWED_HOSTS` | list | `thrillwiki.com,www.thrillwiki.com` | **Required.** Comma-separated list of valid hostnames. No default in production - requests will fail without this. |
| `CSRF_TRUSTED_ORIGINS` | list | `https://thrillwiki.com,https://www.thrillwiki.com` | **Required.** Must include `https://` prefix. CSRF validation fails without proper configuration. |
| `REDIS_URL` | url | `redis://[:password@]host:6379/0` | **Required for caching and sessions.** Production uses Redis for caching, session storage, and Celery broker. |

### Production Settings Behavior

When `DJANGO_SETTINGS_MODULE=config.django.production`:
- `DEBUG` is always `False`
- `ALLOWED_HOSTS` has **no default** - must be explicitly set
- `CSRF_TRUSTED_ORIGINS` has **no default** - must be explicitly set
- SSL redirect is enforced (`SECURE_SSL_REDIRECT=True`)
- Secure cookies are enforced (`SESSION_COOKIE_SECURE=True`, `CSRF_COOKIE_SECURE=True`)
- HSTS is enabled with 1-year max-age
- Redis cache is required (application will fail if `REDIS_URL` is not set)

### Validation

Run the following to validate your production configuration:

```bash
DJANGO_SETTINGS_MODULE=config.django.production python manage.py check --deploy
```

## Core Django Settings

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `DEBUG` | bool | `True` | Enable debug mode |
| `DJANGO_SETTINGS_MODULE` | string | `config.django.local` | Settings module to use |
| `ALLOWED_HOSTS` | list | `localhost,127.0.0.1` | Comma-separated allowed hosts |
| `CSRF_TRUSTED_ORIGINS` | list | `` | Comma-separated trusted origins |

## Database Configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `DATABASE_URL` | url | (required) | Database connection URL |
| `DATABASE_CONN_MAX_AGE` | int | `600` | Connection pool timeout (seconds) |
| `DATABASE_CONNECT_TIMEOUT` | int | `10` | Connection timeout (seconds) |
| `DATABASE_STATEMENT_TIMEOUT` | int | `30000` | Query timeout (milliseconds) |
| `DATABASE_READ_REPLICA_URL` | url | `` | Optional read replica URL |

### Database URL Formats

```bash
# PostgreSQL with PostGIS
DATABASE_URL=postgis://user:password@host:5432/dbname

# PostgreSQL without PostGIS
DATABASE_URL=postgres://user:password@host:5432/dbname

# SQLite (development only)
DATABASE_URL=sqlite:///path/to/db.sqlite3
```

## GeoDjango Settings

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `GDAL_LIBRARY_PATH` | path | `/opt/homebrew/lib/libgdal.dylib` | Path to GDAL library |
| `GEOS_LIBRARY_PATH` | path | `/opt/homebrew/lib/libgeos_c.dylib` | Path to GEOS library |

### Platform-Specific Paths

```bash
# macOS (Homebrew)
GDAL_LIBRARY_PATH=/opt/homebrew/lib/libgdal.dylib
GEOS_LIBRARY_PATH=/opt/homebrew/lib/libgeos_c.dylib

# Linux (Ubuntu/Debian)
GDAL_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgdal.so
GEOS_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgeos_c.so
```

## Cache Configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `REDIS_URL` | url | `redis://127.0.0.1:6379/1` | Redis connection URL |
| `REDIS_SESSIONS_URL` | url | (from REDIS_URL) | Redis URL for sessions |
| `REDIS_API_URL` | url | (from REDIS_URL) | Redis URL for API cache |
| `REDIS_MAX_CONNECTIONS` | int | `100` | Maximum Redis connections |
| `REDIS_CONNECTION_TIMEOUT` | int | `20` | Redis connection timeout |
| `REDIS_IGNORE_EXCEPTIONS` | bool | `True` | Graceful degradation |
| `CACHE_MIDDLEWARE_SECONDS` | int | `300` | Cache middleware timeout |
| `CACHE_MIDDLEWARE_KEY_PREFIX` | string | `thrillwiki` | Cache key prefix |
| `CACHE_KEY_PREFIX` | string | `thrillwiki` | Default cache key prefix |

## Email Configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `EMAIL_BACKEND` | string | `django_forwardemail.backends.ForwardEmailBackend` | Email backend class |
| `SERVER_EMAIL` | email | `django_webmaster@thrillwiki.com` | Server email address |
| `DEFAULT_FROM_EMAIL` | string | `ThrillWiki <noreply@thrillwiki.com>` | Default from address |
| `EMAIL_SUBJECT_PREFIX` | string | `[ThrillWiki]` | Subject prefix |
| `EMAIL_TIMEOUT` | int | `30` | Email operation timeout |

### ForwardEmail Settings

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FORWARD_EMAIL_BASE_URL` | url | `https://api.forwardemail.net` | API base URL |
| `FORWARD_EMAIL_API_KEY` | string | `` | API key |
| `FORWARD_EMAIL_DOMAIN` | string | `` | Sending domain |

### SMTP Settings

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `EMAIL_HOST` | string | `localhost` | SMTP server |
| `EMAIL_PORT` | int | `587` | SMTP port |
| `EMAIL_USE_TLS` | bool | `True` | Use TLS |
| `EMAIL_USE_SSL` | bool | `False` | Use SSL |
| `EMAIL_HOST_USER` | string | `` | SMTP username |
| `EMAIL_HOST_PASSWORD` | string | `` | SMTP password |

## Security Settings

### SSL/HTTPS

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECURE_SSL_REDIRECT` | bool | `False` | Redirect HTTP to HTTPS |
| `SECURE_PROXY_SSL_HEADER` | tuple | `` | Proxy SSL header |

### HSTS (HTTP Strict Transport Security)

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECURE_HSTS_SECONDS` | int | `31536000` | HSTS max-age |
| `SECURE_HSTS_INCLUDE_SUBDOMAINS` | bool | `True` | Include subdomains |
| `SECURE_HSTS_PRELOAD` | bool | `False` | Allow preload |
| `SECURE_REDIRECT_EXEMPT` | list | `` | URLs exempt from redirect |

### Security Headers

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECURE_BROWSER_XSS_FILTER` | bool | `True` | XSS filter header |
| `SECURE_CONTENT_TYPE_NOSNIFF` | bool | `True` | Nosniff header |
| `X_FRAME_OPTIONS` | string | `DENY` | Frame options |
| `SECURE_REFERRER_POLICY` | string | `strict-origin-when-cross-origin` | Referrer policy |
| `SECURE_CROSS_ORIGIN_OPENER_POLICY` | string | `same-origin` | COOP header |

### Cookie Security

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SESSION_COOKIE_SECURE` | bool | `False` | HTTPS-only session cookie |
| `SESSION_COOKIE_HTTPONLY` | bool | `True` | No JS access |
| `SESSION_COOKIE_SAMESITE` | string | `Lax` | SameSite attribute |
| `SESSION_COOKIE_AGE` | int | `3600` | Cookie age (seconds) |
| `CSRF_COOKIE_SECURE` | bool | `False` | HTTPS-only CSRF cookie |
| `CSRF_COOKIE_HTTPONLY` | bool | `True` | No JS access |
| `CSRF_COOKIE_SAMESITE` | string | `Lax` | SameSite attribute |

### Cloudflare Turnstile

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `TURNSTILE_SITE_KEY` | string | `` | Turnstile site key |
| `TURNSTILE_SECRET_KEY` | string | `` | Turnstile secret key |
| `TURNSTILE_VERIFY_URL` | url | `https://challenges.cloudflare.com/turnstile/v0/siteverify` | Verification URL |

## API Configuration

### CORS

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `CORS_ALLOWED_ORIGINS` | list | `` | Allowed origins |
| `CORS_ALLOW_ALL_ORIGINS` | bool | `False` | Allow all origins |

### Rate Limiting

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `API_RATE_LIMIT_PER_MINUTE` | int | `60` | Requests per minute |
| `API_RATE_LIMIT_PER_HOUR` | int | `1000` | Requests per hour |
| `API_RATE_LIMIT_ANON_PER_MINUTE` | int | `60` | Anonymous rate limit |
| `API_RATE_LIMIT_USER_PER_HOUR` | int | `1000` | Authenticated rate limit |

### Pagination

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `API_PAGE_SIZE` | int | `20` | Default page size |
| `API_MAX_PAGE_SIZE` | int | `100` | Maximum page size |
| `API_VERSION` | string | `1.0.0` | API version string |

## JWT Configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `JWT_ACCESS_TOKEN_LIFETIME_MINUTES` | int | `15` | Access token lifetime |
| `JWT_REFRESH_TOKEN_LIFETIME_DAYS` | int | `7` | Refresh token lifetime |
| `JWT_ISSUER` | string | `thrillwiki` | Token issuer |

## Cloudflare Images

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `CLOUDFLARE_IMAGES_ACCOUNT_ID` | string | `` | Account ID |
| `CLOUDFLARE_IMAGES_API_TOKEN` | string | `` | API token |
| `CLOUDFLARE_IMAGES_ACCOUNT_HASH` | string | `` | Account hash |
| `CLOUDFLARE_IMAGES_WEBHOOK_SECRET` | string | `` | Webhook secret |
| `CLOUDFLARE_IMAGES_DEFAULT_VARIANT` | string | `public` | Default variant |
| `CLOUDFLARE_IMAGES_UPLOAD_TIMEOUT` | int | `300` | Upload timeout |
| `CLOUDFLARE_IMAGES_CLEANUP_HOURS` | int | `24` | Cleanup interval |
| `CLOUDFLARE_IMAGES_MAX_FILE_SIZE` | int | `10485760` | Max file size (bytes) |
| `CLOUDFLARE_IMAGES_REQUIRE_SIGNED_URLS` | bool | `False` | Require signed URLs |

## Road Trip Service

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ROADTRIP_USER_AGENT` | string | `ThrillWiki/1.0` | OSM API user agent |
| `ROADTRIP_CACHE_TIMEOUT` | int | `86400` | Geocoding cache timeout |
| `ROADTRIP_ROUTE_CACHE_TIMEOUT` | int | `21600` | Route cache timeout |
| `ROADTRIP_MAX_REQUESTS_PER_SECOND` | int | `1` | Rate limit |
| `ROADTRIP_REQUEST_TIMEOUT` | int | `10` | Request timeout |
| `ROADTRIP_MAX_RETRIES` | int | `3` | Max retries |
| `ROADTRIP_BACKOFF_FACTOR` | int | `2` | Retry backoff |

## Logging Configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `LOG_DIR` | path | `logs` | Log directory |
| `ROOT_LOG_LEVEL` | string | `INFO` | Root log level |
| `DJANGO_LOG_LEVEL` | string | `WARNING` | Django log level |
| `DB_LOG_LEVEL` | string | `WARNING` | Database log level |
| `APP_LOG_LEVEL` | string | `INFO` | Application log level |
| `PERFORMANCE_LOG_LEVEL` | string | `INFO` | Performance log level |
| `QUERY_LOG_LEVEL` | string | `WARNING` | Query log level |
| `NPLUSONE_LOG_LEVEL` | string | `WARNING` | N+1 detection level |
| `REQUEST_LOG_LEVEL` | string | `INFO` | Request log level |
| `CELERY_LOG_LEVEL` | string | `INFO` | Celery log level |
| `CONSOLE_LOG_LEVEL` | string | `INFO` | Console output level |
| `FILE_LOG_LEVEL` | string | `INFO` | File output level |
| `FILE_LOG_FORMATTER` | string | `json` | File log format |

## Monitoring

### Sentry

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SENTRY_DSN` | url | `` | Sentry DSN |
| `SENTRY_ENVIRONMENT` | string | `production` | Sentry environment |
| `SENTRY_TRACES_SAMPLE_RATE` | float | `0.1` | Trace sampling rate |

### Health Checks

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `HEALTH_CHECK_DISK_USAGE_MAX` | int | `90` | Max disk usage % |
| `HEALTH_CHECK_MEMORY_MIN` | int | `100` | Min available memory MB |

## Feature Flags

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ENABLE_DEBUG_TOOLBAR` | bool | `True` | Enable debug toolbar |
| `ENABLE_SILK_PROFILER` | bool | `False` | Enable Silk profiler |
| `TEMPLATES_ENABLED` | bool | `True` | Enable Django templates |
| `AUTOCOMPLETE_BLOCK_UNAUTHENTICATED` | bool | `False` | Require auth for autocomplete |

## File Upload Settings

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FILE_UPLOAD_MAX_MEMORY_SIZE` | int | `2621440` | Max in-memory upload (bytes) |
| `DATA_UPLOAD_MAX_MEMORY_SIZE` | int | `10485760` | Max request size (bytes) |
| `DATA_UPLOAD_MAX_NUMBER_FIELDS` | int | `1000` | Max form fields |

## WhiteNoise Settings

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `WHITENOISE_COMPRESSION_QUALITY` | int | `90` | Compression quality |
| `WHITENOISE_MAX_AGE` | int | `31536000` | Cache max-age |
| `WHITENOISE_MANIFEST_STRICT` | bool | `False` | Strict manifest mode |

## Secret Management

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECRET_ROTATION_ENABLED` | bool | `False` | Enable rotation checks |
| `SECRET_KEY_VERSION` | string | `1` | Current secret version |
| `SECRET_EXPIRY_WARNING_DAYS` | int | `30` | Warning threshold |
| `PASSWORD_MIN_LENGTH` | int | `8` | Minimum password length |

## Celery Configuration

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `CELERY_TASK_ALWAYS_EAGER` | bool | `False` | Run tasks synchronously |
| `CELERY_TASK_EAGER_PROPAGATES` | bool | `False` | Propagate task errors |

## Third-Party Integrations

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FRONTEND_DOMAIN` | url | `https://thrillwiki.com` | Frontend URL |
| `LOGIN_REDIRECT_URL` | path | `/` | Post-login redirect |
| `ACCOUNT_LOGOUT_REDIRECT_URL` | path | `/` | Post-logout redirect |
| `ACCOUNT_EMAIL_VERIFICATION` | string | `mandatory` | Email verification mode |
