Configuration
Audience: Technical administration
Overview
oPBWeb splits its configuration into three levels:
.envin the project directory: non-secret settings such as ports, LDAP server, time limits. Docker Compose reads the file and inserts the values intodocker-compose.yml.secrets/: all passwords and keys as individual files. They reach the containers as Docker secrets and are never stored in.env.- User interface: the connections to the opsi config servers (URL, technical user, password, stage, CA certificate; in the Depots view) and the defaults for new packages. These settings are stored in the database, see Config servers and Settings for new packages .
./setup-opbweb.sh creates .env and the missing secrets, see Installation
. Afterwards you can edit .env directly or run the script again; the existing values are then the defaults. Changes to .env take effect with the next ./start.sh; Docker Compose then recreates the affected containers.
Variables in .env
The template .env.example contains all variables of the following tables; commented-out lines show the default. If a variable is missing from .env, its default applies. setup-opbweb.sh writes the variables for database, Redis, object storage and CORS_ORIGINS only if they were already in the previous .env; variables the script does not know are kept at the end of the file.
General and sign-in
| Variable | Default | Service | Meaning |
|---|---|---|---|
TZ | Europe/Berlin | all | time zone of the containers, also for the time of the daily cleanup (see Settings → Worker ) |
LOG_LEVEL | INFO | api, worker | log verbosity: DEBUG, INFO, WARNING or ERROR. From INFO on, the API logs the uvicorn start, every HTTP request and problems with LDAP sign-in, see Logs |
DEBUG | false | api, worker | true starts uvicorn with --reload (hot reload of the API) and the worker with file watching, and also enables the debug output of httpx and python-opsi; for development only. Outside --dev, setup-opbweb.sh warns if the value is true |
OPBWEB_LANG | system locale | scripts | Language of setup-opbweb.sh, start.sh and scripts/init-secrets.sh: de or en. Set by setup-opbweb.sh (option --lang); if missing, the locale decides (de… → German, otherwise English). Does not affect the oPBWeb user interface |
ADMIN_USERNAME | admin | api | name of the first administrator account; only applies while the user table is empty |
COOKIE_SECURE | false | api, worker | setup-opbweb.sh sets true, because oPBWeb always runs via HTTPS (see TLS and COOKIE_SECURE
); false only in the development stack |
CORS_ORIGINS | ["http://localhost:3100"] | api | allowed origins as a JSON list; only needed for the Vite dev server, in production everything comes from the same origin |
ACCESS_TOKEN_MINUTES | 15 | api | validity of the access token in minutes (renewed automatically in the browser) |
REFRESH_TOKEN_DAYS | 7 | api | validity of the refresh cookie in days, i.e. how long a sign-in lasts without entering the password again |
ACCOUNT_MAX_FAILED_ATTEMPTS | 5 | api | failed attempts after which a local account is locked for the address they came from. One address may have four times as many failed attempts in total (all accounts, LDAP included); after that the API answers with 429. An account is locked for all addresses only after ten times as many failed attempts. |
ACCOUNT_LOCKOUT_MINUTES | 15 | api | duration of the locks and period in which failed attempts count |
LDAP_* | – | api | sign-in via LDAP/AD, see LDAP Sign-in |
opsi and packages
| Variable | Default | Service | Meaning |
|---|---|---|---|
OPSI_TIMEOUT_SECONDS | 60 | worker | time limit in seconds for all other opsi calls (JSON-RPC, WebDAV listing) |
OPSI_INSTALL_TIMEOUT_SECONDS | 3600 | worker | time limit in seconds for the upload to the repository and for depot_installPackage or depot_uninstallPackage |
JOB_TIMEOUT_SECONDS | 28800 | worker | upper limit in seconds for a whole job, e.g. an installation on several depots including waiting for busy depots |
BUILD_COMPRESSION | zstd | worker | compression of built .opsi files: zstd (opsi 4.3 default) or gzip |
BUILD_COMPRESSION only affects new builds. Packages that are already built keep their compression until they are built again.
Frontend and images
| Variable | Default | Meaning |
|---|---|---|
FRONTEND_HTTP_PORT | 80 | host port for HTTP |
FRONTEND_HTTPS_PORT | 443 | host port for HTTPS |
BACKEND_IMAGE | opbweb-backend:latest (dev: :dev) | image for api and worker |
FRONTEND_IMAGE | opbweb-frontend:latest | image for frontend |
GARAGE_INIT_IMAGE | opbweb-garage-init:1.0.0 | image for garage-init |
OPBWEB_REGISTRY | registry of the GitLab CI (registry.gitlab.open-mind.space/pages/opsipackagebuilder-web) | only for setup-opbweb.sh: prefix from which the script builds the image names (<prefix>/backend:<version> etc.); can also be set as an environment variable when calling the script |
If the three image variables are empty, the locally built images apply, see Image overrides and registry mirror .
Database, Redis, object storage
You normally do not need to set these variables. They exist for special cases, such as external services instead of the bundled containers; .env.example lists them commented out.
| Variable | Default | Meaning |
|---|---|---|
DB_NAME | opbweb | name of the database that MariaDB creates on first start |
DB_USER | opbweb | database user |
DATABASE_URL | mysql+asyncmy://opbweb@db:3306/opbweb | application connection (without password; the application inserts the password from the secret) |
DATABASE_URL_SYNC | mysql+pymysql://opbweb@db:3306/opbweb | connection for the migrations |
REDIS_URL | redis://redis:6379/0 | Redis (without password) |
S3_ENDPOINT | garage:3900 | Garage S3 endpoint |
S3_BUCKET | packages | bucket; garage-init creates it under this name |
S3_SECURE | false | TLS to the S3 endpoint |
Do not change DB_NAME and DB_USER after the first start. MariaDB creates the database and the user only when the volume is initialized for the first time. In addition, the default URLs contain the database name opbweb hard-coded; if you change DB_NAME, you must set DATABASE_URL and DATABASE_URL_SYNC accordingly.
Secrets
scripts/init-secrets.sh creates missing secrets in the directory secrets/. setup-opbweb.sh and start.sh call the script, start.sh on every start. It never overwrites existing files. Do not share the contents of secrets in tickets or logs.
The secrets/ directory has permissions 750, the files 444; the script sets them again on every run. Docker passes the secret files into the containers with the host permissions, and the processes there run as other users (MariaDB, Redis, the application as appuser with UID 1000). With 600 they could not read the files on a Linux host. The directory with 750 keeps other users of the host out.
| File | Generated as | Used by | Purpose |
|---|---|---|---|
db_password.txt | random value (hex) | db, api, worker | password of the MariaDB user opbweb |
db_root_password.txt | random value (hex) | db | MariaDB root password, also used for dumps |
redis_password.txt | random value (hex) | redis, api, worker | Redis password |
redis.acl | from redis_password.txt | redis | Redis ACL file |
jwt_secret_key.txt | random value (hex, 64 characters) | api | signature of the sign-in tokens |
opsi_key_public.pem | RSA key (3072 bit), public part | api, worker | encrypts the stored opsi password |
opsi_key_private.pem | RSA key (3072 bit), private part | worker | decrypts the opsi password; only the worker talks to opsi |
settings_encryption_key.txt | random value (hex, 64 characters) | worker | AES-256 key of versions before 0.18.6; the worker reads old entries with it and re-encrypts them at start |
garage_rpc_secret.txt | random value (hex, 64 characters) | garage | Garage RPC secret |
garage_admin_token.txt | random value (hex, 64 characters) | garage, garage-init | token for the Garage admin API |
admin_password.txt | 20 characters | api | password of the first administrator; shown once when the account is created |
ldap_bind_password.txt | empty; setup-opbweb.sh writes the password you enter into it | api | password of the LDAP service account |
The S3 credentials are not in secrets/. garage-init generates the key in Garage and writes it to the volume garage-credentials.
Changing secrets later
Not every secret can simply be replaced after the first start:
| File | Consequence of a change |
|---|---|
db_password.txt, db_root_password.txt | MariaDB adopts the passwords only on first initialization. After that, a changed file no longer matches the database; you would also have to change the password in MariaDB itself. |
redis_password.txt | start.sh creates redis.acl from the new password on the next start and restarts Redis. |
jwt_secret_key.txt | All sessions become invalid; everyone has to sign in again. |
opsi_key_private.pem, opsi_key_public.pem | Replace only as a pair (delete both, init-secrets.sh creates a new pair). The stored opsi passwords can no longer be decrypted afterwards; enter them again under Depots → config server for each config server. |
settings_encryption_key.txt | Only needed for entries from versions before 0.18.6; do not change it while such entries still need converting. |
garage_rpc_secret.txt, garage_admin_token.txt | Do not change; they belong to the existing object storage. |
admin_password.txt | Only applies while no user exists. Later, the administrator changes their password in the user interface. |
ldap_bind_password.txt | Not critical; restart the stack after the change. |
The files are read-only (444). To change one, briefly remove the protection and set it again afterwards, for example:
chmod u+w secrets/ldap_bind_password.txt
# edit the file
chmod 444 secrets/ldap_bind_password.txt
You can also enter the LDAP password again with ./setup-opbweb.sh. The services read the secrets at startup. After a change, restart the stack (./stop.sh, then ./start.sh).
TLS and COOKIE_SECURE
On every start, the frontend container decides based on the files in the directory certs/:
| Present | nginx serves |
|---|---|
certs/fullchain.pem and certs/key.pem | HTTPS on container port 443 (host: FRONTEND_HTTPS_PORT), HTTP/2, TLS 1.2 and 1.3; container port 80 (host: FRONTEND_HTTP_PORT) redirects with 301 to https://<host>:<FRONTEND_HTTPS_PORT> |
| one or both files missing | HTTP only on container port 80 (only in the development stack or with a manually created .env) |
Outside the development stack, setup-opbweb.sh always provides a certificate: supplied (fullchain.pem and key.pem next to the script), existing or self-signed, see Installation
. The container log shows the choice: „TLS-Zertifikat gefunden – HTTPS auf Port 443“ (TLS certificate found – HTTPS on port 443) or „Kein TLS-Zertifikat – HTTP auf Port 80“ (no TLS certificate – HTTP on port 80). fullchain.pem contains the server certificate with all intermediate certificates. The container gets the target port of the redirect as HTTPS_PORT (= FRONTEND_HTTPS_PORT).
With HTTPS, nginx sends the header Strict-Transport-Security (one year). Browsers that have opened oPBWeb via HTTPS once will refuse HTTP for this host name afterwards.
After replacing a certificate (put the new files next to the script and run ./setup-opbweb.sh --updateonly, or replace them directly in certs/), restarting the frontend is enough:
docker restart opbweb-frontend
In addition, nginx lets at most 10 sign-in attempts per minute and address through (briefly 10 more). Both limits require oPBWeb to see the real client address; if another proxy sits in front or Docker hides the addresses, all users share one address.
COOKIE_SECURE controls the Secure flag of the refresh cookie opbweb_refresh (valid for 7 days, httpOnly, SameSite=Strict). If the sign-in arrives over HTTPS, the API always sets the flag, even with false. For the setting:
| Operation | COOKIE_SECURE |
|---|---|
| HTTPS | true |
| HTTP only | false; with true, the browser does not store the cookie and the session cannot be renewed |
Ports
| Container port | Host port | Service |
|---|---|---|
| 80 | FRONTEND_HTTP_PORT (80) | nginx, HTTP |
| 443 | FRONTEND_HTTPS_PORT (443) | nginx, HTTPS |
Both ports are always published, but only one of them responds. All other services are not reachable from outside in production. The additional ports of the development stack are listed under Operation and Updates .
Outbound, the worker needs HTTPS on port 4447 to the opsi configserver and the depots.
Time limits and sizes
| Limit | Value | Configurable |
|---|---|---|
| Upload and install/uninstall on the depot | 3600 s | OPSI_INSTALL_TIMEOUT_SECONDS |
| Other opsi calls | 60 s | OPSI_TIMEOUT_SECONDS |
| Job runtime | 8 h (28800 s) | JOB_TIMEOUT_SECONDS |
| Parallel jobs in the worker | 10 | no |
| File manager calls API → worker | 60 s | no |
| Waiting for an occupied depot | up to 6 h | no |
| Upload size through nginx | 20 GB | no (client_max_body_size in frontend/nginx/common.inc) |
| nginx timeout to the API | 660 s without data transfer | no |
| Download links | 2 min | no |
| Text files in the editor | 5 MB (package files), 2 MB (file manager) | no |
OPSI_INSTALL_TIMEOUT_SECONDS limits a single opsi call, JOB_TIMEOUT_SECONDS the whole job – for an installation that is all depots one after the other, including waiting for busy depots (up to 6 h). Therefore set JOB_TIMEOUT_SECONDS well above that waiting time. If a job exceeds the limit or the worker is stopped, the job ends with “Cancelled: job time limit exceeded or worker stopped.” and counts as failed.
Image overrides and registry mirror
Without overrides, ./start.sh --build builds your own images locally and names them opbweb-backend:latest, opbweb-frontend:latest and opbweb-garage-init:1.0.0. With BACKEND_IMAGE, FRONTEND_IMAGE and GARAGE_INIT_IMAGE you use prebuilt images instead, for example from the GitLab registry (see Build and Delivery
). setup-opbweb.sh sets the three variables when you choose r at the images question; the prefix comes from OPBWEB_REGISTRY. Done manually, it looks like this (example, adapt it to your registry):
BACKEND_IMAGE=registry.example.com/gruppe/opsipackagebuilder-web/backend:<version>
FRONTEND_IMAGE=registry.example.com/gruppe/opsipackagebuilder-web/frontend:<version>
GARAGE_INIT_IMAGE=registry.example.com/gruppe/opsipackagebuilder-web/garage-init:latest
Then pull the images and start without --build:
docker login registry.example.com
docker compose pull
./start.sh