80 lines
3.3 KiB
Python
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()
|