Skip to content

Commit 35a6e4a

Browse files
committed
feat: add production-ready configurations, distributed locking, storage backends, and metrics
- Added `.env.prod.example` for production environment configuration - Introduced distributed locking layer (Redis and file-based implementations) - Added abstraction for storage backends (local, S3, MinIO support) - Implemented Prometheus metrics for monitoring performance - Added database indexes to optimize paste queries - Enhanced exception handling with new custom exception classes
1 parent df5abfd commit 35a6e4a

36 files changed

+3301
-228
lines changed

.env.example

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
# DevBin Backend Environment Configuration
22

3+
# Environment (dev, staging, prod)
4+
APP_ENVIRONMENT=dev
5+
36
# Server Configuration
47
APP_PORT=8000
58
APP_HOST=0.0.0.0
69
APP_WORKERS=1
710
APP_RELOAD=false
811
APP_DEBUG=false
912

13+
# Logging Configuration
14+
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
15+
APP_LOG_LEVEL=INFO
16+
# Log format: text (human-readable) or json (structured, for log aggregation)
17+
APP_LOG_FORMAT=text
18+
1019
# Database Configuration
1120
APP_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/devbin
1221

@@ -34,22 +43,61 @@ APP_BASE_FOLDER_PATH=./files
3443
APP_MIN_STORAGE_MB=1024
3544
APP_KEEP_DELETED_PASTES_TIME_HOURS=336
3645

46+
# Storage Configuration
47+
# Storage backend: local, s3, or minio (default: local)
48+
APP_STORAGE_TYPE=local
49+
50+
# S3 Configuration (when STORAGE_TYPE=s3)
51+
# APP_S3_BUCKET_NAME=devbin-pastes
52+
# APP_S3_REGION=us-east-1
53+
# APP_S3_ACCESS_KEY=your_access_key
54+
# APP_S3_SECRET_KEY=your_secret_key
55+
# APP_S3_ENDPOINT_URL= # Optional: custom S3 endpoint URL
56+
57+
# MinIO Configuration (when STORAGE_TYPE=minio)
58+
# APP_S3_BUCKET_NAME=devbin-pastes
59+
# APP_MINIO_ENDPOINT=minio:9000
60+
# APP_MINIO_ACCESS_KEY=minioadmin
61+
# APP_MINIO_SECRET_KEY=minioadmin
62+
# APP_MINIO_SECURE=true
63+
3764
# Compression settings
65+
# Compression is most effective for content >= 2KB (achieves 30-40% compression ratio)
66+
# Smaller content compresses poorly and wastes CPU cycles
3867
APP_COMPRESSION_ENABLED=true
39-
APP_COMPRESSION_THRESHOLD_BYTES=512
68+
APP_COMPRESSION_THRESHOLD_BYTES=2048
4069
APP_COMPRESSION_LEVEL=6
4170

4271
# Caching
72+
# Cache backend: memory (default) or redis
73+
APP_CACHE_TYPE=memory
4374
APP_CACHE_SIZE_LIMIT=1000
4475
APP_CACHE_TTL=300
4576

77+
# Redis Configuration (when CACHE_TYPE=redis or LOCK_TYPE=redis)
78+
# APP_REDIS_HOST=localhost
79+
# APP_REDIS_PORT=6379
80+
# APP_REDIS_DB=0
81+
# APP_REDIS_PASSWORD= # Optional password
82+
83+
# Distributed Locking
84+
# Lock backend: file (default) or redis
85+
APP_LOCK_TYPE=file
86+
4687
# Privacy Settings
4788
APP_SAVE_USER_AGENT=false
4889
APP_SAVE_IP_ADDRESS=false
4990

5091
# Optional: Rate limit bypass token for trusted apps
5192
# APP_BYPASS_TOKEN=your_secret_bypass_token_here
5293

94+
# Metrics Authentication
95+
# Optional: Secure the Prometheus /metrics endpoint with Bearer token authentication
96+
# If not set, metrics endpoint remains publicly accessible
97+
# Production deployments should set this to a strong random token
98+
# APP_METRICS_TOKEN=your_secure_random_token_here
99+
# Example generation: openssl rand -hex 32
100+
53101
# Frontend configurations
54102
API_BASE_URL=http://devbin:8000
55103
PORT=3000

