feat(opnform)!: add admin and OIDC bootstrap, rename role to lowercase
Rename roles/OpnForm → roles/opnform so the role resolves as
digitalboard.core.opnform (Ansible collection convention is
lowercase). Update tests/test.yml reference accordingly.
Add automated admin user creation via POST /api/register, gated on
opnform_admin_email + opnform_admin_password. Idempotent through a
prior login probe. Without these vars the manual setup page flow is
preserved.
Add automated OIDC IdentityConnection setup via the per-workspace
/api/open/workspaces/{id}/oidc-connections endpoint, gated on
opnform_oidc_enabled. Hard-coupled to the admin bootstrap (the API
requires an authenticated admin token); validation block fails fast
if OIDC is enabled without admin credentials. Supports both an
explicit opnform_oidc_group_role_mappings list and a fallback
opnform_oidc_admin_group convenience var.
Convert opnform_oidc_scopes from space-separated string to YAML list
to match OpnForm's API expectation. Rewrite README "First login" and
"OIDC setup" sections to reflect that self-hosted OpnForm does not
ship a pre-seeded admin and to document the new bootstrap paths.
BREAKING CHANGE: opnform_oidc_scopes changed from space-separated
string to YAML list. Inventories that override it must update from
"openid profile email" to [openid, profile, email].
This commit is contained in:
parent
3f90843f97
commit
2341815daf
11 changed files with 366 additions and 145 deletions
189
roles/opnform/templates/docker-compose.yml.j2
Normal file
189
roles/opnform/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#---------------------------------------------------------------------#
|
||||
# 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
|
||||
43
roles/opnform/templates/nginx.conf.j2
Normal file
43
roles/opnform/templates/nginx.conf.j2
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue