Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9f94a9f
flail: claude-generated 'authelia' service
tseaver Apr 18, 2026
9ecf18f
chore: add OIDC provider config to 'authelia' service
tseaver Apr 18, 2026
0f2349a
chore: move self-cert generation to script
tseaver Apr 18, 2026
c3b9041
chore: sync 'CLAUDE.md' with changes
tseaver Apr 19, 2026
ada82df
feat: use 'Authelia' OIDC provider
tseaver Apr 19, 2026
f91037b
fix: 'generate-secrets' script adds derived values to configs directly
tseaver Apr 19, 2026
46a575f
fix: create files w/ generated content from '.in' templates
tseaver Apr 19, 2026
8fee369
fix: inline the generated OIDC JWKS PEM into 'authelia/configuration.…
tseaver Apr 19, 2026
d2fdce4
fix: error out early if invalid '.gen' dirs created by 'DC up'
tseaver Apr 19, 2026
552fdb0
chore: drop stale, commented-out envvar secret config
tseaver Apr 19, 2026
61b5b1c
chore: bind-mount 'nginx.conf
tseaver Apr 19, 2026
5473d7e
fix: preserve existing secrets
tseaver Apr 19, 2026
4e8ea97
fix: make 'soliplex.localhost' the canonical hostname for the stack
tseaver Apr 19, 2026
307fc15
fix: (re)generate Authelia argon hash in 'generate-secrets.sh'
tseaver Apr 19, 2026
f2d2651
docs: OIDC provider role in 'aurelia/README.md'
tseaver Apr 19, 2026
f13fae9
fix: plumb user claims from 'authelia' to Soliplex 'backend'
tseaver Apr 20, 2026
33b173e
chore: flesh out backend logging config
tseaver Apr 20, 2026
c2d4884
chore: bump 'soliplex >= 0.60.1, < 0.61
tseaver Apr 20, 2026
ceebecb
chore: bump 'soliplex >= 0.60.3, < 0.61'
tseaver Apr 20, 2026
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ uploads/

# Scripts / files generated by agents in sandboxes.
backend/sandbox/workdirs/

# Config files copied from templates by: 'scripts/generate-nginx-cert.sh'
backend/environment/oidc/cacert.pem

