chore: scaffold initial project with env config, docker setup, and quickstart docs
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
Gunicorn Configuration for WebDAV Server
|
||||
Production-grade WSGI server configuration with aiohttp worker
|
||||
"""
|
||||
|
||||
import os
|
||||
import multiprocessing
|
||||
|
||||
# ============================================================================
|
||||
# Server Socket
|
||||
# ============================================================================
|
||||
|
||||
bind = f"{os.getenv('HOST', '0.0.0.0')}:{os.getenv('PORT', '8080')}"
|
||||
backlog = 2048
|
||||
|
||||
# ============================================================================
|
||||
# Worker Processes
|
||||
# ============================================================================
|
||||
|
||||
# Number of worker processes
|
||||
workers = int(os.getenv('WORKERS', multiprocessing.cpu_count() * 2 + 1))
|
||||
|
||||
# Worker class - MUST be aiohttp.GunicornWebWorker for aiohttp
|
||||
worker_class = 'aiohttp.GunicornWebWorker'
|
||||
|
||||
# Worker connections (only for async workers)
|
||||
worker_connections = int(os.getenv('WORKER_CONNECTIONS', 1024))
|
||||
|
||||
# Maximum requests a worker will process before restarting (prevents memory leaks)
|
||||
max_requests = int(os.getenv('MAX_REQUESTS', 10000))
|
||||
max_requests_jitter = int(os.getenv('MAX_REQUESTS_JITTER', 1000))
|
||||
|
||||
# Worker timeout in seconds
|
||||
timeout = int(os.getenv('WORKER_TIMEOUT', 60))
|
||||
|
||||
# Graceful timeout for workers
|
||||
graceful_timeout = int(os.getenv('GRACEFUL_TIMEOUT', 30))
|
||||
|
||||
# Keep-alive timeout
|
||||
keepalive = int(os.getenv('KEEPALIVE_TIMEOUT', 30))
|
||||
|
||||
# ============================================================================
|
||||
# Logging
|
||||
# ============================================================================
|
||||
|
||||
# Access log
|
||||
accesslog = os.getenv('ACCESS_LOG_FILE', './logs/gunicorn_access.log')
|
||||
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
||||
|
||||
# Error log
|
||||
errorlog = os.getenv('ERROR_LOG_FILE', './logs/gunicorn_error.log')
|
||||
|
||||
# Log level
|
||||
loglevel = os.getenv('LOG_LEVEL', 'info').lower()
|
||||
|
||||
# Capture output from workers
|
||||
capture_output = True
|
||||
|
||||
# Enable log rotation
|
||||
logconfig_dict = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'generic': {
|
||||
'format': '%(asctime)s [%(process)d] [%(levelname)s] %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
'class': 'logging.Formatter'
|
||||
},
|
||||
'access': {
|
||||
'format': '%(message)s',
|
||||
'class': 'logging.Formatter'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'generic',
|
||||
'stream': 'ext://sys.stdout'
|
||||
},
|
||||
'error_file': {
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'formatter': 'generic',
|
||||
'filename': errorlog,
|
||||
'maxBytes': 10485760, # 10MB
|
||||
'backupCount': 5
|
||||
},
|
||||
'access_file': {
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'formatter': 'access',
|
||||
'filename': accesslog,
|
||||
'maxBytes': 10485760, # 10MB
|
||||
'backupCount': 5
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'gunicorn.error': {
|
||||
'handlers': ['console', 'error_file'],
|
||||
'level': loglevel.upper(),
|
||||
'propagate': False
|
||||
},
|
||||
'gunicorn.access': {
|
||||
'handlers': ['access_file'],
|
||||
'level': 'INFO',
|
||||
'propagate': False
|
||||
}
|
||||
},
|
||||
'root': {
|
||||
'level': loglevel.upper(),
|
||||
'handlers': ['console', 'error_file']
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Process Naming
|
||||
# ============================================================================
|
||||
|
||||
proc_name = 'webdav_server'
|
||||
|
||||
# ============================================================================
|
||||
# Server Mechanics
|
||||
# ============================================================================
|
||||
|
||||
# Daemon mode (run in background)
|
||||
daemon = os.getenv('DAEMON', 'false').lower() == 'true'
|
||||
|
||||
# PID file
|
||||
pidfile = os.getenv('PID_FILE', './gunicorn.pid')
|
||||
|
||||
# User and group to run workers
|
||||
user = os.getenv('USER', None)
|
||||
group = os.getenv('GROUP', None)
|
||||
|
||||
# Directory to switch to before loading apps
|
||||
chdir = os.getenv('CHDIR', '.')
|
||||
|
||||
# Environment variables to set for workers
|
||||
raw_env = [
|
||||
f"WEBDAV_ENV={os.getenv('WEBDAV_ENV', 'production')}"
|
||||
]
|
||||
|
||||
# ============================================================================
|
||||
# Server Hooks
|
||||
# ============================================================================
|
||||
|
||||
def on_starting(server):
|
||||
"""Called just before the master process is initialized."""
|
||||
server.log.info("Starting WebDAV Server...")
|
||||
|
||||
# Create necessary directories
|
||||
os.makedirs('./logs', exist_ok=True)
|
||||
os.makedirs('./webdav', exist_ok=True)
|
||||
os.makedirs('./backups', exist_ok=True)
|
||||
|
||||
|
||||
def on_reload(server):
|
||||
"""Called to recycle workers during a reload."""
|
||||
server.log.info("Reloading WebDAV Server...")
|
||||
|
||||
|
||||
def when_ready(server):
|
||||
"""Called just after the server is started."""
|
||||
server.log.info(f"WebDAV Server is ready. Listening on: {bind}")
|
||||
server.log.info(f"Workers: {workers}")
|
||||
server.log.info(f"Worker class: {worker_class}")
|
||||
|
||||
|
||||
def pre_fork(server, worker):
|
||||
"""Called just before a worker is forked."""
|
||||
pass
|
||||
|
||||
|
||||
def post_fork(server, worker):
|
||||
"""Called just after a worker has been forked."""
|
||||
server.log.info(f"Worker spawned (pid: {worker.pid})")
|
||||
|
||||
|
||||
def pre_exec(server):
|
||||
"""Called just before a new master process is forked."""
|
||||
server.log.info("Forked child, re-executing.")
|
||||
|
||||
|
||||
def worker_int(worker):
|
||||
"""Called when a worker receives the SIGINT or SIGQUIT signal."""
|
||||
worker.log.info(f"Worker received INT or QUIT signal (pid: {worker.pid})")
|
||||
|
||||
|
||||
def worker_abort(worker):
|
||||
"""Called when a worker receives the SIGABRT signal."""
|
||||
worker.log.info(f"Worker received SIGABRT signal (pid: {worker.pid})")
|
||||
|
||||
|
||||
def pre_request(worker, req):
|
||||
"""Called just before a worker processes the request."""
|
||||
worker.log.debug(f"{req.method} {req.path}")
|
||||
|
||||
|
||||
def post_request(worker, req, environ, resp):
|
||||
"""Called after a worker processes the request."""
|
||||
pass
|
||||
|
||||
|
||||
def worker_exit(server, worker):
|
||||
"""Called just after a worker has been exited."""
|
||||
server.log.info(f"Worker exited (pid: {worker.pid})")
|
||||
|
||||
|
||||
def on_exit(server):
|
||||
"""Called just before exiting Gunicorn."""
|
||||
server.log.info("Shutting down WebDAV Server...")
|
||||
|
||||
# ============================================================================
|
||||
# SSL Configuration (if needed)
|
||||
# ============================================================================
|
||||
|
||||
# Uncomment and configure for SSL support
|
||||
# keyfile = '/path/to/key.pem'
|
||||
# certfile = '/path/to/cert.pem'
|
||||
# ssl_version = 'TLSv1_2'
|
||||
# cert_reqs = 0 # ssl.CERT_NONE
|
||||
# ca_certs = None
|
||||
# suppress_ragged_eofs = True
|
||||
# do_handshake_on_connect = False
|
||||
# ciphers = 'TLSv1'
|
||||
Reference in New Issue
Block a user