Compare commits

..

6 Commits

58 changed files with 653 additions and 212 deletions

View File

@ -1,8 +1,16 @@
# General
pause_duration: "120" # Database delay to wait for the central database before continue tasks
timezone: "Etc/UTC"
locale: "en" # Some applications are case sensitive
HOST_CURRENCY: "EUR"
HOST_TIMEZONE: "UTC"
# https://en.wikipedia.org/wiki/ISO_639
HOST_LL: "en" # Some applications are case sensitive
HOST_LL_CC: "{{HOST_LL}}_{{HOST_LL | upper }}"
HOST_DATE_FORMAT: "YYYY-MM-DD"
HOST_TIME_FORMAT: "HH:mm"
HOST_THOUSAND_SEPARATOR: "."
HOST_DECIMAL_MARK: ","
# Deployment mode
deployment_mode: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster mode.

View File

@ -10,6 +10,7 @@ defaults_domains:
bluesky_web: "bskyweb.{{primary_domain}}"
discourse: "forum.{{primary_domain}}"
elk: "elk.{{primary_domain}}"
espocrm: "espocrm.{{primary_domain}}"
file_server: "files.{{primary_domain}}"
friendica: "friendica.{{primary_domain}}"
funkwhale: "music.{{primary_domain}}"
@ -55,6 +56,7 @@ defaults_redirect_domain_mappings:
- { source: "akaunting.{{primary_domain}}", target: "{{domains.akaunting}}" }
- { source: "bbb.{{primary_domain}}", target: "{{domains.bigbluebutton}}" }
- { source: "discourse.{{primary_domain}}", target: "{{domains.discourse}}" }
- { source: "crm.{{primary_domain}}", target: "{{domains.espocrm}}" }
- { source: "funkwhale.{{primary_domain}}", target: "{{domains.funkwhale}}" }
- { source: "gitea.{{primary_domain}}", target: "{{domains.gitea}}" }
- { source: "keycloak.{{primary_domain}}", target: "{{domains.keycloak}}" }

View File

@ -1,8 +1,10 @@
ports:
# Ports which are exposed to localhost
localhost:
web_socket:
# https://developer.mozilla.org/de/docs/Web/API/WebSockets_API
websocket:
mastodon: 4001
espocrm: 4002
oauth2_proxy:
phpmyadmin: 4181
lam: 4182
@ -53,6 +55,7 @@ ports:
phpldapadmin: 8037
fusiondirectory: 8038
presentation: 8039
espocrm: 8040
bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port
# Ports which are exposed to the World Wide Web
public:

View File

@ -80,6 +80,8 @@ defaults_networks:
subnet: 192.168.103.32/28
presentation:
subnet: 192.168.103.48/28
espocrm:
subnet: 192.168.103.64/28
# /24 Networks / 254 Usable Clients
bigbluebutton:

View File

@ -39,7 +39,7 @@ defaults_oidc:
# Helper Variables:
# Keep in mind to mapp this variables if there is ever the possibility for the user to define them in the inventory
_ldap_dn_base: "dc={{primary_domain_sld}},dc={{primary_domain_tld}}"
_ldap_server_port: "{% if applications.ldap.network.local | bool %}{{ ports.localhost.ldap.ldap }}{% else %}{{ ports.localhost.ldaps.ldap }}{% endif %}"
_ldap_server_port: "{% if applications.ldap.network.docker | bool %}{{ ports.localhost.ldap.ldap }}{% else %}{{ ports.localhost.ldaps.ldap }}{% endif %}"
ldap:
# Distinguished Names (DN)
@ -60,11 +60,11 @@ ldap:
# Password to access dn.bind
bind_credential: "{{applications.ldap.administrator_database_password}}"
server:
domain: "{{applications.ldap.hostname if applications.ldap.network.local | bool else domains.ldap}}" # Mapping for public or locale access
domain: "{{applications.ldap.hostname if applications.ldap.network.docker | bool else domains.ldap}}" # Mapping for public or locale access
port: "{{_ldap_server_port}}"
uri: "{% if applications.ldap.network.local | bool %}ldap://{{ applications.ldap.hostname }}{% else %}ldaps://{{ domains.ldap }}{% endif %}:{{ _ldap_server_port }}"
uri: "{% if applications.ldap.network.docker | bool %}ldap://{{ applications.ldap.hostname }}{% else %}ldaps://{{ domains.ldap }}{% endif %}:{{ _ldap_server_port }}"
network:
local: "{{applications.ldap.network.local}}" # Uses the application configuration to define if local network should be available or not
local: "{{applications.ldap.network.docker}}" # Uses the application configuration to define if local network should be available or not
user_objects:
- person # Basic person attributes (sn, cn …) RFC 4519
- inetOrgPerson # Extended Internet / intranet person RFC 2798

View File

@ -1,6 +1,6 @@
# You should change this to match your reverse proxy DNS name and protocol
APP_URL=https://{{domains[application_id]}}
LOCALE={{locale}}
LOCALE={{ HOST_LL }}
# Don't change this unless you rename your database container or use rootless podman, in case of using rootless podman you should set it to 127.0.0.1 (NOT localhost)
DB_HOST={{database_host}}

View File

@ -15,7 +15,7 @@
- name: configure {{domains[application_id]}}.conf
template:
src: roles/nginx-docker-reverse-proxy/templates/domain.conf.j2
src: roles/nginx-docker-reverse-proxy/templates/vhost/basic.conf.j2
dest: "{{nginx.directories.http.servers}}{{domains[application_id]}}.conf"
notify: restart nginx

View File

