diff --git a/roles/opnform/README.md b/roles/opnform/README.md deleted file mode 100644 index 2dfad2d..0000000 --- a/roles/opnform/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# 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-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 - -OpnForm in self-hosted mode does **not** ship a pre-seeded admin user. -The first user to register becomes the owner of the default workspace, -and further public registration is disabled afterwards (additional -users must be invited via the Admin UI). - -This role supports two ways to create that first user: - -### Option A — automated bootstrap (recommended) - -Set `opnform_admin_email` and `opnform_admin_password` (ideally from -Vault / OpenBao). The role then POSTs to `/api/register` after the -API container is healthy, skipping the setup page entirely. The task -is idempotent: it does a login check first and only registers if the -user does not already exist. - -```yaml -opnform_admin_name: "Administrator" # default -opnform_admin_email: "admin@example.com" -opnform_admin_password: "{{ vault_opnform_admin_password }}" -``` - -Password rules enforced by OpnForm: minimum 8 characters, at least one -letter, one digit, and one of `@$!%*#?&-_+=.,:;<>^()[]{}|~`. - -### Option B — manual setup page - -Leave `opnform_admin_email` / `opnform_admin_password` empty. Visit -`opnform_base_url` and complete the setup page in the browser. - -## OIDC setup - -Set `opnform_oidc_enabled: true` and the role creates an -IdentityConnection on the admin's default workspace via -`POST /api/open/workspaces/{id}/oidc-connections`. OpnForm enforces a -single OIDC connection per workspace, so the task is idempotent (GETs -existing connections first and skips if any exist). - -**Prerequisite**: the admin bootstrap must be configured -(`opnform_admin_email` + `opnform_admin_password`). The OIDC API -requires an authenticated admin token; the role logs in with those -credentials to make the call. The validation block fails fast if OIDC -is enabled without admin credentials. - -### Required when `opnform_oidc_enabled: true` - -| Variable | Notes | -|---|---| -| `opnform_oidc_client_secret` | from your IdP, never commit | -| `opnform_oidc_domain` | email domain that triggers OIDC (e.g. `example.com`) | - -### Tunables (defaults shown) - -```yaml -opnform_oidc_issuer: "https://auth.digitalboard.ch/realms/Digitalboard" -opnform_oidc_client_id: "opnform-digitalboard" -opnform_oidc_client_name: "Digitalboard" # display name in UI -opnform_oidc_slug: "oidc" # used in /auth/{slug}/callback -opnform_oidc_scopes: [openid, profile, email, groups] -``` - -### Group → role mapping - -Two ways, the list takes precedence: - -```yaml -# Option 1: full list (any number of mappings) -opnform_oidc_group_role_mappings: - - idp_group: "opnform-admins" - role: admin - - idp_group: "opnform-editors" - role: editor - -# Option 2: convenience — single admin group -opnform_oidc_admin_group: "opnform-admins" # mapped to role=admin -``` - -Valid roles: `owner`, `admin`, `editor`, `member`. - -## 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 }}" -``` diff --git a/roles/opnform/defaults/main.yml b/roles/opnform/defaults/main.yml deleted file mode 100644 index 0f61c3a..0000000 --- a/roles/opnform/defaults/main.yml +++ /dev/null @@ -1,109 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# defaults file for opnform - -# Base directory configuration (inherited from base role or defined here) -docker_compose_base_dir: /etc/docker/compose -docker_volume_base_dir: /srv/data - -# opnform-specific configuration -opnform_service_name: opnform -opnform_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ opnform_service_name }}" -opnform_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ opnform_service_name }}" -opnform_storage_dir: "{{ opnform_docker_volume_dir }}/storage" -opnform_db_data_dir: "{{ opnform_docker_volume_dir }}/db" -opnform_redis_data_dir: "{{ opnform_docker_volume_dir }}/redis" - -# Service configuration -opnform_domain: "forms.local.test" -opnform_base_url: "https://forms.local.test" - -# Images -opnform_api_image: "jhumanj/opnform-api:latest" -opnform_client_image: "jhumanj/opnform-client:latest" -opnform_redis_image: "redis:7" -opnform_db_image: "postgres:16" -opnform_ingress_image: "nginx:1" - -# REQUIRED SECRETS — must be overridden per-inventory. -# Provide via OpenBao lookup, Ansible Vault or extra-vars. -# Never commit real keys to version control. -# -# Generate with: -# opnform_app_key: echo "base64:$(openssl rand -base64 32)" -# opnform_jwt_secret: openssl rand -hex 32 -# opnform_front_api_secret: openssl rand -hex 32 -# -# opnform_app_key MUST start with the prefix "base64:" — the validate -# task at the top of tasks/main.yml enforces this. -opnform_app_key: "" -opnform_jwt_secret: "" -opnform_front_api_secret: "" - -# Database credentials. opnform_db_password must be overridden; the -# validate task fails fast on an empty value. -opnform_db_name: "opnform" -opnform_db_user: "opnform" -opnform_db_password: "" - -# Admin bootstrap — when email+password are set, the role creates the -# first user via OpnForm's /api/register endpoint, skipping the -# self-hosted setup page. Leave both empty to keep the manual setup flow. -# Password must satisfy OpnForm's rules: min 8 chars, contain a letter, -# a digit and one of @$!%*#?&-_+=.,:;<>^()[]{}|~ -# Provide via OpenBao, Ansible Vault or extra-vars. -opnform_admin_name: "Administrator" -opnform_admin_email: "" -opnform_admin_password: "" -opnform_admin_hear_about_us: "ansible" - -# PHP configuration -opnform_php_memory_limit: "1G" -opnform_php_max_execution_time: "600" -opnform_php_upload_max_filesize: "64M" -opnform_php_post_max_size: "64M" - -# Nginx ingress -opnform_nginx_max_body_size: "64m" - -# Mail configuration (optional — defaults to log driver) -opnform_mail_mailer: "log" -opnform_mail_host: "" -opnform_mail_port: "" -opnform_mail_username: "" -opnform_mail_password: "" -opnform_mail_encryption: "" -opnform_mail_from_address: "noreply@digitalboard.ch" -opnform_mail_from_name: "OpnForm" - -# OIDC configuration — when enabled, the role auto-creates an -# IdentityConnection in the first workspace via OpnForm's API after the -# admin bootstrap. Requires opnform_admin_email/_password to be set -# (the API call needs an authenticated admin token). -opnform_oidc_enabled: false -opnform_oidc_issuer: "https://auth.digitalboard.ch/realms/Digitalboard" -opnform_oidc_client_id: "opnform-digitalboard" -opnform_oidc_client_secret: "" -opnform_oidc_client_name: "Digitalboard" -# OpnForm-side identifier used in /auth/{slug}/callback. Lowercase -# alphanumeric + hyphens, unique across all identity_connections. -opnform_oidc_slug: "oidc" -# Email domain that triggers OIDC login for matching users (e.g. users -# with @example.com emails are redirected to the IdP). Required when -# opnform_oidc_enabled is true. -opnform_oidc_domain: "" -opnform_oidc_scopes: - - openid - - profile - - email - - groups -# Convenience: maps a single IdP group to the OpnForm "admin" role. -# Ignored when opnform_oidc_group_role_mappings is non-empty. -opnform_oidc_admin_group: "opnform-admins" -# Full group-to-role mapping list. Takes precedence over the convenience -# var. Each item: {idp_group: "", role: "owner|admin|editor|member"} -opnform_oidc_group_role_mappings: [] - -# Traefik configuration -opnform_traefik_network: "proxy" -opnform_use_ssl: true diff --git a/roles/opnform/handlers/main.yml b/roles/opnform/handlers/main.yml deleted file mode 100644 index 1c0b422..0000000 --- a/roles/opnform/handlers/main.yml +++ /dev/null @@ -1,8 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# handlers file for opnform - -- name: restart opnform - community.docker.docker_compose_v2: - project_src: "{{ opnform_docker_compose_dir }}" - state: restarted diff --git a/roles/opnform/meta/argument_specs.yml b/roles/opnform/meta/argument_specs.yml deleted file mode 100644 index 9fbfc7a..0000000 --- a/roles/opnform/meta/argument_specs.yml +++ /dev/null @@ -1,220 +0,0 @@ ---- -argument_specs: - main: - short_description: Deploy OpnForm (api + ui + db + redis + ingress) via Docker Compose. - description: - - Renders a Compose stack for the full OpnForm setup (PHP-FPM api, - Nuxt ui, Postgres, Redis, nginx ingress) and exposes it through - Traefik. - - Optionally bootstraps the first admin user via the OpnForm - C(/api/register) endpoint (skipping the self-hosted setup page) - and provisions a single OIDC identity connection in the default - workspace via the workspace API. Both bootstraps are idempotent. - options: - docker_compose_base_dir: - type: path - default: /etc/docker/compose - docker_volume_base_dir: - type: path - default: /srv/data - opnform_service_name: - type: str - default: opnform - opnform_docker_compose_dir: - type: path - description: Defaults to C({{ docker_compose_base_dir }}/{{ opnform_service_name }}). - opnform_docker_volume_dir: - type: path - description: Defaults to C({{ docker_volume_base_dir }}/{{ opnform_service_name }}). - opnform_storage_dir: - type: path - description: OpnForm storage volume mounted into the api container. - opnform_db_data_dir: - type: path - opnform_redis_data_dir: - type: path - - opnform_domain: - type: str - default: forms.local.test - description: Hostname used in the traefik Host rule. - opnform_base_url: - type: str - default: https://forms.local.test - description: Public URL OpnForm uses for APP_URL and NUXT_PUBLIC_APP_URL. - - opnform_api_image: - type: str - default: jhumanj/opnform-api:latest - opnform_client_image: - type: str - default: jhumanj/opnform-client:latest - opnform_redis_image: - type: str - default: "redis:7" - opnform_db_image: - type: str - default: "postgres:16" - opnform_ingress_image: - type: str - default: "nginx:1" - - opnform_app_key: - type: str - required: true - description: - - Laravel application key. Must be prefixed with C(base64:). - Generate with C(echo "base64:$(openssl rand -base64 32)"). - Provide via OpenBao, Ansible Vault or extra-vars. - opnform_jwt_secret: - type: str - required: true - description: JWT signing secret. Generate with C(openssl rand -hex 32). - opnform_front_api_secret: - type: str - required: true - description: Shared secret between ui and api. Generate with C(openssl rand -hex 32). - - opnform_db_name: - type: str - default: opnform - opnform_db_user: - type: str - default: opnform - opnform_db_password: - type: str - required: true - - opnform_admin_name: - type: str - default: Administrator - opnform_admin_email: - type: str - default: '' - description: - - When non-empty (together with C(opnform_admin_password)) the role - bootstraps the first user via C(/api/register), skipping the - self-hosted setup page. Required when C(opnform_oidc_enabled=true). - opnform_admin_password: - type: str - default: '' - description: - - "Must satisfy OpnForm's policy: min 8 chars, letter + digit + - symbol from C(@$!%*#?&-_+=.,:;<>^()[]{}|~)." - opnform_admin_hear_about_us: - type: str - default: ansible - - opnform_php_memory_limit: - type: str - default: 1G - opnform_php_max_execution_time: - type: str - default: "600" - opnform_php_upload_max_filesize: - type: str - default: 64M - opnform_php_post_max_size: - type: str - default: 64M - opnform_nginx_max_body_size: - type: str - default: 64m - - opnform_mail_mailer: - type: str - default: log - choices: [log, smtp, ses, mailgun, postmark, sendmail] - opnform_mail_host: - type: str - default: '' - opnform_mail_port: - type: str - default: '' - opnform_mail_username: - type: str - default: '' - opnform_mail_password: - type: str - default: '' - opnform_mail_encryption: - type: str - default: '' - choices: ['', tls, ssl] - opnform_mail_from_address: - type: str - default: noreply@digitalboard.ch - opnform_mail_from_name: - type: str - default: OpnForm - - opnform_oidc_enabled: - type: bool - default: false - description: - - "When true the role calls the workspace API to create a single - OIDC C(identity_connection) on the default workspace after the - admin bootstrap. Requires C(opnform_admin_email) + - C(opnform_admin_password) so the role can authenticate. - Idempotent: skipped when any connection already exists." - opnform_oidc_issuer: - type: str - default: https://auth.digitalboard.ch/realms/Digitalboard - description: OIDC issuer URL. - opnform_oidc_client_id: - type: str - default: opnform-digitalboard - opnform_oidc_client_secret: - type: str - default: '' - description: Required when C(opnform_oidc_enabled=true). - opnform_oidc_client_name: - type: str - default: Digitalboard - description: Display name shown in the OpnForm UI. - opnform_oidc_slug: - type: str - default: oidc - description: - - OpnForm-side identifier used in C(/auth/{slug}/callback). Lowercase - alphanumeric + hyphens, unique across all C(identity_connections). - opnform_oidc_domain: - type: str - default: '' - description: - - Email domain that triggers OIDC for matching users. Required - when C(opnform_oidc_enabled=true). - opnform_oidc_scopes: - type: list - elements: str - default: [openid, profile, email, groups] - opnform_oidc_admin_group: - type: str - default: opnform-admins - description: - - Convenience setting that maps a single IdP group to the OpnForm - C(admin) role. Ignored when C(opnform_oidc_group_role_mappings) - is non-empty. - opnform_oidc_group_role_mappings: - type: list - elements: dict - default: [] - description: - - Full IdP-group -> OpnForm-role mapping. Takes precedence over - C(opnform_oidc_admin_group). - options: - idp_group: - type: str - required: true - description: Group name as it appears in the IdP groups claim. - role: - type: str - required: true - choices: [owner, admin, editor, member] - - opnform_traefik_network: - type: str - default: proxy - opnform_use_ssl: - type: bool - default: true diff --git a/roles/opnform/meta/main.yml b/roles/opnform/meta/main.yml deleted file mode 100644 index 8a56a7b..0000000 --- a/roles/opnform/meta/main.yml +++ /dev/null @@ -1,16 +0,0 @@ -#SPDX-License-Identifier: MIT-0 -galaxy_info: - author: Tobias Wüst - description: Deploy OpnForm self-hosted form builder via Docker Compose behind Traefik - company: Digitalboard - license: MIT-0 - min_ansible_version: "2.15" - - galaxy_tags: - - opnform - - forms - - docker - - traefik - - oidc - -dependencies: [] diff --git a/roles/opnform/tasks/main.yml b/roles/opnform/tasks/main.yml deleted file mode 100644 index 68e093b..0000000 --- a/roles/opnform/tasks/main.yml +++ /dev/null @@ -1,265 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# tasks file for opnform - -# ===================================================================== -# 0. VALIDATION -# ===================================================================== - -- name: Validate required secrets - ansible.builtin.assert: - that: - - opnform_app_key | length > 0 - - opnform_app_key is match('^base64:[A-Za-z0-9+/=]+$') - - opnform_jwt_secret | length > 0 - - opnform_front_api_secret | length > 0 - - opnform_db_password | length > 0 - fail_msg: >- - OpnForm requires opnform_app_key (prefix 'base64:'), opnform_jwt_secret, - opnform_front_api_secret and opnform_db_password. - Generate with: openssl rand -base64 32 - The app_key MUST be prefixed with "base64:" - Provide via OpenBao, Ansible Vault or extra-vars. - success_msg: Secrets validation passed - -- name: Validate OIDC configuration when enabled - ansible.builtin.assert: - that: - - opnform_oidc_client_secret | length > 0 - - opnform_oidc_domain | length > 0 - - opnform_admin_email | length > 0 - - opnform_admin_password | length > 0 - fail_msg: >- - When opnform_oidc_enabled is true, you must set: - - opnform_oidc_client_secret - - opnform_oidc_domain (email domain that triggers OIDC) - - opnform_admin_email / opnform_admin_password - (the OIDC API requires an authenticated admin; the role logs in - with these credentials to POST the connection) - when: opnform_oidc_enabled | bool - -# ===================================================================== -# 1. PREPARATION -# ===================================================================== - -- name: Ensure required packages are installed - ansible.builtin.package: - name: - - python3-docker - state: present - -- name: Create docker compose directory - ansible.builtin.file: - path: "{{ opnform_docker_compose_dir }}" - state: directory - mode: '0755' - -- name: Create OpnForm data directories - ansible.builtin.file: - path: "{{ item }}" - state: directory - mode: "0755" - loop: - - "{{ opnform_docker_volume_dir }}" - - "{{ opnform_storage_dir }}" - - "{{ opnform_db_data_dir }}" - - "{{ opnform_redis_data_dir }}" - -# ===================================================================== -# 2. CONFIGURATION FILES -# ===================================================================== - -- name: Deploy nginx ingress configuration - ansible.builtin.template: - src: nginx.conf.j2 - dest: "{{ opnform_docker_compose_dir }}/nginx.conf" - mode: '0644' - notify: restart opnform - -- name: Deploy docker-compose file - ansible.builtin.template: - src: docker-compose.yml.j2 - dest: "{{ opnform_docker_compose_dir }}/docker-compose.yml" - mode: '0644' - notify: restart opnform - -# ===================================================================== -# 3. CONTAINER STARTUP -# ===================================================================== - -- name: Start opnform containers - community.docker.docker_compose_v2: - project_src: "{{ opnform_docker_compose_dir }}" - state: present - wait: true - wait_timeout: 180 - -# ===================================================================== -# 4. WAIT FOR API READINESS -# ===================================================================== - -- name: Wait for API container to be healthy - ansible.builtin.command: - cmd: docker inspect --format='{% raw %}{{.State.Health.Status}}{% endraw %}' opnform-api - register: api_health - until: api_health.stdout == "healthy" - retries: 30 - delay: 10 - changed_when: false - -# ===================================================================== -# 5. ADMIN BOOTSTRAP (optional) -# ===================================================================== -# Skips the self-hosted setup page by registering the first user via -# OpnForm's /api/register endpoint. Idempotent: a successful login -# attempt with the same credentials means the user already exists. - -- name: Check if OpnForm admin user already exists - ansible.builtin.uri: - url: "https://127.0.0.1/api/login" - method: POST - headers: - Host: "{{ opnform_domain }}" - body_format: json - body: - email: "{{ opnform_admin_email }}" - password: "{{ opnform_admin_password }}" - status_code: [200, 401, 422] - validate_certs: false - register: opnform_admin_login - when: - - opnform_admin_email | length > 0 - - opnform_admin_password | length > 0 - -- name: Create OpnForm admin user via /api/register - ansible.builtin.uri: - url: "https://127.0.0.1/api/register" - method: POST - headers: - Host: "{{ opnform_domain }}" - body_format: json - body: - name: "{{ opnform_admin_name }}" - email: "{{ opnform_admin_email }}" - password: "{{ opnform_admin_password }}" - password_confirmation: "{{ opnform_admin_password }}" - hear_about_us: "{{ opnform_admin_hear_about_us }}" - status_code: [200, 201] - validate_certs: false - no_log: true - when: - - opnform_admin_email | length > 0 - - opnform_admin_password | length > 0 - - opnform_admin_login.status != 200 - -# ===================================================================== -# 6. OIDC IDENTITY CONNECTION (optional) -# ===================================================================== -# Creates a single OIDC connection on the admin's default workspace. -# OpnForm enforces one OIDC connection per workspace, so this block is -# idempotent: we GET existing connections first and skip if any exists. - -- name: Log in as admin to obtain OIDC API token - ansible.builtin.uri: - url: "https://127.0.0.1/api/login" - method: POST - headers: - Host: "{{ opnform_domain }}" - body_format: json - body: - email: "{{ opnform_admin_email }}" - password: "{{ opnform_admin_password }}" - status_code: 200 - validate_certs: false - register: opnform_oidc_token - no_log: true - when: opnform_oidc_enabled | bool - -- name: Fetch admin's workspaces - ansible.builtin.uri: - url: "https://127.0.0.1/api/open/workspaces" - method: GET - headers: - Host: "{{ opnform_domain }}" - Authorization: "Bearer {{ opnform_oidc_token.json.token }}" - status_code: 200 - validate_certs: false - register: opnform_workspaces - no_log: true - when: opnform_oidc_enabled | bool - -- name: Fetch existing OIDC connections for the default workspace - ansible.builtin.uri: - url: "https://127.0.0.1/api/open/workspaces/{{ opnform_workspaces.json[0].id }}/oidc-connections" - method: GET - headers: - Host: "{{ opnform_domain }}" - Authorization: "Bearer {{ opnform_oidc_token.json.token }}" - status_code: 200 - validate_certs: false - register: opnform_existing_oidc - no_log: true - when: opnform_oidc_enabled | bool - -- name: Resolve OIDC group-role mappings - ansible.builtin.set_fact: - _opnform_oidc_group_role_mappings: >- - {{ - opnform_oidc_group_role_mappings - if (opnform_oidc_group_role_mappings | length > 0) - else - ([{'idp_group': opnform_oidc_admin_group, 'role': 'admin'}] - if (opnform_oidc_admin_group | length > 0) else []) - }} - when: opnform_oidc_enabled | bool - -- name: Create OIDC identity connection - ansible.builtin.uri: - url: "https://127.0.0.1/api/open/workspaces/{{ opnform_workspaces.json[0].id }}/oidc-connections" - method: POST - headers: - Host: "{{ opnform_domain }}" - Authorization: "Bearer {{ opnform_oidc_token.json.token }}" - body_format: json - body: - name: "{{ opnform_oidc_client_name }}" - slug: "{{ opnform_oidc_slug }}" - domain: "{{ opnform_oidc_domain }}" - issuer: "{{ opnform_oidc_issuer }}" - client_id: "{{ opnform_oidc_client_id }}" - client_secret: "{{ opnform_oidc_client_secret }}" - scopes: "{{ opnform_oidc_scopes }}" - enabled: true - options: - require_state: true - group_role_mappings: "{{ _opnform_oidc_group_role_mappings }}" - status_code: [201] - validate_certs: false - no_log: true - when: - - opnform_oidc_enabled | bool - - opnform_existing_oidc.json | length == 0 - -- name: Display deployment info - ansible.builtin.debug: - msg: |- - OpnForm deployed at {{ opnform_base_url }} - - {% if opnform_admin_email | length > 0 %} - Admin user bootstrapped: - Email: {{ opnform_admin_email }} - Password: (from opnform_admin_password) - {% else %} - No admin bootstrap configured — visit {{ opnform_base_url }} and - complete the self-hosted setup page to create the first user. - Set opnform_admin_email + opnform_admin_password to automate this. - {% endif %} - - {% if opnform_oidc_enabled %} - OIDC: connection "{{ opnform_oidc_client_name }}" bootstrapped - (slug: {{ opnform_oidc_slug }}, domain: {{ opnform_oidc_domain }}) - Users with @{{ opnform_oidc_domain }} addresses will be - redirected to {{ opnform_oidc_issuer }} on login. - {% else %} - OIDC: disabled (set opnform_oidc_enabled=true to auto-configure) - {% endif %} diff --git a/roles/opnform/templates/docker-compose.yml.j2 b/roles/opnform/templates/docker-compose.yml.j2 deleted file mode 100644 index de88a33..0000000 --- a/roles/opnform/templates/docker-compose.yml.j2 +++ /dev/null @@ -1,189 +0,0 @@ -#---------------------------------------------------------------------# -# OpnForm — Beautiful open-source form builder # -#---------------------------------------------------------------------# -services: - api: &api-service - image: {{ opnform_api_image }} - container_name: opnform-api - restart: unless-stopped - volumes: - - {{ opnform_storage_dir }}:/usr/share/nginx/html/storage:rw - environment: &api-env - APP_ENV: production - APP_KEY: "{{ opnform_app_key }}" - APP_URL: "{{ opnform_base_url }}" - APP_DEBUG: "false" - SELF_HOSTED: "true" - - LOG_CHANNEL: errorlog - LOG_LEVEL: info - - DB_CONNECTION: pgsql - DB_HOST: db - DB_PORT: "5432" - DB_DATABASE: "{{ opnform_db_name }}" - DB_USERNAME: "{{ opnform_db_user }}" - DB_PASSWORD: "{{ opnform_db_password }}" - - REDIS_HOST: redis - REDIS_PORT: "6379" - - CACHE_STORE: redis - CACHE_DRIVER: redis - QUEUE_CONNECTION: redis - SESSION_DRIVER: redis - SESSION_LIFETIME: "120" - BROADCAST_CONNECTION: log - - FILESYSTEM_DISK: local - FILESYSTEM_DRIVER: local - LOCAL_FILESYSTEM_VISIBILITY: public - - MAIL_MAILER: "{{ opnform_mail_mailer }}" - MAIL_HOST: "{{ opnform_mail_host }}" - MAIL_PORT: "{{ opnform_mail_port }}" - MAIL_USERNAME: "{{ opnform_mail_username }}" - MAIL_PASSWORD: "{{ opnform_mail_password }}" - MAIL_ENCRYPTION: "{{ opnform_mail_encryption }}" - MAIL_FROM_ADDRESS: "{{ opnform_mail_from_address }}" - MAIL_FROM_NAME: "{{ opnform_mail_from_name }}" - - JWT_TTL: "1440" - JWT_SECRET: "{{ opnform_jwt_secret }}" - - PHP_MEMORY_LIMIT: "{{ opnform_php_memory_limit }}" - PHP_MAX_EXECUTION_TIME: "{{ opnform_php_max_execution_time }}" - PHP_UPLOAD_MAX_FILESIZE: "{{ opnform_php_upload_max_filesize }}" - PHP_POST_MAX_SIZE: "{{ opnform_php_post_max_size }}" - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "php /usr/share/nginx/html/artisan about || exit 1"] - interval: 30s - timeout: 15s - retries: 3 - start_period: 60s - networks: - - opnform-internal - - api-worker: - <<: *api-service - container_name: opnform-api-worker - command: ["php", "artisan", "queue:work"] - environment: - <<: *api-env - IS_API_WORKER: "true" - healthcheck: - test: ["CMD-SHELL", "pgrep -f 'php artisan queue:work' > /dev/null || exit 1"] - interval: 60s - timeout: 10s - retries: 3 - start_period: 30s - - api-scheduler: - <<: *api-service - container_name: opnform-api-scheduler - command: ["php", "artisan", "schedule:work"] - healthcheck: - test: - - "CMD-SHELL" - - "php /usr/share/nginx/html/artisan app:scheduler-status --mode=check --max-minutes=3 || exit 1" - interval: 60s - timeout: 30s - retries: 3 - start_period: 70s - - ui: - image: {{ opnform_client_image }} - container_name: opnform-ui - restart: unless-stopped - environment: - NUXT_PUBLIC_APP_URL: "{{ opnform_base_url }}" - NUXT_PUBLIC_API_BASE: "/api" - NUXT_PRIVATE_API_BASE: "http://ingress/api" - NUXT_PUBLIC_ENV: production - FRONT_API_SECRET: "{{ opnform_front_api_secret }}" - depends_on: - api: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "wget --spider -q http://localhost:3000/login || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 45s - networks: - - opnform-internal - - redis: - image: {{ opnform_redis_image }} - container_name: opnform-redis - restart: unless-stopped - volumes: - - {{ opnform_redis_data_dir }}:/data - healthcheck: - test: ["CMD-SHELL", "redis-cli ping | grep PONG"] - interval: 30s - timeout: 5s - networks: - - opnform-internal - - db: - image: {{ opnform_db_image }} - container_name: opnform-db - restart: unless-stopped - environment: - POSTGRES_DB: "{{ opnform_db_name }}" - POSTGRES_USER: "{{ opnform_db_user }}" - POSTGRES_PASSWORD: "{{ opnform_db_password }}" - volumes: - - {{ opnform_db_data_dir }}:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U {{ opnform_db_user }}"] - interval: 30s - timeout: 5s - networks: - - opnform-internal - - ingress: - image: {{ opnform_ingress_image }} - container_name: opnform-ingress - restart: unless-stopped - volumes: - - ./nginx.conf:/etc/nginx/templates/default.conf.template:ro - environment: - NGINX_MAX_BODY_SIZE: "{{ opnform_nginx_max_body_size }}" - depends_on: - api: - condition: service_started - ui: - condition: service_started - healthcheck: - test: ["CMD-SHELL", "nginx -t && curl -f http://localhost/ || exit 1"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - networks: - - opnform-internal - - {{ opnform_traefik_network }} - labels: - - traefik.enable=true - - traefik.docker.network={{ opnform_traefik_network }} - - traefik.http.routers.{{ opnform_service_name }}.rule=Host(`{{ opnform_domain }}`) -{% if opnform_use_ssl %} - - traefik.http.routers.{{ opnform_service_name }}.entrypoints=websecure - - traefik.http.routers.{{ opnform_service_name }}.tls=true -{% else %} - - traefik.http.routers.{{ opnform_service_name }}.entrypoints=web -{% endif %} - - traefik.http.services.{{ opnform_service_name }}.loadbalancer.server.port=80 - -networks: - opnform-internal: - driver: bridge - {{ opnform_traefik_network }}: - external: true diff --git a/roles/opnform/templates/nginx.conf.j2 b/roles/opnform/templates/nginx.conf.j2 deleted file mode 100644 index fa3193b..0000000 --- a/roles/opnform/templates/nginx.conf.j2 +++ /dev/null @@ -1,43 +0,0 @@ -map $original_uri $api_uri { - ~^/api(/.*$) $1; - default $original_uri; -} - -server { - listen 80; - server_name {{ opnform_domain }}; - root /app/public; - - client_max_body_size {% raw %}${NGINX_MAX_BODY_SIZE}{% endraw %}; - - access_log /dev/stdout; - error_log /dev/stderr error; - - index index.html index.htm index.php; - - location / { - proxy_http_version 1.1; - proxy_pass http://ui:3000; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - } - - location ~/(api|open|local\/temp|forms\/assets)/ { - set $original_uri $uri; - try_files $uri $uri/ /index.php$is_args$args; - } - - location ~ \.php$ { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass api:9000; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php; - fastcgi_param REQUEST_URI $api_uri; - } -} diff --git a/roles/opnform/tests/inventory b/roles/opnform/tests/inventory deleted file mode 100644 index 712db59..0000000 --- a/roles/opnform/tests/inventory +++ /dev/null @@ -1,2 +0,0 @@ -#SPDX-License-Identifier: MIT-0 -localhost diff --git a/roles/opnform/tests/test.yml b/roles/opnform/tests/test.yml deleted file mode 100644 index 3ff9caa..0000000 --- a/roles/opnform/tests/test.yml +++ /dev/null @@ -1,6 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -- hosts: localhost - remote_user: root - roles: - - opnform \ No newline at end of file diff --git a/roles/opnform/vars/main.yml b/roles/opnform/vars/main.yml deleted file mode 100644 index 94900f8..0000000 --- a/roles/opnform/vars/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -#SPDX-License-Identifier: MIT-0 ---- -# vars file for opnform \ No newline at end of file