126 lines
4.5 KiB
Markdown
126 lines
4.5 KiB
Markdown
# opnform
|
|
|
|
Deploy [OpnForm](https://github.com/OpnForm/OpnForm) as a self-contained
|
|
Docker Compose stack behind Traefik.
|
|
|
|
## What this role does
|
|
|
|
- Deploys the full official OpnForm stack: `api`, `api-worker`, `api-scheduler`,
|
|
`ui`, `db` (Postgres), `redis`, and `ingress` (nginx)
|
|
- Configures all environment variables for self-hosted production use
|
|
- Integrates the ingress container with an existing Traefik proxy network
|
|
- Waits for the API container to become healthy before returning
|
|
|
|
## What this role does NOT do (stage 1)
|
|
|
|
- Does not pre-create an admin user (use the default credentials below)
|
|
- Does not pre-configure OIDC / identity_connections — set up via Admin UI
|
|
|
|
## Architecture note: why two reverse proxies?
|
|
|
|
```
|
|
Browser → Traefik (TLS, host routing) → ingress-nginx → api (PHP-FPM) / ui (Nuxt)
|
|
```
|
|
|
|
The `ingress` container looks like a redundant proxy next to Traefik but
|
|
does a different job. OpnForm's `api` image is **PHP-FPM only** — it
|
|
speaks the FastCGI protocol on port 9000, not HTTP. Traefik cannot
|
|
translate FastCGI, so the ingress nginx is required to:
|
|
|
|
- Translate HTTP `/api/*` requests into FastCGI calls to `api:9000`
|
|
- Rewrite request URIs via the `$api_uri` map
|
|
- Set Laravel-specific FastCGI params (`SCRIPT_FILENAME`, `REQUEST_URI`)
|
|
- Reverse-proxy `/` to the Nuxt UI container on port 3000
|
|
|
|
Both containers run on the same Docker network on the same host, so the
|
|
performance overhead of the extra hop is negligible (in-kernel memory
|
|
copy, not a real network round-trip). Removing the ingress would require
|
|
a custom OpnForm image with a built-in HTTP server, which is out of
|
|
scope for this role.
|
|
|
|
## Required variables
|
|
|
|
Provide via OpenBao, Ansible Vault, or extra-vars. **Never commit real
|
|
secrets to version control.**
|
|
|
|
| Variable | Format | Generate with |
|
|
|---|---|---|
|
|
| `opnform_app_key` | `base64:<32 bytes base64>` | `echo "base64:$(openssl rand -base64 32)"` |
|
|
| `opnform_jwt_secret` | 32 bytes base64 | `openssl rand -base64 32` |
|
|
| `opnform_front_api_secret` | 32 bytes base64 | `openssl rand -base64 32` |
|
|
| `opnform_db_password` | strong password | `openssl rand -base64 24` |
|
|
|
|
When `opnform_oidc_enabled` is `true`:
|
|
|
|
| Variable | Source |
|
|
|---|---|
|
|
| `opnform_oidc_client_secret` | from your Keycloak/Authentik client |
|
|
|
|
The `assert` task at the top of the role will fail fast if any secret is
|
|
missing or malformed.
|
|
|
|
## First login
|
|
|
|
After the role completes, OpnForm seeds a default admin user. Visit
|
|
the URL in `opnform_base_url` and log in with:
|
|
|
|
- Email: `admin@opnform.com`
|
|
- Password: `password`
|
|
|
|
On first login OpnForm will prompt you to change email and password.
|
|
Self-hosted instances disable public registration after this — invite
|
|
further users via the Admin UI.
|
|
|
|
### If the login does not respond
|
|
|
|
The DB seed may have failed. Re-run it manually:
|
|
|
|
```bash
|
|
cd /etc/docker/compose/opnform
|
|
docker compose exec api php artisan migrate:refresh --seed
|
|
docker compose exec api php artisan app:init-project
|
|
```
|
|
|
|
## OIDC setup (stage 2, not yet automated)
|
|
|
|
Manual setup via the Admin UI is currently the supported path:
|
|
|
|
1. Settings → Identity Connections → Add Connection
|
|
2. Provider: OIDC
|
|
3. Issuer: `https://auth.digitalboard.ch/realms/Digitalboard`
|
|
4. Client ID / Secret: from your Keycloak client
|
|
5. Add Group Role Mapping: Entra/Keycloak group Object ID → OpnForm role
|
|
|
|
Direct DB manipulation of `identity_connections` / `group_role_mappings`
|
|
is possible but fragile across OpnForm versions. A future iteration of
|
|
this role may automate it.
|
|
|
|
## Example playbook
|
|
|
|
```yaml
|
|
- name: Deploy OpnForm service
|
|
hosts: opnform_servers
|
|
become: true
|
|
roles:
|
|
- digitalboard.core.opnform
|
|
```
|
|
|
|
With inventory variables:
|
|
|
|
```yaml
|
|
# group_vars/opnform_servers.yml
|
|
opnform_domain: forms.digitalboard.ch
|
|
opnform_base_url: "https://forms.digitalboard.ch"
|
|
opnform_app_key: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
|
'digitalboard/opnform',
|
|
mount_point='kv').data.data.app_key }}"
|
|
opnform_jwt_secret: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
|
'digitalboard/opnform',
|
|
mount_point='kv').data.data.jwt_secret }}"
|
|
opnform_front_api_secret: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
|
'digitalboard/opnform',
|
|
mount_point='kv').data.data.front_api_secret }}"
|
|
opnform_db_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
|
'digitalboard/opnform',
|
|
mount_point='kv').data.data.db_password }}"
|
|
```
|