@ -34,7 +34,7 @@ env:
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LANGUAGE: en_US.UTF-8
#DISCOURSE_DEFAULT_LOCALE: {{locale}} # Deactivated because not right format was selected @todo find right format
#DISCOURSE_DEFAULT_LOCALE: {{ HOST_LL }} # Deactivated because not right format was selected @todo find right format
## How many concurrent web requests are supported? Depends on memory and CPU cores.
## will be set automatically by bootstrap based on detected CPUs, or you can override

View File

@ -0,0 +1,32 @@
# Administration
## 🗑️ Cleanup (Remove instance & volumes)
```bash
cd {{path_docker_compose_instances}}espocrm/
docker compose down
# EspoCRM keeps all uploaded files in the *data* volume
docker volume rm espocrm_data espocrm_database
cd {{path_docker_compose_instances}} && rm -vR {{path_docker_compose_instances}}espocrm
```
## 🔍 Access EspoCRM container shell
```bash
docker compose exec -it web /bin/bash
```
## 🛠️ Database migrations (after image upgrade)
EspoCRM applies migrations automatically on startup. To run them manually:
```bash
docker compose exec -it web php command.php upgrade
```
## 🗄️ Backup database
```bash
# Dump the MySQL/MariaDB database
docker exec espocrm_database /usr/bin/mysqldump -u root -p$MYSQL_ROOT_PASSWORD espocrm > backup_$(date +%F).sql
```
## 🧹 Clear cache
```bash
docker compose exec web php command.php clear cache
```

View File

@ -0,0 +1,25 @@
# ⚙️ Configuration & Setup
## 🔧 Create credentials & pull image
```bash
# Pull the latest EspoCRM image
docker pull espocrm/espocrm:latest
# If you need to precreate a config file, copy the default
# (the container will generate one automatically on first start)
```
## 🏗️ Initial deployment with Docker Compose
```bash
# Change into the instance directory created by the role
cd {{path_docker_compose_instances}}espocrm/
# Launch the stack
docker compose up -d
```
The first start can take a minute while EspoCRM initialises the database schema.
## 🔐 LDAP & OIDC authentication
Both mechanisms are supported out of the box:
- **LDAP:** Configure under *Administration → Authentication → LDAP* after the first login.
- **OIDC:** Configure under *Administration → Authentication → OpenID Connect* and paste the Issuer URL, Client ID and Client Secret from your IdP (Keycloak, Authentik, Entra ID, …).

View File

@ -0,0 +1,16 @@
# EspoCRM
## Description
EspoCRM is a lightweight, opensource Customer Relationship Management platform that helps you manage leads, accounts, opportunities and postsale support in a single, webbased interface.
This Ansible role deploys EspoCRM with Docker, mirroring the structure of the Mastodon role for a consistent operations workflow.
## Overview
- **Sales Pipeline & Activities Stream** track every interaction and schedule followups.
- **Workflow & BPM** automate routine tasks and notifications.
- **Extensible Authentication** native LDAP and OpenID Connect (OIDC) login support.
- **Containerised Architecture** separate services for the web application, cron jobs and an upstream MySQL/MariaDB database.
For detailed instructions see:
- [Installation.md](./Installation.md)
- [Administration.md](./Administration.md)

View File

@ -0,0 +1,2 @@
# Todos
- Finish draft implementation

View File

@ -0,0 +1,22 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "EspoCRM is an opensource CRM with workflow automation, LDAP/OIDC SSO and a lightweight UI"
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
galaxy_tags:
- espocrm
- crm
- docker
- sales
- ldap
- oidc
repository: "https://s.veen.world/cymais"
issue_tracker_url: "https://s.veen.world/cymaisissues"
documentation: "https://s.veen.world/cymais"
logo:
class: "fa-solid fa-briefcase"
dependencies: []

View File

@ -0,0 +1,18 @@
---
- name: "include docker-central-database"
include_role:
name: docker-central-database
- name: "Include setup for domain '{{ domain }}'"
include_role:
name: nginx-domain-setup
vars:
ws_path: "/ws"
ws_port: "{{ ports.localhost.websocket[application_id] }}"
client_max_body_size: "100m"
vhost_flavour: "ws_generic"
domain: "{{ domains[application_id] }}"
http_port: "{{ ports.localhost.http[application_id] }}"
- name: "copy docker-compose.yml and env file"
include_tasks: copy-docker-compose-and-env.yml

View File

@ -0,0 +1,49 @@
services:
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
web:
image: espocrm/espocrm:{{ applications.espocrm.version }}
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
command: "php-fpm"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:80"
{% include 'templates/docker/container/depends-on-just-database.yml.j2' %}
{% include 'templates/docker/container/networks.yml.j2' %}
volumes:
- data:/var/www/html
daemon:
image: espocrm/espocrm:{{ applications.espocrm.version }}
restart: {{docker_restart_policy}}
logging:
driver: journald
entrypoint: docker-daemon.sh
{% include 'templates/docker/container/networks.yml.j2' %}
volumes:
- data:/var/www/html
websocket:
image: espocrm/espocrm:{{ applications.espocrm.version }}
restart: {{docker_restart_policy}}
logging:
driver: journald
environment:
- ESPOCRM_CONFIG_USE_WEB_SOCKET=true
- ESPOCRM_CONFIG_WEB_SOCKET_URL=ws://{{ domains[application_id] }}/ws
- ESPOCRM_CONFIG_WEB_SOCKET_ZERO_M_Q_SUBSCRIBER_DSN=tcp://*:7777
- ESPOCRM_CONFIG_WEB_SOCKET_ZERO_M_Q_SUBMISSION_DSN=tcp://websocket:7777
entrypoint: docker-websocket.sh
{% include 'templates/docker/container/depends-on-just-database.yml.j2' %}
{% include 'templates/docker/container/networks.yml.j2' %}
volumes:
- data:/var/www/html
ports:
- "{{ ports.localhost.websocket[application_id] | default('127.0.0.1:8081') }}:8080"
{% include 'templates/docker/compose/volumes.yml.j2' %}
data:
{% include 'templates/docker/compose/networks.yml.j2' %}