backend/.env.example

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
# DevBin Backend Environment Configuration
22

3+
# Environment (dev, staging, prod)
4+
APP_ENVIRONMENT=dev
5+
36
# Server Configuration
47
APP_PORT=8000
58
APP_HOST=0.0.0.0
69
APP_WORKERS=1
710
APP_RELOAD=false
811
APP_DEBUG=false
912

13+
# Logging Configuration
14+
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
15+
APP_LOG_LEVEL=INFO
16+
# Log format: text (human-readable) or json (structured, for log aggregation)
17+
APP_LOG_FORMAT=text
18+
1019
# Database Configuration
1120
APP_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/devbin
1221

@@ -34,18 +43,57 @@ APP_BASE_FOLDER_PATH=./files
3443
APP_MIN_STORAGE_MB=1024
3544
APP_KEEP_DELETED_PASTES_TIME_HOURS=336
3645

46+
# Storage Configuration
47+
# Storage backend: local, s3, or minio (default: local)
48+
APP_STORAGE_TYPE=local
49+
50+
# S3 Configuration (when STORAGE_TYPE=s3)
51+
# APP_S3_BUCKET_NAME=devbin-pastes
52+
# APP_S3_REGION=us-east-1
53+
# APP_S3_ACCESS_KEY=your_access_key
54+
# APP_S3_SECRET_KEY=your_secret_key
55+
# APP_S3_ENDPOINT_URL= # Optional: custom S3 endpoint URL
56+
57+
# MinIO Configuration (when STORAGE_TYPE=minio)
58+
# APP_S3_BUCKET_NAME=devbin-pastes
59+
# APP_MINIO_ENDPOINT=minio:9000
60+
# APP_MINIO_ACCESS_KEY=minioadmin
61+
# APP_MINIO_SECRET_KEY=minioadmin
62+
# APP_MINIO_SECURE=true
63+
3764
# Compression settings
65+
# Compression is most effective for content >= 2KB (achieves 30-40% compression ratio)
66+
# Smaller content compresses poorly and wastes CPU cycles
3867
APP_COMPRESSION_ENABLED=true
39-
APP_COMPRESSION_THRESHOLD_BYTES=512
68+
APP_COMPRESSION_THRESHOLD_BYTES=2048
4069
APP_COMPRESSION_LEVEL=6
4170

4271
# Caching
72+
# Cache backend: memory (default) or redis
73+
APP_CACHE_TYPE=memory
4374
APP_CACHE_SIZE_LIMIT=1000
4475
APP_CACHE_TTL=300
4576

77+
# Redis Configuration (when CACHE_TYPE=redis or LOCK_TYPE=redis)
78+
# APP_REDIS_HOST=localhost
79+
# APP_REDIS_PORT=6379
80+
# APP_REDIS_DB=0
81+
# APP_REDIS_PASSWORD= # Optional password
82+
83+
# Distributed Locking
84+
# Lock backend: file (default) or redis
85+
APP_LOCK_TYPE=file
86+
4687
# Privacy Settings
4788
APP_SAVE_USER_AGENT=false
4889
APP_SAVE_IP_ADDRESS=false
4990

5091
# Optional: Rate limit bypass token for trusted apps
5192
# APP_BYPASS_TOKEN=your_secret_bypass_token_here
93+
94+
# Metrics Authentication
95+
# Optional: Secure the Prometheus /metrics endpoint with Bearer token authentication
96+
# If not set, metrics endpoint remains publicly accessible
97+
# Production deployments should set this to a strong random token
98+
# APP_METRICS_TOKEN=your_secure_random_token_here
99+
# Example generation: openssl rand -hex 32

backend/.env.prod.example

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# DevBin Backend - Production Environment Configuration
2+
# Copy this file to .env and configure with your production values
3+
4+
# Environment - MUST be 'prod' for production
5+
APP_ENVIRONMENT=prod
6+
7+
# Server Configuration
8+
APP_PORT=8000
9+
APP_HOST=0.0.0.0
10+
# Set to auto-scale based on CPU cores (recommended for production)
11+
APP_WORKERS=true
12+
# Disable auto-reload in production for stability
13+
APP_RELOAD=false
14+
# Disable debug mode in production (REQUIRED)
15+
APP_DEBUG=false
16+
17+
# Logging Configuration
18+
# Use INFO or WARNING in production to reduce log volume
19+
APP_LOG_LEVEL=INFO
20+
# Use JSON format for structured logging in production (RECOMMENDED)
21+
APP_LOG_FORMAT=json
22+
23+
# Database Configuration
24+
# Use strong credentials and restrict network access
25+
APP_DATABASE_URL=postgresql+asyncpg://devbin_user:CHANGE_ME_STRONG_PASSWORD@db-server:5432/devbin_prod
26+
27+
# Security - HTTPS
28+
# Enable HTTPS redirect if SSL is terminated at the application level
29+
# Keep false if using reverse proxy (nginx, traefik, caddy, etc.)
30+
APP_ENFORCE_HTTPS=false
31+
32+
# Security - CORS
33+
# Specify exact allowed domains (REQUIRED in production)
34+
APP_CORS_DOMAINS=["https://devbin.example.com","https://app.devbin.example.com"]
35+
# Wildcard MUST be disabled in production
36+
APP_ALLOW_CORS_WILDCARD=false
37+
38+
# Trusted Hosts (for X-Forwarded-For header)
39+
# Add your load balancer/proxy IPs
40+
APP_TRUSTED_HOSTS=["10.0.0.0/8","172.16.0.0/12"]
41+
42+
# Paste Configuration
43+
APP_MAX_CONTENT_LENGTH=10000
44+
APP_BASE_FOLDER_PATH=/var/lib/devbin/files
45+
# Higher threshold for production storage
46+
APP_MIN_STORAGE_MB=10240
47+
# Retention policy: 336 hours = 14 days
48+
APP_KEEP_DELETED_PASTES_TIME_HOURS=336
49+
50+
# Storage Configuration
51+
# Options: local, s3, minio (s3/minio recommended for production)
52+
APP_STORAGE_TYPE=s3
53+
54+
# S3 Configuration (when STORAGE_TYPE=s3)
55+
APP_S3_BUCKET_NAME=devbin-prod-pastes
56+
APP_S3_REGION=us-east-1
57+
APP_S3_ACCESS_KEY=AKIAXXXXXXXXXXXXXXXX
58+
APP_S3_SECRET_KEY=CHANGE_ME_SECRET_KEY
59+
# APP_S3_ENDPOINT_URL= # Optional: custom S3 endpoint URL
60+
61+
# MinIO Configuration (when STORAGE_TYPE=minio)
62+
# APP_S3_BUCKET_NAME=devbin-prod-pastes
63+
# APP_MINIO_ENDPOINT=minio.internal:9000
64+
# APP_MINIO_ACCESS_KEY=CHANGE_ME_ACCESS_KEY
65+
# APP_MINIO_SECRET_KEY=CHANGE_ME_SECRET_KEY
66+
# APP_MINIO_SECURE=true
67+
68+
# Compression settings (optimized for production)
69+
APP_COMPRESSION_ENABLED=true
70+
APP_COMPRESSION_THRESHOLD_BYTES=2048
71+
APP_COMPRESSION_LEVEL=6
72+
73+
# Caching (Redis recommended for production)
74+
APP_CACHE_TYPE=redis
75+
APP_CACHE_SIZE_LIMIT=10000
76+
APP_CACHE_TTL=300
77+
78+
# Redis Configuration (for cache and distributed locking)
79+
APP_REDIS_HOST=redis.internal
80+
APP_REDIS_PORT=6379
81+
APP_REDIS_DB=0
82+
APP_REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD
83+
84+
# Distributed Locking (Redis recommended for multi-instance deployments)
85+
APP_LOCK_TYPE=redis
86+
87+
# Privacy Settings (configure based on your privacy policy)
88+
APP_SAVE_USER_AGENT=false
89+
APP_SAVE_IP_ADDRESS=false
90+
91+
# Optional: Rate limit bypass token for trusted apps/monitoring
92+
# APP_BYPASS_TOKEN=CHANGE_ME_SECURE_RANDOM_TOKEN_HERE
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""add_paste_indexes
2+
3+
Revision ID: 4e57d32ab2ac
4+
Revises: 6c5a2c764fb8
5+
Create Date: 2025-12-25 20:15:09.567249
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '4e57d32ab2ac'
16+
down_revision: Union[str, Sequence[str], None] = '6c5a2c764fb8'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# Add indexes to optimize paste queries
24+
op.create_index('idx_pastes_expires_at', 'pastes', ['expires_at'])
25+
op.create_index('idx_pastes_deleted_at', 'pastes', ['deleted_at'])
26+
op.create_index('idx_pastes_created_at', 'pastes', ['created_at'])
27+
28+
29+
def downgrade() -> None:
30+
"""Downgrade schema."""
31+
# Remove indexes
32+
op.drop_index('idx_pastes_created_at', 'pastes')
33+
op.drop_index('idx_pastes_deleted_at', 'pastes')
34+
op.drop_index('idx_pastes_expires_at', 'pastes')

backend/app/api/routes.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,50 @@
22

33
from dependency_injector.wiring import Provide, inject
44
from fastapi import APIRouter, Depends
5+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6+
from prometheus_client import CONTENT_TYPE_LATEST, generate_latest
57
from starlette.requests import Request
8+
from starlette.responses import Response
69

710
from app.api.subroutes.pastes import pastes_route
11+
from app.config import config
812
from app.containers import Container
13+
from app.exceptions import UnauthorizedError
914
from app.ratelimit import limiter
1015
from app.services.health_service import HealthService
1116

1217
router = APIRouter()
1318

1419

20+
# Metrics authentication
21+
metrics_security = HTTPBearer(auto_error=False, description="Metrics access token")
22+
23+
24+
def verify_metrics_token(
25+
credentials: HTTPAuthorizationCredentials | None = Depends(metrics_security)
26+
) -> None:
27+
"""
28+
Verify Bearer token for metrics endpoint.
29+
30+
If METRICS_TOKEN is not configured, endpoint is publicly accessible.
31+
If configured, valid Bearer token is required.
32+
33+
Raises:
34+
UnauthorizedError: If token is missing or invalid when authentication is required
35+
"""
36+
# If no token configured, allow public access
37+
if not config.METRICS_TOKEN:
38+
return
39+
40+
# Token is configured, authentication required
41+
if not credentials:
42+
raise UnauthorizedError("Bearer token required for metrics access")
43+
44+
# Validate token (plain string comparison)
45+
if credentials.credentials != config.METRICS_TOKEN:
46+
raise UnauthorizedError("Invalid metrics token")
47+
48+
1549
@router.get("/health")
1650
@limiter.limit("60/minute")
1751
@inject
@@ -22,4 +56,30 @@ async def health(
2256
return await health_service.check()
2357

2458

59+
@router.get("/ready")
60+
@limiter.limit("60/minute")
61+
@inject
62+
async def ready(
63+
request: Request,
64+
health_service: HealthService = Depends(Provide[Container.health_service]),
65+
):
66+
"""Readiness endpoint for load balancers."""
67+
return await health_service.ready()
68+
69+
70+
@router.get("/metrics")
71+
async def metrics(
72+
_: None = Depends(verify_metrics_token)
73+
):
74+
"""
75+
Prometheus metrics endpoint.
76+
77+
Requires Bearer token authentication if APP_METRICS_TOKEN is configured.
78+
"""
79+
return Response(
80+
content=generate_latest(),
81+
media_type=CONTENT_TYPE_LATEST,
82+
)
83+
84+
2585
router.include_router(pastes_route)

0 commit comments

Comments
 (0)