# Config files copied from templates by 'scripts/generate-secrets.sh'
authelia/configuration.yml
authelia/users_database.yml
backend/environment/oidc/config.yaml
43 changes: 37 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,36 @@ A Docker Compose template that assembles a running Soliplex stack (backend, Flut
First-time setup (required before `up`):

```bash
./scripts/generate-secrets.sh # populates .secrets/*.gen (gitignored)
./scripts/generate-secrets.sh # populates .secrets/*.gen (gitignored)
./scripts/generate-nginx-cert.sh # generates nginx TLS cert + builds backend cacert.pem from cacert.pem.in
```

Both scripts rebuild a gitignored output file from a version-controlled
`*.in` template on every run (idempotent — safe to re-run):

- `generate-nginx-cert.sh` rebuilds `backend/environment/oidc/cacert.pem`
from `backend/environment/oidc/cacert.pem.in` (the Mozilla CA bundle),
appending the freshly generated nginx public cert between BEGIN/END
marker comments so the backend trusts the HTTPS path to Authelia.
- `generate-secrets.sh` rebuilds two configs from their templates,
injecting derived values in place of `REPLACE_ME` placeholders:
- `backend/environment/oidc/config.yaml` from `config.yaml.in` — the
OIDC JWKS **public key** PEM is written under
`auth_systems[authelia].token_validation_pem`, between the
`BEGIN/END PUBLIC KEY` markers.
- `authelia/configuration.yml` from `configuration.yml.in` — the
PBKDF2-SHA512 **digest** of the OIDC client secret is written under
`identity_providers.oidc.clients[soliplex].client_secret`,
replacing the `$pbkdf2-sha512$REPLACE_ME` placeholder.

The plaintext OIDC client secret stays in
`.secrets/authelia_oidc_client_secret.gen` for the backend to mount.

The three gitignored outputs (`cacert.pem`, `config.yaml`,
`configuration.yml`) should never be hand-edited — edits will be
overwritten the next time either script runs. Edit the `.in` templates
instead.

Run the stack:

```bash
Expand All @@ -33,11 +60,12 @@ Ports exposed to the host: `9000` (nginx HTTP), `9443` (nginx HTTPS, self-signed

### Service graph (see `docker-compose.yml`)

- **nginx** — serves the Flutter web frontend (built from the `soliplex/frontend` release tarball inside `nginx/Dockerfile`) and reverse-proxies `/api/` and `/mcp/` to `backend:8000`. Terminates TLS on 9443 with a self-signed cert generated at build time.
- **backend** — runs `soliplex-cli serve /environment`. **Currently launched with `--no-auth-mode`** (see `docker-compose.yml`; marked temporary). The `--reload=config` flag means edits under `backend/environment/` take effect without rebuild.
- **nginx** — serves the Flutter web frontend (built from the `soliplex/frontend` release tarball inside `nginx/Dockerfile`) and reverse-proxies `/api/` and `/mcp/` to `backend:8000`. Terminates TLS on 9443 with a self-signed cert generated on the host by `scripts/generate-nginx-cert.sh` (into `.secrets/nginx-server.{crt,key}.gen`) and bind-mounted into the container — so cert rotation is `./scripts/generate-nginx-cert.sh && docker compose restart nginx backend`, no rebuild required. Also proxies `/authelia/` through to the Authelia container (portal UI + OIDC endpoints). **No longer enforces auth** at the edge — the backend is now the OIDC relying party and enforces auth itself.
- **backend** — runs `soliplex-cli serve /environment`. **Currently launched with `--no-auth-mode`** (see `docker-compose.yml`; marked temporary — drop the flag once the OIDC client registration is verified). The `--reload=config` flag means edits under `backend/environment/` take effect without rebuild. Acts as an OIDC relying party against Authelia — config in `backend/environment/oidc/config.yaml` (gitignored; rebuilt from `config.yaml.in` by `scripts/generate-secrets.sh`), `authelia_oidc_client_secret` mounted at `/run/secrets/authelia_oidc_client_secret`, and the nginx self-signed cert appended to `backend/environment/oidc/cacert.pem` (gitignored; rebuilt from `cacert.pem.in` by `scripts/generate-nginx-cert.sh`) so it trusts the HTTPS path to `/authelia/.well-known/openid-configuration`.
- **authelia** — acts as the OpenID Connect Provider for the backend. Portal + OIDC endpoints (`/.well-known/openid-configuration`, `/api/oidc/*`) served at `https://soliplex.localhost:9443/authelia/` (path prefix, same origin — no hosts-file edits required on systems with systemd-resolved / modern glibc, which auto-resolve `*.localhost` to 127.0.0.1). File-based user backend at `authelia/users_database.yml`; Postgres storage in DB `soliplex_authelia`; secrets injected via `AUTHELIA_*_FILE` (`authelia_jwt_secret`, `authelia_session_secret`, `authelia_storage_encryption_key`, `authelia_db_password`, `authelia_oidc_hmac_secret`). The JWKS signing key is NOT mounted as a Docker secret — Authelia doesn't recognise `_FILE` env vars for list entries (jwks[]) — so `scripts/generate-secrets.sh` inlines the PEM into the rebuilt `authelia/configuration.yml`. OIDC client is registered there under `identity_providers.oidc.clients`; the client-secret digest also lives inline. That file is gitignored and is rebuilt from `authelia/configuration.yml.in` by `scripts/generate-secrets.sh`, which in the same run also rebuilds `backend/environment/oidc/config.yaml` from its `.in` template with the matching JWKS public-key PEM. Default dev credentials `admin` / `authelia` — rotate the argon2 hash before any non-local use. Authelia requires HTTPS, so the OIDC flow only works on 9443. **Access the stack via `https://soliplex.localhost:9443/`** — Authelia's validator rejects bare `localhost` as a session cookie domain, and `127.0.0.1` doesn't work either because the backend (inside its container) can't reach the host on that IP; `soliplex.localhost` resolves identically on both sides (host-side via `.localhost` auto-resolution; container-side via `extra_hosts: soliplex.localhost → host-gateway` in `docker-compose.yml`), keeping the OIDC `iss` claim consistent.
- **haiku-rag** — watches `rag/docs/` and writes a LanceDB to `rag/db/`. That same `rag/db/` directory is bind-mounted into the backend at `/db` so the backend's `rag` skill can query it. Delegates document conversion/chunking to docling-serve.
- **docling-serve** — stateless document converter. CPU image by default; comment swap in `docker-compose.yml` for GPU.
- **postgres** — three databases created on first boot by `postgres/config/init.sh`: `soliplex_agui` (thread persistence), `soliplex_authz` (authorization policy), `soliplex_ingester`. Each gets a dedicated low-privilege role whose password is read from `/run/secrets/<name>_db_password`. Init runs only on an empty data volume; to re-run, `docker compose down -v`.
- **postgres** — four databases created on first boot by `postgres/config/init.sh`: `soliplex_agui` (thread persistence), `soliplex_authz` (soliplex's own authorization policy — distinct from Authelia), `soliplex_ingester`, `soliplex_authelia` (Authelia session/config storage). Each gets a dedicated low-privilege role whose password is read from `/run/secrets/<name>_db_password`. Init runs only on an empty data volume; to re-run, `docker compose down -v`.

### Secrets

Expand Down Expand Up @@ -72,5 +100,8 @@ Drop documents into `rag/docs/` and haiku-rag will ingest them on its monitor cy

- `constraints.txt` pins `soliplex >= 0.60.0.1, < 0.61`. Bumping this is a backend rebuild.
- The frontend is pulled from **the latest** `soliplex/frontend` GitHub release inside `nginx/Dockerfile` — rebuilds are not reproducible across time unless you pin the tarball URL. Cache-bust hash is captured from the release tag and written to `/tmp/soliplex-frontend-release-hash` during build.
- Backend `--no-auth-mode` is explicitly labeled temporary in `docker-compose.yml`. Don't assume auth is enforced end-to-end in this template.
- `docker compose down -v` drops the `postgres_data` volume — all chat threads, authz grants, and ingester state go with it.
- Backend `--no-auth-mode` is explicitly labeled temporary in `docker-compose.yml`. Until it's removed, **nothing enforces auth** — nginx no longer runs the `auth_request` gate either. Drop the flag once the Authelia OIDC client registration is verified end-to-end.
- `docker compose down -v` drops the `postgres_data` volume — all chat threads, authz grants, ingester state, **and Authelia session/config state** go with it.
- The nginx self-signed cert rotates every time `scripts/generate-nginx-cert.sh` is re-run. That script both writes `.secrets/nginx-server.{crt,key}.gen` and rebuilds `backend/environment/oidc/cacert.pem` from `cacert.pem.in`, appending the public cert between marker comments. Forgetting to re-run it will make the backend's OIDC discovery call fail with an X.509 verify error.
- Don't hand-edit `backend/environment/oidc/cacert.pem`, `backend/environment/oidc/config.yaml`, or `authelia/configuration.yml` — they're gitignored build artifacts. Edit the matching `*.in` templates and re-run the generator scripts.
- Authelia requires HTTPS for its OIDC flow, which only works on 9443. The 9000 listener stays open for behind-an-upstream-proxy deployments that terminate TLS upstream.
103 changes: 101 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,103 @@
# Soliplex Docker Compose Template

A starting point for running Soliplex and related servcies under
Docker Compose.
A starting point for running Soliplex and related services under Docker
Compose: the Soliplex backend, a Flutter web frontend served by nginx,
the haiku.rag document pipeline, docling-serve, Postgres, and Authelia
as the OpenID Connect provider.

## Prerequisites

- Docker + Docker Compose
- OpenSSL (for the secret and cert generation scripts)
- An Ollama server reachable from the stack (for the `gpt-oss:*` models
referenced in `backend/environment/installation.yaml`)

## First-time setup

Run the two generator scripts, in either order, before the first
`docker compose up`:

```bash
./scripts/generate-secrets.sh # populates .secrets/*.gen (gitignored)
./scripts/generate-nginx-cert.sh # self-signed nginx cert + builds backend cacert.pem from cacert.pem.in
```

Both scripts are idempotent. Each one rebuilds a gitignored output file
from a version-controlled `*.in` template on every run:

- `generate-nginx-cert.sh` rebuilds `backend/environment/oidc/cacert.pem`
from `cacert.pem.in` (the Mozilla CA bundle) and appends the freshly
generated nginx public cert so the backend trusts the HTTPS path to
Authelia.
- `generate-secrets.sh` also writes the `.secrets/*.gen` files and
rebuilds two configs from their templates, injecting derived values
in place of `REPLACE_ME` placeholders:
- the OIDC JWKS **public key** PEM into
`backend/environment/oidc/config.yaml` (from `config.yaml.in`) under
`auth_systems[authelia].token_validation_pem`
- the PBKDF2-SHA512 **digest** of the OIDC client secret into
`authelia/configuration.yml` (from `configuration.yml.in`) under
`identity_providers.oidc.clients[soliplex].client_secret`

No manual paste step is required. The plaintext OIDC client secret stays
in `.secrets/authelia_oidc_client_secret.gen` for the backend to mount.
Don't hand-edit the three gitignored outputs — edit the `.in` templates
and re-run the scripts.

Create a `.env` file defining `OLLAMA_BASE_URL`, pointing at the Ollama
server that will serve the models.

## Running the stack

```bash
docker compose up # foreground
docker compose up -d # detached
docker compose build <service> # rebuild one service
docker compose logs -f backend
docker compose down # stop (keeps postgres_data volume)
docker compose down -v # stop AND wipe postgres volume
```

Access the UI at **`https://soliplex.localhost:9443/`** — not `localhost`
or `127.0.0.1`. The OIDC flow requires the same URL to be reachable from
both the browser (host side) and the backend container; `soliplex.localhost`
auto-resolves to 127.0.0.1 on the host (systemd-resolved / modern glibc
handle `*.localhost` per RFC 6761) and is routed to the host via
`extra_hosts` from inside the backend container. Authelia also accepts it
as a cookie domain (contains a period). You'll have to accept the
self-signed cert on first load.

If your host OS does not auto-resolve `*.localhost`, add this to
`/etc/hosts`:

```
127.0.0.1 soliplex.localhost
```

Default Authelia dev credentials: **`admin` / `authelia`**. Rotate the
argon2 hash in `authelia/users_database.yml` before any non-local use.

### Ports

| Port | Service |
|------|---------|
| 9000 | nginx HTTP (no TLS — for upstream-terminated deployments) |
| 9443 | nginx HTTPS (self-signed; **use this one**) |
| 8000 | backend direct |
| 8001 | haiku-rag MCP |
| 5001 | docling-serve |
| 5432 | Postgres |

### Re-running setup

- Rotating the nginx cert: re-run `./scripts/generate-nginx-cert.sh` then
`docker compose restart nginx backend`. No rebuild needed.
- Rotating any secret: re-run `./scripts/generate-secrets.sh`. For secrets
tied to the Postgres users created on first boot, you must also
`docker compose down -v` to re-init the data volume, or the backend
will fail to authenticate to the DB.

## Further reading

Architectural details (service graph, secret modes, Soliplex config
layout, sandbox, RAG pipeline, gotchas) live in [`CLAUDE.md`](./CLAUDE.md).
1 change: 1 addition & 0 deletions authelia/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM docker.io/authelia/authelia:latest
Loading