Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed .env
Empty file.
39 changes: 39 additions & 0 deletions .github/workflows/backend-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Backend tests

on:
push:
paths:
- "backend/**"
- ".github/workflows/backend-test.yml"
pull_request:
paths:
- "backend/**"
- ".github/workflows/backend-test.yml"

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6
with:
enable-cache: true
cache-dependency-glob: backend/uv.lock
- name: Install dependencies
run: uv sync --frozen --extra test
- name: Run tests
run: uv run pytest
74 changes: 67 additions & 7 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Normalize image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
Expand Down Expand Up @@ -118,17 +120,61 @@ jobs:
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

# No-auth, no-network packaging check: prove the shipped non-root image can
# resolve the bundled Copilot runtime. Does not authenticate, use credentials,
# or reach the network, and prints no environment values.
runtime-check:
needs: build-and-push-image
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- name: Normalize image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Log in to the Container registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get short SHA
id: get_short_sha
run: echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
- name: Verify bundled runtime resolves for the non-root user
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ steps.get_short_sha.outputs.short_sha }}
run: |
docker pull "$IMAGE"
docker run --rm -i --network none --user app --entrypoint python "$IMAGE" - <<'PY'
import os

from openui import config
from openui.copilot.device_auth import resolve_copilot_cli_path

data_dir = config.default_db.parent
assert data_dir.is_dir(), f"data directory was not created: {data_dir}"
assert os.access(data_dir, os.W_OK), (
f"data directory is not writable by the app user: {data_dir}"
)

cli_path = resolve_copilot_cli_path()
assert os.path.exists(cli_path), "bundled Copilot runtime is missing"
assert os.access(cli_path, os.X_OK), "bundled Copilot runtime is not executable"
print("bundled Copilot runtime resolved and executable")
PY

test:
permissions:
contents: read
packages: write
attestations: write
id-token: write
packages: read
needs: build-and-push-image
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Normalize image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
name: Install pnpm
with:
Expand All @@ -155,12 +201,24 @@ jobs:
- name: Install dependencies
working-directory: ./frontend
run: pnpm install
- name: Get short SHA
id: get_short_sha
run: echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
- name: Log in to the Container registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull image for Playwright
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ steps.get_short_sha.outputs.short_sha }}
run: docker pull "$IMAGE"
- name: Log out of the Container registry
run: docker logout "$REGISTRY"
- name: Install Playwright Browsers
working-directory: ./frontend
run: pnpm exec playwright install --with-deps chromium webkit
- name: Get short SHA
id: get_short_sha
run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Run Playwright tests
env:
DOCKER_TAG: sha-${{ steps.get_short_sha.outputs.short_sha }}
Expand All @@ -176,14 +234,16 @@ jobs:
retention-days: 30

release:
needs: test
needs: [test, runtime-check]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Normalize image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Log in to the Container registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
Expand Down
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,70 @@ If you have your OPENAI_API_KEY set in the environment already, just remove `=xx

*If you make changes to the frontend or backend, you'll need to run `docker-compose build` to have them reflected in the service.*

### GitHub Copilot

This fork can use GitHub Copilot as an additional provider for text-to-UI and
screenshot-to-UI generation. It uses the official Copilot SDK; it does not turn
Copilot into a public OpenAI-compatible API.

- Existing OpenAI, Groq, Ollama, and LiteLLM providers remain available.
- Copilot sessions run in SDK `empty` mode with no tools, shell, filesystem,
MCP servers, skills, plugins, or persistent conversation.
- A real-account smoke test is manual because it uses the account's allowance.

Two authentication modes are available:

**Device mode** (local single-user) — the server authenticates using the host
machine's GitHub credentials via the GitHub device flow. Set
`OPENUI_COPILOT_AUTH_MODE=device`. All requests share one machine identity and
allowance; device mode is **not suitable for untrusted multi-user access**.
Device mode is **strictly local and bare-process only**:

- OpenUI refuses to start unless `OPENUI_ENVIRONMENT=local` and `OPENUI_HOST`
binds a loopback address (`localhost`, `127.0.0.1`, or `::1`).
- The in-app device setup endpoints only accept requests whose TCP peer is
loopback, whose `Host` and (any) `Origin` hostnames are loopback, and that
carry no proxy headers (`Forwarded`, `X-Forwarded-For`, `X-Real-IP`, `Via`).
- Device mode must **not** be served through a reverse proxy, a port-forwarding
or tunnel platform, a public tunnel, a custom hostname, or Gitpod/Codespaces
port exposure — a same-host proxy connects from loopback and would otherwise
make remote requests look local.
- Device mode **cannot run inside a container** (Docker/Kubernetes): bridge
networking delivers requests from a non-loopback gateway, so OpenUI refuses
to start device mode in a container. Container deployments must use OAuth
mode.

The only supported private-remote path is an SSH local port forward to a bare
host process while you browse a loopback URL
(`ssh -L 7878:127.0.0.1:7878 host`), rather than exposing OpenUI directly. For
cloud, container, or multi-user deployments use OAuth mode instead.

```bash
export OPENUI_COPILOT_ENABLED=1
export OPENUI_COPILOT_AUTH_MODE=device
cd backend
uv run python -m copilot download-runtime
uv run python -m openui
```

Then open Settings, select **Connect GitHub Copilot**, copy the one-time code,
authorize at `https://github.com/login/device`, and wait for models to refresh.

**OAuth mode** (cloud / multi-user) — each user signs in individually with
their own GitHub account and Copilot entitlement. Requires a GitHub OAuth App
and a token-encryption key. Set `OPENUI_COPILOT_AUTH_MODE=oauth` (the default).

```bash
export OPENUI_COPILOT_ENABLED=1
export OPENUI_COPILOT_AUTH_MODE=oauth
export OPENUI_TOKEN_ENCRYPTION_KEY='v1:<generated-base64url-key>'
export GITHUB_CLIENT_ID='<oauth-app-client-id>'
export GITHUB_CLIENT_SECRET='<oauth-app-client-secret>'
```

See [`backend/README.md`](backend/README.md#github-copilot-provider) for full
setup instructions for both modes.

## Development

A [dev container](https://github.com/wandb/openui/blob/main/.devcontainer/devcontainer.json) is configured in this repository which is the quickest way to get started.
Expand Down
28 changes: 0 additions & 28 deletions backend/.github/workflows/test.yml

This file was deleted.

18 changes: 14 additions & 4 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

WORKDIR /app

ENV UV_LINK_MODE=copy UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1 \
COPILOT_CLI_EXTRACT_DIR=/app/.copilot-runtime

RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
Expand All @@ -13,15 +15,23 @@ RUN --mount=type=cache,target=/root/.cache/uv \
COPY . /app

RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --extra litellm --no-dev
uv sync --frozen --extra litellm --no-dev \
&& /app/.venv/bin/python -m copilot download-runtime

# Copy the virtualenv into a distroless image
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

WORKDIR /app

RUN groupadd --system app \
&& useradd --system --gid app --home-dir /app --no-create-home app \
&& chown app:app /app

COPY --from=builder --chown=app:app /app /app

ENV PATH="/app/.venv/bin:$PATH"
ENV PATH="/app/.venv/bin:$PATH" \
COPILOT_CLI_EXTRACT_DIR=/app/.copilot-runtime \
HOME=/app

USER app

ENTRYPOINT ["python", "-m", "openui", "--litellm"]
Loading