Skip to content

ISSUE: Admin UI unreachable — "/" returns 401 "Missing Authorization header" because the frontend build is silently excluded from the wheel #33

Description

@Classic298

Bug description

Visiting the orchestrator root (http://host:3000/) in a browser to open the admin UI (added in 0.0.4) returns:

{"detail":"Missing Authorization header"}

instead of serving the UI. This happens with the official Docker image (ghcr.io/open-webui/terminals:0.0.5) and any image/install built with pip install ., whenever TERMINALS_API_KEY or TERMINALS_OPEN_WEBUI_URL is set.

Steps to reproduce

  1. Run the official image, e.g. docker run -p 3000:3000 -e TERMINALS_API_KEY=secret -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/open-webui/terminals:0.0.5
  2. Open http://localhost:3000/ in a browser.

Expected: the admin UI loads (TERMINALS_ENABLE_UI defaults to true).
Actual: 401 {"detail":"Missing Authorization header"}.

Root cause

The built frontend never makes it into the installed Python package:

  1. terminals/frontend/build/ is gitignored (root .gitignore has build/, and terminals/frontend/.gitignore has build).

  2. Hatchling honors .gitignore during wheel file selection and excludes VCS-ignored files even when they match an include pattern. The "terminals/frontend/build/**/*" entry under [tool.hatch.build.targets.wheel].include in pyproject.toml is therefore a no-op. Hatchling's documented override for shipping gitignored build outputs is the separate artifacts option, which is not used.

  3. So the wheel built by pip install . in the Dockerfile ships the frontend source (src/, package.json, …) but not frontend/build/. Verifiable in a running container:

    $ docker exec <container> pip show -f terminals | grep frontend
      terminals/frontend/.gitignore
      terminals/frontend/package.json
      terminals/frontend/src/routes/+page.svelte
      ...            # <-- no frontend/build/* entries
    
  4. At runtime the terminals console script imports the package from site-packages, where FRONTEND_BUILD_DIR.exists() (terminals/main.py) is false, so the GET / UI route is never registered.

  5. The browser request falls through to the auth-protected catch-all reverse proxy (/{path:path} in terminals/routers/proxy.py), whose verify_api_key dependency rejects it with 401 Missing Authorization header.

Debugging note: the image does contain the built UI at /app/terminals/frontend/build (the Dockerfile COPY --from=frontend target), which is misleading — that copy is dead weight; the app runs from site-packages.

Fix (verified)

Declare the build output as hatchling artifacts in pyproject.toml:

[tool.hatch.build.targets.wheel]
packages = ["terminals"]
include = [
    "terminals/**/*.py",
    "terminals/alembic.ini",
    "terminals/migrations/**/*",
    "terminals/frontend/build/**/*",
]
artifacts = [
    "terminals/frontend/build/**/*",
]

Rebuilding the wheel with this change makes terminals/frontend/build/** appear in the wheel, and the UI is then served on /.

Workaround for existing deployments

Until a fixed image is published, make Python prefer the complete source tree already inside the image, e.g. in docker-compose:

services:
  terminals:
    environment:
      PYTHONPATH: "/app"

or copy the build into site-packages:

docker exec <container> sh -c 'cp -r /app/terminals/frontend/build "$(pip show terminals | sed -n "s/^Location: //p")/terminals/frontend/"'
docker restart <container>

Affected versions

0.0.4 (UI introduced) and 0.0.5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions