# retoor import time from pathlib import Path from dotenv import load_dotenv from os import environ load_dotenv() BASE_DIR = Path(__file__).resolve().parent.parent STATIC_DIR = BASE_DIR / "devplacepy" / "static" TEMPLATES_DIR = BASE_DIR / "devplacepy" / "templates" # Single source of truth for every runtime/user-generated artifact. Everything the # app creates or modifies at runtime lives under DATA_DIR, never inside the package # and never served via /static. Overridable by DEVPLACE_DATA_DIR (point at a volume # in production). Each path below is derived from DATA_DIR; no other module computes # a runtime path from scratch. DATA_DIR = Path(environ.get("DEVPLACE_DATA_DIR", str(BASE_DIR / "data"))) UPLOADS_DIR = DATA_DIR / "uploads" ATTACHMENTS_DIR = UPLOADS_DIR / "attachments" PROJECT_FILES_DIR = UPLOADS_DIR / "project_files" CONTAINER_WORKSPACES_DIR = DATA_DIR / "container_workspaces" ZIPS_DIR = DATA_DIR / "zips" ZIP_STAGING_DIR = DATA_DIR / "zip_staging" FORK_STAGING_DIR = DATA_DIR / "fork_staging" SEO_REPORTS_DIR = DATA_DIR / "seo_reports" KEYS_DIR = DATA_DIR / "keys" BOT_DIR = DATA_DIR / "bot" LOCKS_DIR = DATA_DIR / "locks" DEVII_TASKS_DB = DATA_DIR / "devii_tasks.db" DEVII_LESSONS_DB = DATA_DIR / "devii_lessons.db" DATABASE_URL = environ.get( "DEVPLACE_DATABASE_URL", f"sqlite:///{DATA_DIR / 'devplace.db'}" ) SECRET_KEY = environ.get("SECRET_KEY", "devplace-secret-key-change-in-production") SECONDS_PER_DAY = 86400 SESSION_MAX_AGE = SECONDS_PER_DAY * 7 SESSION_MAX_AGE_REMEMBER = SECONDS_PER_DAY * 30 PORT = 10500 SITE_URL = environ.get("DEVPLACE_SITE_URL", "").rstrip("/") # Cache-busting version stamped onto every app-owned static URL. Computed once at # process start, so a restart/deploy changes it and busts every browser cache while # assets stay heavily cached (immutable, 1 year) between deploys. Set # DEVPLACE_STATIC_VERSION at launch so multiple prod workers share one value. STATIC_VERSION = environ.get("DEVPLACE_STATIC_VERSION") or str(int(time.time())) INTERNAL_BASE_URL = environ.get( "DEVPLACE_INTERNAL_BASE_URL", f"http://localhost:{PORT}" ).rstrip("/") INTERNAL_GATEWAY_URL = f"{INTERNAL_BASE_URL}/openai/v1/chat/completions" INTERNAL_MODEL = "molodetz" INTERNAL_EMBED_MODEL = "molodetz~embed" SERVICE_LOCK_FILE = LOCKS_DIR / "devplace-services.lock" INIT_LOCK_FILE = LOCKS_DIR / "devplace-init.lock" # Every container instance runs this one prebuilt image (built once via `make ppy`). CONTAINER_IMAGE = environ.get("DEVPLACE_CONTAINER_IMAGE", "ppy:latest") # Override host the /p/ ingress proxy dials, with the published host port, # instead of the container's own bridge IP. Set this only for topologies where # the app cannot route to the container network directly (containerized app via # docker socket: use host.docker.internal). Left empty, the proxy connects # straight to the container IP and container port, which avoids the host # port-publishing layer (docker-proxy / iptables DNAT / loopback) entirely. CONTAINER_PROXY_HOST = environ.get("DEVPLACE_CONTAINER_PROXY_HOST", "").strip() VAPID_PRIVATE_KEY_FILE = KEYS_DIR / "notification-private.pem" VAPID_PRIVATE_KEY_PKCS8_FILE = KEYS_DIR / "notification-private.pkcs8.pem" VAPID_PUBLIC_KEY_FILE = KEYS_DIR / "notification-public.pem" VAPID_SUB = environ.get("DEVPLACE_VAPID_SUB", "mailto:retoor@molodetz.nl") # Documented registry of every runtime directory. ensure_data_dirs() creates them # all at startup so the tree always exists before the DB, keys, locks, uploads, and # job staging are written. DATA_PATHS: dict[str, Path] = { "data": DATA_DIR, "uploads": UPLOADS_DIR, "attachments": ATTACHMENTS_DIR, "project_files": PROJECT_FILES_DIR, "container_workspaces": CONTAINER_WORKSPACES_DIR, "zips": ZIPS_DIR, "zip_staging": ZIP_STAGING_DIR, "fork_staging": FORK_STAGING_DIR, "seo_reports": SEO_REPORTS_DIR, "keys": KEYS_DIR, "bot": BOT_DIR, "locks": LOCKS_DIR, } def ensure_data_dirs() -> None: for path in DATA_PATHS.values(): path.mkdir(parents=True, exist_ok=True)