diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 8a87b2e..b98a2b4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -5,11 +5,13 @@ python manage.py migrate --noinput python manage.py ensure_admin python manage.py purge_ratelimits +# Forwarded headers are evaluated by core.middleware.ProxyMiddleware (TRUSTED_PROXIES), +# not by gunicorn, so gunicorn keeps its default of trusting none of them from remote peers. +# The control socket (gunicorn >= 26) is not used and /app is read-only for the app user. exec gunicorn config.wsgi:application \ --bind 0.0.0.0:8000 \ --workers "${GUNICORN_WORKERS:-2}" \ --threads "${GUNICORN_THREADS:-4}" \ --timeout "${GUNICORN_TIMEOUT:-120}" \ + --no-control-socket \ --access-logfile - -# Forwarded headers are evaluated by core.middleware.ProxyMiddleware (TRUSTED_PROXIES), -# not by gunicorn, so gunicorn keeps its default of trusting none of them from remote peers. diff --git a/recipes/ai.py b/recipes/ai.py index 700cbda..3351911 100644 --- a/recipes/ai.py +++ b/recipes/ai.py @@ -207,9 +207,14 @@ def _check_limits(user): ratelimit.hit(global_key, settings.AI_RATE_LIMIT_GLOBAL_PER_DAY, day) +DEFAULT_BASE_URL = "https://api.openai.com/v1" + + def _client(): + # Always pass base_url explicitly: with base_url=None the SDK falls back to the OPENAI_BASE_URL + # environment variable, which docker compose sets to "" when the .env line is left empty. return openai.OpenAI(api_key=settings.OPENAI_API_KEY, timeout=settings.OPENAI_TIMEOUT, max_retries=1, - base_url=settings.OPENAI_BASE_URL or None) + base_url=settings.OPENAI_BASE_URL or DEFAULT_BASE_URL) def _create_completion(messages): diff --git a/tests/test_ai.py b/tests/test_ai.py index d452843..187b3f6 100644 --- a/tests/test_ai.py +++ b/tests/test_ai.py @@ -392,3 +392,13 @@ def test_end_to_end_photo_import_through_sdk(stub_openai_server, user_client): assert response.context["form"].initial["title"] == "Linsen-Dal" parts = received["body"]["messages"][1]["content"] assert any(p["type"] == "image_url" and p["image_url"]["url"].startswith("data:image/jpeg;base64,") for p in parts) + + +def test_empty_base_url_env_falls_back_to_openai(monkeypatch, settings): + """docker compose sets OPENAI_BASE_URL="" for an empty .env line; the SDK must not pick that up.""" + monkeypatch.setenv("OPENAI_BASE_URL", "") + settings.OPENAI_API_KEY = "sk-test" + settings.OPENAI_BASE_URL = "" + assert str(ai._client().base_url).rstrip("/") == "https://api.openai.com/v1" + settings.OPENAI_BASE_URL = "http://proxy.example/v1" + assert str(ai._client().base_url).rstrip("/") == "http://proxy.example/v1"