View File

@ -0,0 +1,98 @@
#############################################
# EspoCRM Docker Environment (.env) ENGLISH
# See: https://hub.docker.com/r/espocrm/espocrm
#############################################
# ------------------------------------------------
# Database connection
# ------------------------------------------------
ESPOCRM_DATABASE_PLATFORM=Mysql
ESPOCRM_DATABASE_HOST={{ database_host }}
ESPOCRM_DATABASE_PORT={{ database_port }}
ESPOCRM_DATABASE_NAME={{ database_name }}
ESPOCRM_DATABASE_USER={{ database_username }}
ESPOCRM_DATABASE_PASSWORD={{ database_password }}
# Disable EspoCRMs built-in cron (handled externally)
CRON_DISABLED=true
# ------------------------------------------------
# Initial admin account
# ------------------------------------------------
ESPOCRM_ADMIN_USERNAME={{ applications[application_id].users.administrator.username }}
ESPOCRM_ADMIN_PASSWORD={{ applications[application_id].credentials.administrator.password }}
# Public base URL of the EspoCRM instance
ESPOCRM_SITE_URL={{ web_protocol }}://{{ domains[application_id] }}
# ------------------------------------------------
# General UI & locale settings
# ------------------------------------------------
ESPOCRM_CONFIG_LANGUAGE={{ HOST_LL_CC }}
ESPOCRM_CONFIG_DATE_FORMAT={{ HOST_DATE_FORMAT }}
ESPOCRM_CONFIG_TIME_FORMAT={{ HOST_TIME_FORMAT }}
ESPOCRM_CONFIG_TIME_ZONE={{ HOST_TIMEZONE }}
# ESPOCRM_CONFIG_WEEK_START: 0 = Sunday, 1 = Monday
ESPOCRM_CONFIG_WEEK_START=1
ESPOCRM_CONFIG_DEFAULT_CURRENCY={{ HOST_CURRENCY }}
ESPOCRM_CONFIG_THOUSAND_SEPARATOR={{ HOST_THOUSAND_SEPARATOR }}
ESPOCRM_CONFIG_DECIMAL_MARK={{HOST_DECIMAL_MARK}}
# ------------------------------------------------
# Logger
# ------------------------------------------------
ESPOCRM_CONFIG_LOGGER_LEVEL={{ 'DEBUG' if enable_debug | bool else 'INFO' }}
ESPOCRM_CONFIG_LOGGER_PATH=php://stdout
ESPOCRM_CONFIG_LOGGER_ROTATION=false
# ------------------------------------------------
# System SMTP settings
# ------------------------------------------------
ESPOCRM_CONFIG_SMTP_SERVER={{ system_email.host }}
ESPOCRM_CONFIG_SMTP_PORT={{ system_email.port }}
ESPOCRM_CONFIG_SMTP_SECURITY=TLS
ESPOCRM_CONFIG_SMTP_AUTH=true
ESPOCRM_CONFIG_SMTP_USERNAME={{ users['no-reply'].email }}
ESPOCRM_CONFIG_SMTP_PASSWORD={{ users['no-reply'].mailu_token }}
ESPOCRM_CONFIG_OUTBOUND_EMAIL_FROM_NAME={{ service_provider.company.titel }} - CRM
ESPOCRM_CONFIG_OUTBOUND_EMAIL_FROM_ADDRESS={{ users['no-reply'].email }}
# ------------------------------------------------
# LDAP settings (optional)
# Applied only if the feature flag is true
# ------------------------------------------------
{% if applications[application_id].features.ldap | bool %}
ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Ldap
ESPOCRM_CONFIG_LDAP_HOST={{ ldap.server.domain }}
ESPOCRM_CONFIG_LDAP_PORT={{ ldap.server.port }}
# ESPOCRM_CONFIG_LDAP_SECURITY: "", SSL or TLS
ESPOCRM_CONFIG_LDAP_SECURITY=
ESPOCRM_CONFIG_LDAP_USERNAME={{ ldap.dn.administrator }}
ESPOCRM_CONFIG_LDAP_PASSWORD={{ ldap.bind_credential }}
ESPOCRM_CONFIG_LDAP_BASE_DN={{ ldap.dn.users }}
ESPOCRM_CONFIG_LDAP_USER_LOGIN_FILTER=(sAMAccountName=%USERNAME%)
{% endif %}
# ------------------------------------------------
# OpenID Connect settings (optional)
# Applied only if the feature flag is true
# ------------------------------------------------
{% if applications[application_id].features.oidc | bool %}
# ------------------------------------------------
# OpenID Connect settings
# ------------------------------------------------
ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Oidc
ESPOCRM_CONFIG_OIDC_FALLBACK=false # set true if you want LDAP as fallback
ESPOCRM_CONFIG_OIDC_CLIENT_ID={{ oidc.client.id }}
ESPOCRM_CONFIG_OIDC_CLIENT_SECRET={{ oidc.client.secret }}
ESPOCRM_CONFIG_OIDC_AUTHORIZATION_ENDPOINT={{ oidc.client.authorize_url }}
ESPOCRM_CONFIG_OIDC_TOKEN_ENDPOINT={{ oidc.client.token_url }}
ESPOCRM_CONFIG_OIDC_USER_INFO_ENDPOINT={{ oidc.client.user_info_url }}
ESPOCRM_CONFIG_OIDC_JWKS_ENDPOINT={{ oidc.client.certs }}
ESPOCRM_CONFIG_OIDC_AUTHORIZATION_REDIRECT_URI=https://{{ domains[application_id] }}/oidc/callback
ESPOCRM_CONFIG_OIDC_SCOPES=openid,profile,email
{% endif %}

