""" 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 = True public_base_url: str = "" state_db: str = "" session_secret: str = "" log_level: str = "INFO" chunked_uploads_enabled: bool = True chunk_size_mb: int = 50 timezone: str = "UTC" telegram_bot_api_key: str = "" telegram_bot_owner_id: str = "" 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", "test123") # 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) 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"), True) 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"), True) try: chunk_size_mb = int(os.getenv("CHUNK_SIZE_MB", "50")) except ValueError: chunk_size_mb = 50 timezone = os.getenv("TIMEZONE", "UTC") telegram_bot_api_key = os.getenv("TELEGRAM_BOT_API_KEY", "") telegram_bot_owner_id = os.getenv("TELEGRAM_BOT_OWNER_ID", "") 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, telegram_bot_api_key=telegram_bot_api_key, telegram_bot_owner_id=telegram_bot_owner_id, )