Files
kevinveenbirkenbachandClaude Opus 5.5 3cefb01d71 fix(web-app-suitecrm): keep sessions in Redis so replicas share them
With three swarm replicas the PHP session files on the shared NFS volume
were read as empty under concurrent requests of one browser, so SuiteCRM
answered 403 on /api/graphql and dropped the session. Sessions now live in
Redis with session locking that waits like flock did; the phpredis version
is pinned in services.yml with its own update source, and the engine lookup
renders the save path.

Refs #879

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:31:19 +02:00

77 lines
3.2 KiB
Django/Jinja

{% include 'roles/sys-svc-container/templates/env.j2' %}
APP_ENV=prod
APP_DEBUG={{ (MODE_DEBUG | bool and not (SUITECRM_SSO_ENABLED | bool)) | ternary(1, 0) }}
SERVER_SCHEME={{ lookup('tls', application_id, 'protocols.web') }}
TRUSTED_PROXIES={{ NETWORK_TRUSTED_PROXY_CIDRS | join(',') }}
TRUSTED_HOSTS=^{{ domain | replace('.', '\\.') }}$
DATABASE_URL=mysql://{{ lookup('database', application_id, 'username') }}:{{ lookup('database', application_id, 'password') | urlencode }}@{{ lookup('database', application_id, 'host') }}:{{ lookup('database', application_id, 'port') }}/{{ lookup('database', application_id, 'name') }}
SUITECRM_DB_HOST={{ lookup('database', application_id, 'host') }}
SUITECRM_DB_PORT={{ lookup('database', application_id, 'port') }}
SUITECRM_DB_NAME={{ lookup('database', application_id, 'name') }}
SUITECRM_DB_USER={{ lookup('database', application_id, 'username') }}
SUITECRM_DB_PASSWORD={{ lookup('database', application_id, 'password') | dotenv_quote }}
SUITECRM_ADMIN_USERNAME={{ lookup('config', application_id, 'users.administrator.username') }}
SUITECRM_ADMIN_PASSWORD={{ lookup('config', application_id, 'secrets.credentials.administrator_password') | dotenv_quote }}
SUITECRM_ADMIN_EMAIL={{ lookup('users', 'administrator').email }}
SUITECRM_URL={{ SUITECRM_URL }}
SUITECRM_SESSION_SAVE_PATH={{ lookup('engine', 'redis', application_id, 'session_path') }}
{% if SUITECRM_SSO_ENABLED | bool %}
AUTH_TYPE=saml
SAML_IDP_ENTITY_ID={{ OIDC.CLIENT.ISSUER_URL }}
SAML_IDP_SSO_URL={{ [OIDC.CLIENT.ISSUER_URL, 'protocol/saml'] | path_join }}
SAML_IDP_SLO_URL={{ [OIDC.CLIENT.ISSUER_URL, 'protocol/saml'] | path_join }}
SAML_SP_ENTITY_ID={{ SUITECRM_SAML_SP_ENTITY_ID }}
SAML_COMPRESS_REQUESTS=true
SAML_USE_ATTRIBUTE_FRIENDLY_NAME=false
SAML_USERNAME_ATTRIBUTE={{ OIDC.ATTRIBUTES.USERNAME }}
SAML_AUTO_CREATE=enabled
SAML_AUTOCREATE_ATTRIBUTES_MAP={"{{ OIDC.ATTRIBUTES.USERNAME }}":"user_name","email":"email1","firstName":"first_name","lastName":"last_name"}
SUITECRM_SAML_IDP_DESCRIPTOR_URL={{ SUITECRM_SAML_IDP_DESCRIPTOR_URL }}
{% if SUITECRM_OIDC_SOCKS_PROXY | length > 0 %}
SUITECRM_OIDC_SOCKS_PROXY={{ SUITECRM_OIDC_SOCKS_PROXY }}
{% endif %}
{% elif SUITECRM_LDAP_ENABLED | bool %}
AUTH_TYPE=ldap
LDAP_AUTO_CREATE=enabled
LDAP_PROVIDER_BASE_DN={{ LDAP.DN.OU.USERS | dotenv_quote }}
LDAP_PROVIDER_SEARCH_DN={{ LDAP.DN.ADMINISTRATOR.DATA | dotenv_quote }}
LDAP_PROVIDER_SEARCH_PASSWORD={{ LDAP.BIND_CREDENTIAL | dotenv_quote }}
LDAP_PROVIDER_DEFAULT_ROLES=ROLE_USER
LDAP_PROVIDER_UID_KEY={{ LDAP.USER.ATTRIBUTES.ID | dotenv_quote }}
LDAP_PROVIDER_FILTER={{ (LDAP.USER.ATTRIBUTES.ID ~ '={username}') | dotenv_quote }}
LDAP_CONNECTION_OPTION_DEBUG_LEVEL={{ MODE_DEBUG | bool | ternary(7, 0) }}
LDAP_HOST={{ LDAP.SERVER.DOMAIN }}
LDAP_PORT={{ LDAP.SERVER.PORT }}
LDAP_ENCRYPTION={{ (LDAP.SERVER.SECURITY or 'none') | lower }}
LDAP_PROTOCOL_VERSION=3
LDAP_REFERRALS=false
LDAP_DN_STRING={{ LDAP.DN.OU.USERS }}
LDAP_QUERY_STRING={{ LDAP.USER.ATTRIBUTES.ID }}={username}
LDAP_SEARCH_DN={{ LDAP.DN.ADMINISTRATOR.DATA }}
LDAP_SEARCH_PASSWORD={{ LDAP.BIND_CREDENTIAL | dotenv_quote }}
{% else %}
AUTH_TYPE=native
{% endif %}
NODE_OPTIONS=--max-old-space-size={{ SUITECRM_MAX_OLD_SPACE_SIZE }}