View File

@ -0,0 +1,5 @@
application_id: "espocrm"
# Password for the espocrm DB user (taken from inventory applications dict)
database_password: "{{ applications[application_id].credentials.database.password }}"
# EspoCRM uses MySQL/MariaDB
database_type: "mariadb"

View File

@ -1,6 +1,8 @@
# Administration
## Show Configuration
## Configuration
### Show Configuration
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b 'cn=config'"
```
@ -18,7 +20,16 @@ docker exec -it ldap bash -c "ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b 'cn=co
docker exec -it ldap ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config" "(olcDatabase=*)"
```
## Show all Entries
## Data
### Set Credentials
To execute the following commands set the credentials via:
```bash
export $(grep -Ev '^(#|$)' .env/env | xargs)
```
### Show all Entries
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -o ldif-wrap=no -x -D \"\$LDAP_ADMIN_DN\" -w \"\$LDAP_ADMIN_PASSWORD\" -b \"\$LDAP_ROOT\"";
```

View File

@ -1,25 +1,42 @@
- name: Load memberof module from file in OpenLDAP container
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}01_member_of_configuration.ldif
listen: "Import LDIF files"
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/01_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
# @todo Remove the following ignore errors when setting up a new server
# Just here because debugging would take to much time
ignore_errors: true
- name: Refint Module Activation for OpenLDAP
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}02_member_of_configuration.ldif
listen: "Import LDIF files"
docker exec -i {{ applications[application_id].hostname }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/02_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
register: ldapadd_result
failed_when: ldapadd_result.rc not in [0, 68]
# @todo Remove the following ignore errors when setting up a new server
# Just here because debugging would take to much time
ignore_errors: true
- name: "Import schemas"
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{ldif_docker_path}}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 80]
listen:
- "Import schema LDIF files"
- "Import all LDIF files"
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/schema/*.j2', wantlist=True) }}"
- name: Refint Overlay Configuration for OpenLDAP
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}03_member_of_configuration.ldif
listen: "Import LDIF files"
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/03_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
register: ldapadd_result
failed_when: ldapadd_result.rc not in [0, 68]
# @todo Remove the following ignore errors when setting up a new server
@ -32,14 +49,7 @@
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 20, 68]
listen: "Import LDIF files"
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/data/*.j2', wantlist=True) }}"
- name: "Import schemas"
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{ldif_docker_path}}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 80]
listen: "Import LDIF files"
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/schema/*.j2', wantlist=True) }}"
listen:
- "Import data LDIF files"
- "Import all LDIF files"
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/data/*.j2', wantlist=True) }}"

View File

@ -1,9 +1,9 @@
# In own task file for easier looping
- name: "Create LDIF files at {{ ldif_host_path }}/{{ folder }}"
- name: "Create LDIF files at {{ ldif_host_path }}{{ folder }}"
template:
src: "{{ item }}"
dest: "{{ ldif_host_path }}/{{ folder }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
dest: "{{ ldif_host_path }}{{ folder }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
mode: '770'
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/' ~ folder ~ '/*.j2', wantlist=True) }}"
notify: Import LDIF files
notify: "Import {{ folder }} LDIF files"

View File

@ -35,11 +35,77 @@
- name: "Process all LDIF types"
include_tasks: create_ldif_files.yml
loop: "{{ ldif_types }}"
loop:
- configuration
- schema
loop_control:
loop_var: folder
- name: Force LDIF files import
command: /bin/true
notify: Import LDIF files
when: applications.ldap.force_import | bool
- name: flush LDIF handlers
meta: flush_handlers
- name: install python-ldap
community.general.pacman:
name:
- python-ldap
state: present
###############################################################################
# 1) Create the LDAP entry if it does not yet exist
###############################################################################
- name: Ensure LDAP users exist
community.general.ldap_entry:
dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}"
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "{{ ldap.dn.administrator }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: "{{ ldap.user_objects }}"
attributes:
uid: "{{ item.key }}" # {{ ldap.attributes.user_id }} can't be used as key here, dynamic key generation isn't possible
sn: "{{ item.value.sn | default(item.key) }}"
cn: "{{ item.value.cn | default(item.key) }}"
userPassword: "{SSHA}{{ item.value.password }}"
loginShell: /bin/bash
homeDirectory: "/home/{{ item.key }}"
uidNumber: "{{ item.value.uid | int }}"
gidNumber: "{{ item.value.gid | int }}"
state: present # ↳ creates but never updates
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
###############################################################################
# 2) Keep the objectClass list AND the mail attribute up-to-date
###############################################################################
- name: Ensure required objectClass values and mail address are present
community.general.ldap_attrs:
dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}"
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "{{ ldap.dn.administrator }}"
bind_pw: "{{ ldap.bind_credential }}"
attributes:
objectClass: "{{ ldap.user_objects }}"
mail: "{{ item.value.email }}"
state: exact # exact is safest for single-valued attributes
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
- name: "Ensure container for application roles exists"
community.general.ldap_entry:
dn: "{{ ldap.dn.application_roles }}"
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "{{ ldap.dn.administrator }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: organizationalUnit
attributes:
ou: roles
description: Container for application access profiles
state: present
- name: "Process all LDIF types"
include_tasks: create_ldif_files.yml
loop:
- data
loop_control:
loop_var: folder

View File

@ -6,7 +6,7 @@ services:
image: bitnami/openldap:{{ applications[application_id].version }}
container_name: {{ applications[application_id].hostname }}
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
{% if applications[application_id].network.public | bool %}
{% if applications[application_id].network.public | bool or applications[application_id].network.local | bool %}
ports:
- 127.0.0.1:{{ports.localhost.ldap.ldap}}:{{ldap_docker_port}} # Expose just on localhost so that nginx stream proxy can use it
{% endif %}

View File

@ -1,19 +1,4 @@
#######################################################################
# Generic container for Application roles
#######################################################################
dn: {{ldap.dn.application_roles}}
objectClass: organizationalUnit
ou: roles
description: Container for application access profiles
{#
This template generates two LDIF entries for each application in defaults_applications:
one for the administrator role and one for the standard user role.
Please adjust the base DN (dc=example,dc=com) and other attributes as necessary.
#}
{% for app, config in defaults_applications.items() %}
{% for app, config in applications.items() %}
dn: cn={{ app }}-administrator,{{ldap.dn.application_roles}}
objectClass: top
objectClass: organizationalRole
@ -27,3 +12,31 @@ cn: {{ app }}-user
description: Standard user role for {{ app }} (automatically generated)
{% endfor %}
{% for username, user in users.items() %}
#######################################################################
# Assign {{ username }} to application user roles
#######################################################################
{% for app, config in applications.items() %}
# Assign {{ username }} to {{ app }}-users
dn: cn={{ app }}-user,{{ ldap.dn.application_roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.users }}
{% if users.is_admin | default(false) | bool %}
# Assign {{ username }} to {{ app }}-administrator
dn: cn={{ app }}-administrator,{{ ldap.dn.application_roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }}
{% endif %}
{% endfor %}
{% endfor %}

View File

@ -1,58 +0,0 @@
{##
# Iterate over all users and create LDAP entries for each, then assign admin to application roles
# This template loops through a 'users' list variable where each user is a dict with keys:
# username, uid, gid, password (optional), sn (optional), cn (optional)
##}
#######################################################################
# Container for Application Roles (if not already created)
#######################################################################
dn: {{ ldap.dn.application_roles }}
objectClass: organizationalUnit
ou: roles
description: Container for application access profiles
{% for username, user in users.items() %}
#######################################################################
# Create User {{ username }}
#######################################################################
dn: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.users }}
{% for cls in ldap.user_objects %}
objectClass: {{ cls }}
{% endfor %}
{{ ldap.attributes.user_id }}: {{ username }}
sn: {{ username }}
cn: {{ username }}
userPassword: {SSHA}{{ user.password }}
loginShell: /bin/bash
homeDirectory: /home/{{ username }}
uidNumber: {{ user.uid }}
gidNumber: {{ user.gid }}
#######################################################################
# Assign {{ username }} to application user roles
#######################################################################
{% for app, config in defaults_applications.items() %}
dn: cn={{ app }}-user,{{ ldap.dn.application_roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.users }}
{% endfor %}
{% endfor %}
#######################################################################
# Add Admin User to All Application Role Groups (unchanged)
#######################################################################
{% for app, config in defaults_applications.items() %}
dn: cn={{ app }}-administrator,{{ ldap.dn.application_roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }}
dn: cn={{ app }}-user,{{ ldap.dn.application_roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }}
{% endfor %}

View File

@ -1,4 +1,4 @@
TZ={{timezone}}
TZ={{ HOST_TIMEZONE }}
# Administrator setup

View File

@ -61,7 +61,7 @@ listmonk_settings:
- key: "app.lang"
value: '"{{ locale }}"'
value: '"{{ HOST_LL }}"'
# - key: "messengers"
# value: '[]'

View File

@ -1,9 +0,0 @@
- name: "include role for {{application_id}} to recieve certs & do modification routines"
include_role:
name: nginx-https-get-cert-modify-all
- name: configure {{domain}}.conf
template:
src: "mastodon.conf.j2"
dest: "{{nginx.directories.http.servers}}{{domain}}.conf"
notify: restart nginx

View File

@ -3,13 +3,18 @@
include_role:
name: docker-central-database
- name: "include create-domains.yml for mastodon"
include_tasks: create-domains.yml
- name: "Include setup for domain '{{ domain }}'"
include_role:
name: nginx-domain-setup
loop: "{{ [domains.mastodon] + domains.mastodon_alternates }}"
loop_control:
loop_var: domain
vars:
http_port: "{{ ports.localhost.http[application_id] }}"
http_port: "{{ ports.localhost.http[application_id] }}"
ws_path: "/api/v1/streaming"
ws_port: "{{ ports.localhost.websocket[application_id] }}"
client_max_body_size: "80m"
vhost_flavour: "ws_generic"
- name: "copy docker-compose.yml and env file"
include_tasks: copy-docker-compose-and-env.yml

View File

@ -24,7 +24,7 @@ services:
healthcheck:
test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
ports:
- "127.0.0.1:{{ports.localhost.web_socket[application_id]}}:4000"
- "127.0.0.1:{{ports.localhost.websocket[application_id]}}:4000"
{% include 'templates/docker/container/depends-on-database-redis.yml.j2' %}
{% include 'templates/docker/container/networks.yml.j2' %}

View File

@ -12,5 +12,5 @@ server {
listen [::]:8448 ssl default_server;
{% include 'roles/nginx-modifier-all/templates/global.includes.conf.j2'%}
{% include 'roles/nginx-docker-reverse-proxy/templates/proxy_pass.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2' %}
}

View File

@ -8,7 +8,7 @@
- name: configure {{domains[application_id]}}.conf
template:
src: "roles/nginx-docker-reverse-proxy/templates/domain.conf.j2"
src: "roles/nginx-docker-reverse-proxy/templates/vhost/basic.conf.j2"
dest: "{{nginx.directories.http.servers}}{{domains[application_id]}}.conf"
notify: restart nginx
vars:

View File

@ -75,7 +75,7 @@ http {
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Frame-Options "SAMEORIGIN" always;
{% include 'roles/nginx-docker-reverse-proxy/templates/iframe.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/headers/iframe.conf.j2' %}
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;

View File

@ -18,7 +18,7 @@ server
client_body_buffer_size 400M;
fastcgi_buffers 64 4K;
{% include 'roles/nginx-docker-reverse-proxy/templates/proxy_pass.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2' %}
location ^~ /.well-known {
rewrite ^/\.well-known/host-meta\.json /public.php?service=host-meta-json last;

View File

@ -12,7 +12,7 @@ nextcloud_system_config:
value: "{{ on_calendar_nextcloud }}"
- parameter: "default_phone_region"
value: "{{ locale | upper }}"
value: "{{ HOST_LL | upper }}"
- parameter: "trusted_domains 0"
value: "{{domains[application_id]}}"

View File

@ -5,7 +5,7 @@ server {
{% include 'roles/nginx-modifier-all/templates/global.includes.conf.j2'%}
{% include 'roles/nginx-docker-reverse-proxy/templates/iframe.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/headers/iframe.conf.j2' %}
##
# Application

View File

@ -15,8 +15,8 @@ ENFORCE_EMAIL_VERIFICATION=false
PF_MAX_USERS=1000
OAUTH_ENABLED=true
APP_TIMEZONE={{timezone}}
APP_LOCALE={{locale}}
APP_TIMEZONE={{ HOST_TIMEZONE }}
APP_LOCALE={{ HOST_LL }}
## Pixelfed Tweaks
LIMIT_ACCOUNT_SIZE=true
@ -49,7 +49,7 @@ MAIL_DRIVER=log
MAIL_HOST={{system_email.host}}
MAIL_PORT={{system_email.port}}
MAIL_FROM_ADDRESS="{{ users['no-reply'].email }}"
MAIL_FROM_NAME="Pixelfed"
MAIL_FROM_NAME={{ service_provider.company.titel }} - Pixelfed
MAIL_USERNAME={{ users['no-reply'].email }}
MAIL_PASSWORD={{ users['no-reply'].mailu_token }}
# Not sure if the following is correct

View File

@ -7,8 +7,8 @@ APP_DEBUG={{enable_debug | string | lower }}
APP_KEY={{applications.snipe_it.app_key}}
APP_URL=https://{{domains[application_id]}}
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
APP_TIMEZONE='{{timezone}}'
APP_LOCALE={{locale}}
APP_TIMEZONE='{{ HOST_TIMEZONE }}'
APP_LOCALE={{ HOST_LL }}
MAX_RESULTS=500
# --------------------------------------------
@ -49,15 +49,15 @@ DB_SSL_VERIFY_SERVER=null
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
MAIL_MAILER = smtp
MAIL_HOST = {{system_email.host}} # SMTP server address
MAIL_PORT = {{system_email.port}} # SMTP server address
MAIL_USERNAME = {{ users['no-reply'].email }} # user to connect the SMTP server
MAIL_PASSWORD = {{ users['no-reply'].mailu_token }} # SMTP user's password
MAIL_TLS_VERIFY_PEER = {{ system_email.tls | capitalize }} # use TLS (secure) connection with the SMTP server
MAIL_FROM_ADDR = {{ users['no-reply'].email }} # default email address for the automated emails
MAIL_FROM_NAME = 'Snipe-IT'
MAIL_REPLYTO_ADDR = {{ users['no-reply'].email }} # default email address for the automated emails
MAIL_REPLYTO_NAME = 'Snipe-IT'
MAIL_HOST = {{system_email.host}}
MAIL_PORT = {{system_email.port}}
MAIL_USERNAME = {{ users['no-reply'].email }}
MAIL_PASSWORD = {{ users['no-reply'].mailu_token }}
MAIL_TLS_VERIFY_PEER = {{ system_email.tls | capitalize }}
MAIL_FROM_ADDR = {{ users['no-reply'].email }}
MAIL_FROM_NAME = {{ service_provider.company.titel }} - Snipe-IT
MAIL_REPLYTO_ADDR = {{ users['no-reply'].email }}
MAIL_REPLYTO_NAME = {{ service_provider.company.titel }} - Snipe-IT
MAIL_AUTO_EMBED_METHOD = 'attachment'
# --------------------------------------------

View File

@ -1,19 +1,31 @@
# role nginx-docker-reverse-proxy
# Nginx Docker Reverse Proxy 🚀
Uses nginx as an [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) for local docker applications.
## Description
## debug
```bash
curl -I {{address}}
```
- https://serverfault.com/questions/434915/nginx-proxy-caching-how-to-check-if-it-is-working
This Ansible role deploys **Nginx** as a high-performance [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) in front of Docker-hosted services.
It provides automatic TLS integration, WebSocket support, and a flexible templating system for per-application configuration.
## performance
- https://stackoverflow.com/questions/33703230/caching-images-on-all-folder-levels-of-nginx-reverse-proxy
- https://www.tweaked.io/guide/nginx-proxying/
- https://serverfault.com/questions/796735/nginx-reverse-proxy-is-slow/796740
- https://serverfault.com/questions/741610/what-is-the-difference-between-proxy-request-buffering-and-proxy-buffering-on-ng
- https://askubuntu.com/questions/1103626/should-i-enable-client-max-body-size-proxy-request-buffering-and-proxy-bufferin
- https://serverfault.com/questions/692577/whats-the-difference-between-proxy-buffer-and-proxy-cache-module-in-nginx-confi
- https://github.com/sissbruecker/linkding/issues/88
- https://www.bogotobogo.com/DevOps/Docker/docker/compose/Nginx-Reverse-Proxy-Multiple-Containers.php
## Overview
Optimised for Arch Linux, the role installs Nginx, prepares opinionated configuration snippets and exposes a simple interface for other roles to drop in new virtual-hosts.
It plays well with **Lets Encrypt**, **OAuth2 Proxy**, and your existing Docker stack.
## Purpose
The goal of this role is to deliver a **hassle-free, production-ready reverse proxy** for self-hosted containers, suitable for homelabs and small-scale production workloads.
## Features
- **Automatic TLS & HSTS** — integrates with the *nginx-https* role for certificate management.
- **Flexible vHost templates***basic* and *ws_generic* flavours cover standard HTTP and WebSocket applications.
- **Security headers** — sensible defaults plus optional X-Frame-Options / CSP based on application settings.
- **WebSocket & HTTP/2 aware** — upgrades, keep-alive tuning, and gzip already configured.
- **OAuth2 gating** — drop-in support when *docker-oauth2-proxy* is present.
- **Modular includes** — headers, locations, and global snippets are factored for easy extension.
## Credits 📝
Developed and maintained by **Kevin Veen-Birkenbach**.
More at <https://www.veen.world>
Part of the **CyMaIS Project** — licensed under the [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl)

View File

@ -1,3 +1,28 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Nginx reverse proxy front-end for local Docker applications."
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
min_ansible_version: "2.9"
platforms:
- name: Archlinux
versions:
- rolling
galaxy_tags:
- nginx
- docker
- reverse_proxy
- web
- automation
- archlinux
repository: https://s.veen.world/cymais
issue_tracker_url: https://s.veen.world/cymaisissues
documentation: https://s.veen.world/cymais
dependencies:
- docker
- nginx-https
- role: docker
- role: nginx-https

View File

@ -14,7 +14,7 @@ location {{location | default("/")}}
proxy_set_header X-Forwarded-Port 443;
proxy_set_header Accept-Encoding "";
{% include 'roles/nginx-docker-reverse-proxy/templates/iframe.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/headers/iframe.conf.j2' %}
# WebSocket specific header
proxy_http_version 1.1;

View File

@ -18,18 +18,18 @@ server
{% if applications | get_oauth2_enabled(application_id) %}
{% if applications[application_id].oauth2_proxy.location is defined %}
{# Exposed and Unprotected Location #}
{% include 'proxy_pass.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2' %}
{% set oauth2_proxy_enabled = true %}
{% set location = applications[application_id].oauth2_proxy.location %}
{# Gated Location by OAuth2 Proxy #}
{% include 'proxy_pass.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2' %}
{% else %}
{% set oauth2_proxy_enabled = true %}
{# Protected Domain by OAuth2 Proxy #}
{% include 'proxy_pass.conf.j2'%}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2'%}
{% endif %}
{% else %}
{# Exposed Domain - Not protected by OAuth2 Proxy #}
{% include 'proxy_pass.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2' %}
{% endif %}
}

View File

@ -4,15 +4,14 @@ map $http_upgrade $connection_upgrade {
}
server {
server_name {{domain}};
server_name {{ domain }};
{% include 'roles/letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/nginx-modifier-all/templates/global.includes.conf.j2' %}
{% include 'roles/nginx-modifier-all/templates/global.includes.conf.j2'%}
client_max_body_size {{ client_max_body_size | default('100m') }};
keepalive_timeout 70;
sendfile on;
client_max_body_size 80m;
gzip on;
gzip_disable "msie6";
@ -25,24 +24,23 @@ server {
add_header Strict-Transport-Security "max-age=31536000";
{% include 'roles/nginx-docker-reverse-proxy/templates/proxy_pass.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_basic.conf.j2' %}
location /api/v1/streaming {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
{% if ws_path is defined %}
location {{ ws_path }} {
proxy_set_header Host $host;
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 https;
proxy_set_header Proxy "";
proxy_pass http://127.0.0.1:{{ports.localhost.web_socket[application_id]}};
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
tcp_nodelay on;
proxy_pass http://127.0.0.1:{{ ws_port }};
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
tcp_nodelay on;
}
{% endif %}
error_page 500 501 502 503 504 /500.html;
}

View File

@ -1,16 +1,35 @@
# Nginx Domain Setup Role 🚀
# Nginx Domain Setup 🚀
This role streamlines your Nginx configuration by performing several essential tasks:
## Description
- **Modify Nginx configuration** with the `nginx-modifier-all` role.
- **Request and receive HTTPS certificates** using the `nginx-https-get-cert` role.
- **Deploy a domain configuration file** from a Jinja2 template.
- **Optionally secure your domain** with OAuth2 via the `docker-oauth2-proxy` role if enabled.
This role bootstraps **per-domain Nginx configuration**: it requests TLS certificates, applies global modifiers, deploys a ready-made vHost file, and can optionally lock down access via OAuth2.
## Author
## Overview
Developed by [Kevin Veen-Birkenbach](https://www.veen.world) 😎
A higher-level orchestration wrapper, *nginx-domain-setup* ties together several lower-level roles:
---
1. **`nginx-modifier-all`** applies global tweaks and includes.
2. **`nginx-https-get-cert`** obtains Lets Encrypt certificates.
3. **Domain template deployment** copies a Jinja2 vHost from *nginx-docker-reverse-proxy*.
4. **`docker-oauth2-proxy`** *(optional)* protects the site with OAuth2.
Happy automating! 🎉
The result is a complete, reproducible domain rollout in a single playbook task.
## Purpose
Provide **one-stop, idempotent domain provisioning** for Nginx-based homelabs or small production environments.
## Features
- **End-to-end TLS** — certificate retrieval and secure headers included.
- **Template-driven vHosts** — choose *basic* or *ws_generic* flavours (or your own).
- **Conditional OAuth2** — easily toggle authentication per application.
- **Handler-safe** — automatically triggers an Nginx reload when templates change.
- **Composable** — designed to be called repeatedly for many domains.
## Credits 📝
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at <https://www.veen.world>
Part of the **CyMaIS Project** — licensed under the [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl)

View File

@ -0,0 +1,5 @@
# default vhost flavour
vhost_flavour: "basic" # valid: basic | ws_generic
# build the full template path from the flavour
vhost_template_src: "roles/nginx-docker-reverse-proxy/templates/vhost/{{ vhost_flavour }}.conf.j2"

View File

@ -0,0 +1,25 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Automated domain provisioning (TLS, vHost, OAuth2) for Nginx."
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
min_ansible_version: "2.9"
platforms:
- name: Archlinux
versions:
- rolling
galaxy_tags:
- nginx
- tls
- letsencrypt
- oauth2
- automation
- archlinux
repository: https://s.veen.world/cymais
issue_tracker_url: https://s.veen.world/cymaisissues
documentation: https://s.veen.world/cymais

View File

@ -2,10 +2,10 @@
include_role:
name: nginx-https-get-cert-modify-all
- name: "copy nginx domain configuration to {{configuration_destination}}"
template:
src: "roles/nginx-docker-reverse-proxy/templates/domain.conf.j2"
dest: "{{configuration_destination}}"
- name: "copy nginx domain configuration to {{ configuration_destination }}"
template:
src: "{{ vhost_template_src }}"
dest: "{{ configuration_destination }}"
notify: restart nginx
- name: "include the docker-oauth2-proxy role {{domain}}"

View File

@ -1193,4 +1193,9 @@ input.ng-empty::placeholder,.ng-empty::placeholder {
.kanban-swimlane-title {
border-bottom: none;
}
.navbar-toggler {
background-color: rgba(var(--color-rgb-01-75), 0.9);
border-color: var(--color-01-67)
}

View File

@ -6,7 +6,7 @@ server
{% include 'roles/nginx-modifier-all/templates/global.includes.conf.j2'%}
{% include 'roles/nginx-docker-reverse-proxy/templates/iframe.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/headers/iframe.conf.j2' %}
charset utf-8;
location /

View File

@ -6,7 +6,7 @@ server
{% include 'roles/nginx-modifier-all/templates/global.includes.conf.j2'%}
{% include 'roles/nginx-docker-reverse-proxy/templates/iframe.conf.j2' %}
{% include 'roles/nginx-docker-reverse-proxy/templates/headers/iframe.conf.j2' %}
charset utf-8;
location /

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ locale }}">
<html lang="{{ HOST_LL }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -1,7 +1,9 @@
---
- name: install nginx
pacman:
name: nginx
name:
- nginx
- nginx-mod-stream # Enable Stream support
state: present
notify: restart nginx
when: run_once_nginx is not defined

View File

@ -1,3 +1,4 @@
load_module /usr/lib/nginx/modules/ngx_stream_module.so; # Enable Stream support
worker_processes auto;
events

View File

@ -216,6 +216,11 @@
include_role:
name: docker-presentation
- name: setup espocrm hosts
when: ("espocrm" in group_names)
include_role:
name: docker-espocrm
# Native Webserver Roles
- name: setup nginx-serve-htmls
when: ("nginx-serve-htmls" in group_names)

View File

@ -4,7 +4,7 @@ networks:
central_{{ database_type }}:
external: true
{% endif %}
{% if applications[application_id].get('features', {}).get('ldap', false) | bool and applications.ldap.network.local | bool %}
{% if applications[application_id].get('features', {}).get('ldap', false) | bool and applications.ldap.network.docker | bool %}
central_ldap:
external: true
{% endif %}

View File

@ -3,7 +3,7 @@
{% if applications | get_database_central_storage(application_id) | bool and database_type is defined %}
central_{{ database_type }}:
{% endif %}
{% if applications[application_id].get('features', {}).get('ldap', false) | bool and applications.ldap.network.local|bool %}
{% if applications[application_id].get('features', {}).get('ldap', false) | bool and applications.ldap.network.docker|bool %}
central_ldap:
{% endif %}
default:

View File

@ -126,6 +126,30 @@ defaults_applications:
'database': true,
}) }}{% raw %}
## EspoCRM
espocrm:
version: "fpm-alpine"
users:
administrator:
username: "{{ users.administrator.username }}"
email: "{{ users.administrator.email }}"
credentials:
administrator:
password: "{{ users.administrator.password }}"
database:
# password: # Set in your inventory file
{% endraw %}{{ features.render_features({
'matomo': true,
'css': true,
'iframe': false,
'ldap': true,
'oidc': true,
'database': true
}) }}{% raw %}
## File Server
file_server:
{% endraw %}{{ features.render_features({
@ -249,7 +273,8 @@ defaults_applications:
ldap:
version: "latest"
network:
local: True # Activates local network to allow other docker containers to connect
local: True # Activates local network. Necessary for LDIF import routines
docker: True # Activates docker network to allow other docker containers to connect
public: False # Set to true in inventory file if you want to expose the LDAP port to the internet
hostname: "ldap" # Hostname of the LDAP Server in the central_ldap network
webinterface: "lam" # The webinterface which should be used. Possible: lam and phpldapadmin
@ -258,7 +283,6 @@ defaults_applications:
username: "{{users.administrator.username}}" # Administrator username
# administrator_password: # CHANGE for security reasons in inventory file
# administrator_database_password: # CHANGE for security reasons in inventory file
force_import: False # Forces the import of the LDIF files
{% endraw %}{{ features.render_features({
'ldap': true,
}) }}{% raw %}