Files
2026-09-03 10:52:54 +02:00

80 lines
3.3 KiB
Python

"""Runtime configuration read from environment variables.
AppFactory injects variables through the generated runtime .env file, so every
option below has a safe default and the service starts without any variable set.
"""
import os
from dataclasses import dataclass, field
from functools import lru_cache
def _bool(name: str, default: bool) -> bool:
raw = os.getenv(name)
if raw is None or raw.strip() == "":
return default
return raw.strip().lower() in ("1", "true", "yes", "on")
def _int(name: str, default: int) -> int:
raw = os.getenv(name)
if raw is None or raw.strip() == "":
return default
try:
return int(raw)
except ValueError:
return default
def _list(name: str) -> list[str]:
raw = os.getenv(name, "")
return [item.strip() for item in raw.split(",") if item.strip()]
@dataclass(frozen=True)
class Settings:
app_name: str = field(default_factory=lambda: os.getenv("APP_NAME", "html-to-pdf"))
app_version: str = field(default_factory=lambda: os.getenv("APP_VERSION", "1.0.2"))
root_path: str = field(
default_factory=lambda: os.getenv("ROOT_PATH") or os.getenv("BASE_PATH") or ""
)
log_level: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO").upper())
# Job queue
workers: int = field(default_factory=lambda: _int("WORKERS", 2))
queue_max_size: int = field(default_factory=lambda: _int("QUEUE_MAX_SIZE", 100))
sync_timeout_seconds: int = field(default_factory=lambda: _int("SYNC_TIMEOUT_SECONDS", 60))
job_result_ttl_seconds: int = field(default_factory=lambda: _int("JOB_RESULT_TTL_SECONDS", 3600))
storage_dir: str = field(default_factory=lambda: os.getenv("STORAGE_DIR", "/tmp/html-to-pdf"))
# Engines
default_engine: str = field(default_factory=lambda: os.getenv("DEFAULT_ENGINE", "auto"))
chromium_enabled: bool = field(default_factory=lambda: _bool("CHROMIUM_ENABLED", True))
chromium_restart_after_jobs: int = field(
default_factory=lambda: _int("CHROMIUM_RESTART_AFTER_JOBS", 50)
)
# Network
fetch_timeout_seconds: int = field(default_factory=lambda: _int("FETCH_TIMEOUT_SECONDS", 30))
asset_timeout_seconds: int = field(default_factory=lambda: _int("ASSET_TIMEOUT_SECONDS", 10))
max_redirects: int = field(default_factory=lambda: _int("MAX_REDIRECTS", 5))
# SSRF protection. Blocking is on by default and can be widened or narrowed.
ssrf_block_private: bool = field(default_factory=lambda: _bool("SSRF_BLOCK_PRIVATE", True))
ssrf_extra_blocked_cidrs: list[str] = field(default_factory=lambda: _list("SSRF_EXTRA_BLOCKED_CIDRS"))
ssrf_allowed_hosts: list[str] = field(default_factory=lambda: _list("SSRF_ALLOWED_HOSTS"))
# Optional limits. Zero means no limit, which is the default on purpose.
max_pages: int = field(default_factory=lambda: _int("MAX_PAGES", 0))
max_html_bytes: int = field(default_factory=lambda: _int("MAX_HTML_BYTES", 0))
max_render_seconds: int = field(default_factory=lambda: _int("MAX_RENDER_SECONDS", 0))
# Callback delivery
callback_timeout_seconds: int = field(default_factory=lambda: _int("CALLBACK_TIMEOUT_SECONDS", 15))
callback_retries: int = field(default_factory=lambda: _int("CALLBACK_RETRIES", 3))
@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()