""" Config loader for the Immich Drop Uploader (Python). Reads ONLY from .env; there is NO runtime mutation from the UI. """ from __future__ import annotations import os from dataclasses import dataclass import secrets from dotenv import load_dotenv import hashlib import binascii @dataclass class Settings: """App settings loaded from environment variables (.env).""" admin_password: str max_concurrent: int public_upload_page_enabled: bool = False public_base_url: str = "" state_db: str = "" session_secret: str = "" log_level: str = "INFO" chunked_uploads_enabled: bool = False chunk_size_mb: int = 95 timezone: str = "UTC" def _hash_password(pw: str) -> str: """Return PBKDF2-SHA256 hash of a password.""" try: if not pw: return "" salt = os.urandom(16) iterations = 200_000 dk = hashlib.pbkdf2_hmac('sha256', pw.encode('utf-8'), salt, iterations) # use - as the delimiter to avoid Docker env variable substitution return f"pbkdf2_sha256-{iterations}-{binascii.hexlify(salt).decode()}-{binascii.hexlify(dk).decode()}" except Exception: return "" def load_settings() -> Settings: """Load settings from .env, applying defaults when absent.""" # Load environment variables from .env once here so importers don’t have to try: load_dotenv() except Exception: pass admin_password = os.getenv("ADMIN_PASSWORD", "admin") # Default for convenience, should be changed if not admin_password.startswith("pbkdf2_sha256$"): print("="*60) print("WARNING: ADMIN_PASSWORD is in plaintext.") print("For better security, use the hashed password below in your .env file:") hashed_pw = _hash_password(admin_password) if hashed_pw: print(f"ADMIN_PASSWORD={hashed_pw}") print("="*60) # Safe defaults: disable public uploader and invites unless explicitly enabled def as_bool(v: str, default: bool = False) -> bool: if v is None: return default return str(v).strip().lower() in {"1","true","yes","on"} public_upload = as_bool(os.getenv("PUBLIC_UPLOAD_PAGE_ENABLED", "false"), False) try: maxc = int(os.getenv("MAX_CONCURRENT", "3")) except ValueError: maxc = 3 state_db = os.getenv("STATE_DB", "/data/state.db") session_secret = os.getenv("SESSION_SECRET") or secrets.token_hex(32) log_level = os.getenv("LOG_LEVEL", "INFO").upper() chunked_uploads_enabled = as_bool(os.getenv("CHUNKED_UPLOADS_ENABLED", "false"), False) try: chunk_size_mb = int(os.getenv("CHUNK_SIZE_MB", "95")) except ValueError: chunk_size_mb = 95 timezone = os.getenv("TIMEZONE", "UTC") return Settings( admin_password=admin_password, max_concurrent=maxc, public_upload_page_enabled=public_upload, public_base_url=os.getenv("PUBLIC_BASE_URL", ""), state_db=state_db, session_secret=session_secret, log_level=log_level, chunked_uploads_enabled=chunked_uploads_enabled, chunk_size_mb=chunk_size_mb, timezone=timezone, )