diff --git a/roles/web-app-suitecrm/config/main.yml b/roles/web-app-suitecrm/config/main.yml index b0eac509..8ddddfff 100644 --- a/roles/web-app-suitecrm/config/main.yml +++ b/roles/web-app-suitecrm/config/main.yml @@ -27,8 +27,10 @@ docker: database: enabled: true suitecrm: - image: "bitnami/suitecrm" - version: "8" + image: "php" + version: "8.2-apache" + # Git tag from https://github.com/SuiteCRM/SuiteCRM + app_version: "7.14.8" name: "suitecrm" cpus: 1.5 mem_reservation: 1.2g diff --git a/roles/web-app-suitecrm/files/docker-entrypoint-suitecrm.sh b/roles/web-app-suitecrm/files/docker-entrypoint-suitecrm.sh new file mode 100644 index 00000000..3fc687cf --- /dev/null +++ b/roles/web-app-suitecrm/files/docker-entrypoint-suitecrm.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Minimal SuiteCRM entrypoint for Infinito.Nexus +set -eu + +APP_DIR="/var/www/html" +WEB_USER="www-data" +WEB_GROUP="www-data" + +log() { printf '%s %s\n' "[suitecrm-entrypoint]" "$*" >&2; } + +# Ensure application directory exists +if [ ! -d "$APP_DIR" ]; then + log "ERROR: Application directory '$APP_DIR' does not exist." + exit 1 +fi + +# Fix permissions (best-effort, idempotent enough for small instances) +log "Adjusting permissions on ${APP_DIR} (this may take some time on first run)..." +chown -R "$WEB_USER:$WEB_GROUP" "$APP_DIR" +find "$APP_DIR" -type d -exec chmod 755 {} \; +find "$APP_DIR" -type f -exec chmod 644 {} \; + +# Writable directories +for d in cache custom modules themes upload; do + if [ -d "${APP_DIR}/${d}" ]; then + chmod -R 775 "${APP_DIR}/${d}" + fi +done + +# Add a simple healthcheck file +echo "OK" > "${APP_DIR}/healthcheck.html" +chown "$WEB_USER:$WEB_GROUP" "${APP_DIR}/healthcheck.html" + +# (Optional) place for future auto-config (DB, LDAP, OIDC) by editing config.php + +# Hand off to CMD (Apache in foreground by default) +if [ "$#" -gt 0 ]; then + log "Executing CMD: $*" + exec "$@" +fi + +# Default: start Apache HTTPD in foreground +if command -v apache2-foreground >/dev/null 2>&1; then + log "Starting apache2-foreground..." + exec apache2-foreground +fi + +log "No known server command found; keeping container alive." +exec tail -f /dev/null diff --git a/roles/web-app-suitecrm/schema/main.yml b/roles/web-app-suitecrm/schema/main.yml index e69de29b..1e69feaa 100644 --- a/roles/web-app-suitecrm/schema/main.yml +++ b/roles/web-app-suitecrm/schema/main.yml @@ -0,0 +1,5 @@ +credentials: + administrator_password: + description: "Initial password for the SuiteCRM user" + algorithm: "sha256" + validation: "^[a-f0-9]{64}$" \ No newline at end of file diff --git a/roles/web-app-suitecrm/tasks/main.yml b/roles/web-app-suitecrm/tasks/main.yml index 2b422fb7..4bfdac92 100644 --- a/roles/web-app-suitecrm/tasks/main.yml +++ b/roles/web-app-suitecrm/tasks/main.yml @@ -1,6 +1,16 @@ ---- - name: "Load docker, db and proxy for {{ application_id }}" include_role: name: sys-stk-full-stateful vars: - docker_compose_flush_handlers: true + docker_compose_flush_handlers: false + +- name: "Deploy '{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS }}'" + copy: + src: "{{ SUITECRM_ENTRYPOINT_SCRIPT_FILE }}" + dest: "{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS }}" + notify: + - docker compose up + - docker compose build + +- name: "Docker Compose Up for '{{ application_id }}'" + meta: flush_handlers \ No newline at end of file diff --git a/roles/web-app-suitecrm/templates/Dockerfile.j2 b/roles/web-app-suitecrm/templates/Dockerfile.j2 index 8337c315..80bff6a9 100644 --- a/roles/web-app-suitecrm/templates/Dockerfile.j2 +++ b/roles/web-app-suitecrm/templates/Dockerfile.j2 @@ -1 +1,51 @@ -FROM "{{ SUITECRM_IMAGE }}:{{ SUITECRM_VERSION }}" +FROM "{{ SUITECRM_BASE_IMAGE }}:{{ SUITECRM_BASE_VERSION }}" + +# Install required system packages and PHP extensions +RUN apt-get update && apt-get install -y \ + git \ + wget \ + unzip \ + libpng-dev \ + libzip-dev \ + libicu-dev \ + libonig-dev \ + libxml2-dev \ + && docker-php-ext-install \ + mysqli \ + gd \ + zip \ + intl \ + mbstring \ + soap \ + opcache \ + && rm -rf /var/lib/apt/lists/* + +# Install Apache modules +RUN a2enmod rewrite headers + +WORKDIR /var/www/html + +# Install Composer +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# Download SuiteCRM source +RUN set -eux; \ + SUITECRM_TAG="v{{ SUITECRM_APP_VERSION }}"; \ + wget -qO /tmp/suitecrm.tar.gz "https://github.com/SuiteCRM/SuiteCRM/archive/refs/tags/${SUITECRM_TAG}.tar.gz"; \ + tar --strip-components=1 -xzf /tmp/suitecrm.tar.gz -C /var/www/html; \ + rm /tmp/suitecrm.tar.gz + +# Install PHP dependencies via Composer (critical!) +RUN set -eux; \ + composer install \ + --no-dev \ + --prefer-dist \ + --no-interaction \ + --optimize-autoloader + +# Copy entrypoint +COPY {{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_REL }} {{ SUITECRM_ENTRYPOINT_SCRIPT_DOCKER }} +RUN chmod +x {{ SUITECRM_ENTRYPOINT_SCRIPT_DOCKER }} + +ENTRYPOINT ["{{ SUITECRM_ENTRYPOINT_SCRIPT_DOCKER }}"] +CMD ["apache2-foreground"] diff --git a/roles/web-app-suitecrm/templates/docker-compose.yml.j2 b/roles/web-app-suitecrm/templates/docker-compose.yml.j2 index ef0ab81f..4bb54bbb 100644 --- a/roles/web-app-suitecrm/templates/docker-compose.yml.j2 +++ b/roles/web-app-suitecrm/templates/docker-compose.yml.j2 @@ -15,7 +15,7 @@ {% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %} volumes: - - data:/bitnami/suitecrm + - data:/var/www/html/ {% include 'roles/docker-compose/templates/volumes.yml.j2' %} data: diff --git a/roles/web-app-suitecrm/templates/env.j2 b/roles/web-app-suitecrm/templates/env.j2 index c6b792ac..8aa9a01d 100644 --- a/roles/web-app-suitecrm/templates/env.j2 +++ b/roles/web-app-suitecrm/templates/env.j2 @@ -1,83 +1,73 @@ ############################################# -# SuiteCRM Docker Environment (.env) – ENGLISH -# Based on bitnami/suitecrm environment variables +# SuiteCRM Docker Environment (.env) +# Built for Infinito.Nexus Roles ############################################# # ------------------------------------------------ -# Database connection (Bitnami-style) +# Database # ------------------------------------------------ -MARIADB_HOST={{ database_host }} -MARIADB_PORT={{ database_port }} -MARIADB_USER={{ database_username }} -MARIADB_PASSWORD={{ database_password }} +SUITECRM_DB_HOST={{ database_host }} +SUITECRM_DB_PORT={{ database_port }} +SUITECRM_DB_NAME={{ database_name }} +SUITECRM_DB_USER={{ database_username }} +SUITECRM_DB_PASSWORD={{ database_password }} # ------------------------------------------------ # Initial admin account +# (SuiteCRM installer will use this; can also be set in config_override.php) # ------------------------------------------------ -SUITECRM_USERNAME={{ applications | get_app_conf(application_id, 'users.administrator.username') }} -SUITECRM_PASSWORD={{ applications | get_app_conf(application_id, 'credentials.administrator_password') }} -SUITECRM_EMAIL={{ users['contact'].email }} -SUITECRM_HOST={{ domains | get_domain(application_id) }} +SUITECRM_ADMIN_USERNAME={{ applications | get_app_conf(application_id, 'users.administrator.username') }} +SUITECRM_ADMIN_PASSWORD={{ applications | get_app_conf(application_id, 'credentials.administrator_password') }} +SUITECRM_ADMIN_EMAIL={{ users['contact'].email }} + +# Public base URL of the SuiteCRM instance +SUITECRM_URL={{ SUITECRM_URL }} # ------------------------------------------------ -# Logger / basic options -# (SuiteCRM/Bitnami will log to stdout/stderr by default) -# ------------------------------------------------ - -# ------------------------------------------------ -# System SMTP settings +# SMTP (Mailu) # ------------------------------------------------ SUITECRM_SMTP_HOST={{ SYSTEM_EMAIL.HOST }} SUITECRM_SMTP_PORT={{ SYSTEM_EMAIL.PORT }} SUITECRM_SMTP_USER={{ users['contact'].email }} SUITECRM_SMTP_PASSWORD={{ users['contact'].mailu_token }} SUITECRM_SMTP_PROTOCOL={{ "TLS" if SYSTEM_EMAIL.START_TLS else "SSL" }} +SUITECRM_EMAIL_FROM_NAME={{ applications | get_app_conf(application_id, 'email.from_name') }} # ------------------------------------------------ -# LDAP settings (native SuiteCRM 8+ via Symfony) -# Applied only if the feature flag is true +# LDAP settings (optional) # ------------------------------------------------ -{% if applications | get_app_conf(application_id, 'features.ldap') %} -AUTH_TYPE=ldap - -# Base LDAP connection -LDAP_HOST={{ LDAP.SERVER.DOMAIN }} -LDAP_PORT={{ LDAP.SERVER.PORT }} -# Allowed values: none, tls, ssl (see SuiteCRM docs) -LDAP_ENCRYPTION={{ LDAP.SERVER.SECURITY | lower if LDAP.SERVER.SECURITY else "none" }} -LDAP_PROTOCOL_VERSION=3 -LDAP_REFERRALS=false - -# DN / search configuration -LDAP_DN_STRING='{{ LDAP.USER.ATTRIBUTES.ID }}={username},{{ LDAP.DN.OU.USERS }}' -LDAP_QUERY_STRING='' -LDAP_SEARCH_DN={{ LDAP.DN.ADMINISTRATOR.DATA }} -LDAP_SEARCH_PASSWORD={{ LDAP.BIND_CREDENTIAL }} - -# Auto-create SuiteCRM users from LDAP -LDAP_AUTO_CREATE=enabled -LDAP_PROVIDER_BASE_DN={{ LDAP.DN.OU.USERS }} -LDAP_PROVIDER_SEARCH_DN={{ LDAP.DN.ADMINISTRATOR.DATA }} -LDAP_PROVIDER_SEARCH_PASSWORD={{ LDAP.BIND_CREDENTIAL }} -LDAP_PROVIDER_DEFAULT_ROLES=ROLE_USER -LDAP_PROVIDER_UID_KEY={{ LDAP.USER.ATTRIBUTES.ID }} -LDAP_PROVIDER_FILTER='({{ LDAP.USER.ATTRIBUTES.ID }}={username})' +{% if SUITECRM_LDAP_ENABLED | bool %} +SUITECRM_LDAP_ENABLED=true +SUITECRM_LDAP_HOST={{ LDAP.SERVER.DOMAIN }} +SUITECRM_LDAP_PORT={{ LDAP.SERVER.PORT }} +SUITECRM_LDAP_ENCRYPTION={{ LDAP.SERVER.SECURITY | lower if LDAP.SERVER.SECURITY else "none" }} +SUITECRM_LDAP_BASE_DN={{ LDAP.DN.OU.USERS }} +SUITECRM_LDAP_BIND_DN={{ LDAP.DN.ADMINISTRATOR.DATA }} +SUITECRM_LDAP_BIND_PASSWORD={{ LDAP.BIND_CREDENTIAL }} +SUITECRM_LDAP_UID_ATTR={{ LDAP.USER.ATTRIBUTES.ID }} +{% else %} +SUITECRM_LDAP_ENABLED=false {% endif %} # ------------------------------------------------ -# OpenID Connect settings (platform / plugin level) -# Applied only if the feature flag is true +# OpenID Connect settings (optional) # ------------------------------------------------ {% if SUITECRM_OIDC_ENABLED | bool %} +SUITECRM_OIDC_ENABLED=true SUITECRM_OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }} SUITECRM_OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }} - SUITECRM_OIDC_ISSUER_URL={{ OIDC.CLIENT.ISSUER_URL }} -SUITECRM_OIDC_AUTHORIZATION_ENDPOINT={{ OIDC.CLIENT.AUTHORIZE_URL }} -SUITECRM_OIDC_TOKEN_ENDPOINT={{ OIDC.CLIENT.TOKEN_URL }} -SUITECRM_OIDC_USER_INFO_ENDPOINT={{ OIDC.CLIENT.USER_INFO_URL }} -SUITECRM_OIDC_JWKS_ENDPOINT={{ OIDC.CLIENT.CERTS }} - +SUITECRM_OIDC_AUTHORIZATION_URL={{ OIDC.CLIENT.AUTHORIZE_URL }} +SUITECRM_OIDC_TOKEN_URL={{ OIDC.CLIENT.TOKEN_URL }} +SUITECRM_OIDC_USERINFO_URL={{ OIDC.CLIENT.USER_INFO_URL }} +SUITECRM_OIDC_JWKS_URL={{ OIDC.CLIENT.CERTS }} SUITECRM_OIDC_REDIRECT_URI={{ SUITECRM_URL }}/oidc/callback SUITECRM_OIDC_SCOPES=openid,profile,email +{% else %} +SUITECRM_OIDC_ENABLED=false {% endif %} + +# ------------------------------------------------ +# Maintenance mode toggle +# ------------------------------------------------ +SUITECRM_MAINTENANCE={{ SUITECRM_INIT_MAINTENANCE_MODE | lower }} diff --git a/roles/web-app-suitecrm/vars/main.yml b/roles/web-app-suitecrm/vars/main.yml index 20b773ab..e2fac010 100644 --- a/roles/web-app-suitecrm/vars/main.yml +++ b/roles/web-app-suitecrm/vars/main.yml @@ -7,11 +7,14 @@ database_type: "mariadb" # Webserver client_max_body_size: "100m" -vhost_flavour: "generic" -# SuiteCRM container settings -SUITECRM_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.version') }}" -SUITECRM_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.image') }}" +# Container images +# Base PHP image used to run SuiteCRM +SUITECRM_BASE_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.image') }}" +SUITECRM_BASE_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.version') }}" +# Upstream SuiteCRM application version (Git tag, e.g. 8.6.0) +SUITECRM_APP_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.app_version') }}" + SUITECRM_CUSTOM_IMAGE: "custom_suitecrm" SUITECRM_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.' ~ entity_name ~ '.name') }}" SUITECRM_SERVICE: "{{ entity_name }}" @@ -19,6 +22,16 @@ SUITECRM_SERVICE: "{{ entity_name }}" # Volumes SUITECRM_DATA_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}" -# URLs & features +# URLs & feature flags SUITECRM_URL: "{{ domains | get_url(application_id, WEB_PROTOCOL) }}" SUITECRM_OIDC_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc') }}" +SUITECRM_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap') }}" + +# Simple maintenance toggle (for later extensions) +SUITECRM_INIT_MAINTENANCE_MODE: "{{ applications | get_app_conf(application_id, 'maintenance_mode') }}" + +# Entrypoint script (host <-> container mapping) +SUITECRM_ENTRYPOINT_SCRIPT_FILE: "docker-entrypoint-suitecrm.sh" +SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS: "{{ [ docker_compose.directories.volumes, SUITECRM_ENTRYPOINT_SCRIPT_FILE ] | path_join }}" +SUITECRM_ENTRYPOINT_SCRIPT_HOST_REL: "volumes/{{ SUITECRM_ENTRYPOINT_SCRIPT_FILE }}" +SUITECRM_ENTRYPOINT_SCRIPT_DOCKER: "{{ [ '/usr/local/bin/', SUITECRM_ENTRYPOINT_SCRIPT_FILE ] | path_join }}"