docs: the config file lives in $CODERAI_CONFIG_DIR/coderai, not the parent

Reproduced the Digesta "engine#0 crash-loops despite backend.type=cpu and
engine_specs" report on the published 0.2.83 image, GPU-less, with their
config — and it is not the front failing to propagate the engine spec.

coderai-entrypoint creates $CODERAI_CONFIG_DIR/coderai and symlinks
~/.coderai to it (lines 49, 70); the app resolves config from the HOME-style
path. A config.json written at $CODERAI_CONFIG_DIR is therefore never read.
On first start the app writes a DEFAULT coderai/config.json — backend null,
engine_specs null, engines null — reads that, and the engine auto-detects,
finds no GPU and aborts. Which is exactly the "backend: null" they saw and
read as the front regenerating the config.

Documents the live path, the one-liner to check which file is in use, and
that mounting straight at /config/coderai sidesteps it.

Also ships coderai-upgrade in Dockerfile.oci-venv. It was only in
Dockerfile.oci, so every image we actually publish (venv-built) failed
`coderai-docker --upgrade` with "stat /usr/local/bin/coderai-upgrade: no
such file or directory".
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
parent cc03d121
# Admin GUI: trusted forward-auth (reuse an upstream login)
**Status: feature request / requirements.** This is what CoderAI needs to add so
a reverse proxy that has *already* authenticated a user can hand that identity to
CoderAI's admin GUI, instead of the user logging into CoderAI a second time.
## Why
In the Digesta deployment CoderAI runs as a GPU-less RunPod orchestrator behind
nginx on `digesta.tech`, exposed at `/coderai/`. The operators are already
Digesta administrators with a Digesta login (session cookie on the same host,
membership in the `admins` group). We want **those same people, and only those
people, to reach the CoderAI admin GUI with the login they already have** — no
separate CoderAI password to provision, rotate and share, and no second login
screen.
Today that is not possible. CoderAI's admin GUI (`codai/admin/auth.py`) has only
its own login form and HMAC-signed session cookies, with users in `auth.json`.
There is no hook to trust an identity asserted by the proxy: no `X-Forwarded-User`
/ `REMOTE_USER` handling, no OIDC, no "disable the form" switch. So the best we
can do at the proxy is gate `/coderai/` with nginx `auth_request` against a small
Digesta endpoint (only Digesta admins get through) — but the user still meets
CoderAI's own login behind that gate. This request removes that second login.
We considered, and rejected, doing it entirely on our side (Digesta minting a
valid CoderAI session cookie by reading CoderAI's `secret_key` and reproducing
the cookie format). It works but couples the two products through a private
on-disk secret and an undocumented cookie layout, and breaks on any change to
either. A first-class CoderAI feature is the right place for it.
## What we need
A **trusted forward-auth mode** for the admin GUI. When enabled and the request
arrives from a trusted upstream, CoderAI accepts the user identity from a request
header and establishes an admin session for that user **without** showing the
login form.
### 1. A switch, off by default
A config key (and matching env), e.g.:
```json
"admin": {
"forward_auth": {
"enabled": true,
"user_header": "X-Forwarded-User",
"groups_header": "X-Forwarded-Groups",
"admin_group": "admins",
"shared_secret_header": "X-Coderai-Proxy-Secret",
"trusted_proxies": ["127.0.0.1", "::1"],
"auto_create_users": true,
"keep_local_login": false
}
}
```
Env equivalents for the container (`CODERAI_ADMIN_FORWARD_AUTH=1`,
`CODERAI_ADMIN_FORWARD_AUTH_SECRET=…`, …) so it can be set from the quadlet
without editing the mounted config.
### 2. The behaviour
- When `forward_auth.enabled` and the request passes the trust check (below),
read the username from `user_header`. If `auto_create_users`, create the admin
user on first sight; otherwise require it to already exist in `auth.json`.
- Grant admin scope when `groups_header` contains `admin_group` (or, if no groups
header is sent, treat every forward-authed user as admin — make this explicit
in config, don't guess).
- Issue CoderAI's normal signed session for that user, exactly as a successful
form login would, so the rest of the GUI is unchanged.
- `keep_local_login: false` disables the password form entirely (the proxy is the
only way in); `true` leaves the form as a fallback for direct/loopback access.
- Logout: since the real session lives upstream, a logout in CoderAI should clear
the CoderAI session and redirect to a configurable `post_logout_url` (the proxy
/ Digesta logout), not loop back into an auto-login.
### 3. The security contract — this is the part that must be right
A trusted header is a forged-identity hole if it is ever honoured from an
untrusted source. CoderAI must:
- **Only** honour `user_header` when the immediate peer is in `trusted_proxies`
**and** the request carries the correct `shared_secret_header` value. Fail
**closed** (fall back to the form, or 401) if either is missing — never trust
the header on its strength alone.
- **Strip / ignore** any `user_header` and `groups_header` present on a request
that does **not** come from a trusted proxy, so a client cannot send
`X-Forwarded-User: admin` directly.
- Keep the readiness probes (`/healthz`, `/health`, `/v1/health`) exempt, as now.
- Leave the `/v1/*` API on its own bearer tokens — this feature is **GUI-only**
and must not change how the API authenticates.
### 4. What the proxy side then looks like (our end, for reference)
nginx authenticates against Digesta, then forwards the identity plus the shared
secret on to CoderAI:
```nginx
location ^~ /coderai/ {
auth_request /_digesta_admin; # 200 only for a Digesta admin session
auth_request_set $cr_user $upstream_http_x_user;
proxy_pass http://127.0.0.1:8777/; # full image, internal nginx 8776
proxy_set_header X-Forwarded-User $cr_user;
proxy_set_header X-Forwarded-Groups admins;
proxy_set_header X-Coderai-Proxy-Secret "<shared secret, from a file>";
proxy_set_header X-Forwarded-Prefix /coderai;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
# client-supplied copies are dropped by not forwarding them + CoderAI's own strip
}
location = /_digesta_admin { # Digesta validates its own session cookie
internal;
proxy_pass http://127.0.0.1:8611/api/authz/coderai; # 204 for admin, 401 otherwise
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
```
Digesta gets a tiny endpoint (`GET /api/authz/coderai` → `204` if the session is
an `admins` member, else `401`, echoing the username in `X-User`). That part is
ours to build once CoderAI accepts the header.
## Acceptance
- With `forward_auth.enabled` and a correct secret from a trusted proxy, a request
to `/coderai/admin` lands in the dashboard **with no login form**, as the user
named in `X-Forwarded-User`, with admin rights.
- The same request **without** the secret, or with the header set by a client
directly (not via the trusted proxy), does **not** authenticate — it gets the
form or 401, never an admin session.
- `/v1/*` behaviour is unchanged (bearer tokens).
- `keep_local_login: true` still allows the password form on loopback.
## Context (current prod state)
Full image `ghcr.io/nextime/coderai` **0.2.83** runs GPU-less on the Digesta host,
GUI at `https://digesta.tech/coderai/` behind nginx **basic-auth** for now (that
basic-auth is what this feature replaces). Two separate issues on that deployment
are written up in `/AI/sentenze/coderai-surya-fix.md` and are **not** part of this
request: engine#0 crash-looping GPU-less despite `backend.type=cpu` +
`server.engine_specs`, and `/v1/*` serving `200` without a bearer (tokens not
enforced). This forward-auth feature is independent of both.
...@@ -69,6 +69,36 @@ docker run -d --name coderai --restart unless-stopped \ ...@@ -69,6 +69,36 @@ docker run -d --name coderai --restart unless-stopped \
ghcr.io/nextime/coderai:latest ghcr.io/nextime/coderai:latest
``` ```
> ### ⚠ Your config file goes in a **subdirectory**, and this costs people hours
>
> The entrypoint does not use `$CODERAI_CONFIG_DIR` directly. It creates
> **`$CODERAI_CONFIG_DIR/coderai/`** and symlinks `~/.coderai` to it, because the
> app resolves its config from the HOME-style path
> (`coderai-entrypoint` lines 49 and 70). So with the mount above, the file you
> must edit is:
>
> ```
> ~/coderai/config/coderai/config.json ← the one that is read
> ~/coderai/config/config.json ← IGNORED. Silently.
> ```
>
> Put settings in the parent and nothing happens: on first start the app writes
> a **default** `coderai/config.json` with `backend: null`, `engine_specs: null`
> and `engines: null`, reads that, and your carefully set `backend.type: "cpu"`
> is never seen — so the engine auto-detects, finds no GPU and aborts with
> `No supported backend detected`. It looks exactly like the front ignoring
> `engine_specs`. It isn't; the file was never read.
>
> Verify which file is live before you debug anything else:
>
> ```bash
> docker exec coderai sh -c 'readlink -f ~/.coderai; cat ~/.coderai/config.json'
> ```
>
> The same applies to `auth.json`, `models.json` and the rest — they all live in
> that subdirectory. Mounting your config directly at `/config/coderai` also
> works and avoids the whole question.
On a machine **with** a GPU add `--gpus all` (NVIDIA) or `--device /dev/dri` On a machine **with** a GPU add `--gpus all` (NVIDIA) or `--device /dev/dri`
(AMD/Intel, Vulkan) and you are done. (AMD/Intel, Vulkan) and you are done.
......
...@@ -151,6 +151,11 @@ WORKDIR /opt/coderai/app ...@@ -151,6 +151,11 @@ WORKDIR /opt/coderai/app
COPY . /opt/coderai/app COPY . /opt/coderai/app
COPY --from=build_meta /build-manifest.json /opt/coderai/BUILD-MANIFEST.json COPY --from=build_meta /build-manifest.json /opt/coderai/BUILD-MANIFEST.json
COPY packaging/linux/launcher/coderai-oci /usr/local/bin/coderai COPY packaging/linux/launcher/coderai-oci /usr/local/bin/coderai
# The in-image code upgrader. Dockerfile.oci has always shipped it; this
# venv-built variant did not, so `coderai-docker --upgrade` failed with
# `stat /usr/local/bin/coderai-upgrade: no such file or directory` on every
# image built this way — which is every image we actually publish.
COPY packaging/linux/launcher/coderai-upgrade /usr/local/bin/coderai-upgrade
COPY packaging/linux/launcher/with-env /usr/local/bin/with-env COPY packaging/linux/launcher/with-env /usr/local/bin/with-env
COPY packaging/linux/launcher/coderai-entrypoint /usr/local/bin/coderai-entrypoint COPY packaging/linux/launcher/coderai-entrypoint /usr/local/bin/coderai-entrypoint
COPY packaging/linux/launcher/wav2lip /usr/local/bin/wav2lip COPY packaging/linux/launcher/wav2lip /usr/local/bin/wav2lip
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment