Compare commits

...

4 Commits

23 changed files with 194 additions and 104 deletions

View File

@ -77,3 +77,7 @@ defaults_redirect_domain_mappings:
- { source: "taiga.{{primary_domain}}", target: "{{domains.taiga}}" } - { source: "taiga.{{primary_domain}}", target: "{{domains.taiga}}" }
- { source: "videos.{{primary_domain}}", target: "{{domains.peertube}}" } - { source: "videos.{{primary_domain}}", target: "{{domains.peertube}}" }
- { source: "wordpress.{{primary_domain}}", target: "{{domains.wordpress[0]}}" } - { source: "wordpress.{{primary_domain}}", target: "{{domains.wordpress[0]}}" }
# Domains which are deprecated and should be cleaned up
deprecated_domains: []

View File

@ -1,32 +0,0 @@
# 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

@ -1,25 +0,0 @@
# ⚙️ 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

@ -2,15 +2,38 @@
## Description ## 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. Enhance your sales and service processes with EspoCRM, an open-source CRM featuring workflow automation, LDAP/OIDC single sign-on, and a sleek, lightweight UI! 🚀💼
This Ansible role deploys EspoCRM with Docker, mirroring the structure of the Mastodon role for a consistent operations workflow.
## Overview ## 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: This Ansible role deploys EspoCRM using Docker. It handles:
- [Installation.md](./Installation.md)
- [Administration.md](./Administration.md) - MariaDB database provisioning via the `docker-central-database` role
- Nginx domain setup with WebSocket and reverse-proxy configuration
- Environment variable management through Jinja2 templates
- Docker Compose orchestration for **web**, **daemon**, and **websocket** services
- Automatic OIDC scope configuration within the EspoCRM container
With this role, you'll have a production-ready CRM environment that's secure, scalable, and real-time.
## Features
- **Workflow Automation:** Create and manage automated CRM processes with ease 🛠️
- **LDAP/OIDC SSO:** Integrate with corporate identity providers for seamless login 🔐
- **WebSocket Notifications:** Real-time updates via ZeroMQ and WebSockets 🌐
- **Config via Templates:** Fully customizable `.env` and `docker-compose.yml` with Jinja2 ⚙️
- **Health Checks & Logging:** Monitor service health and logs with built-in checks and journald 📈
- **Modular Role Composition:** Leverages central roles for database and Nginx, ensuring consistency across deployments 🔄
## Additional Resources
- [EspoCRM Official Website](https://www.espocrm.com/) 🌍
- [EspoCRM Documentation](https://docs.espocrm.com/) 📖
- [CyMaIS Project Repository](https://github.com/kevinveenbirkenbach/cymais) 🔗
## Credits
Developed and maintained by **Kevin Veen-Birkenbach**.
Consulting & Coaching Solutions: [veen.world](https://www.veen.world) 🌟
Part of the [CyMaIS Project](https://github.com/kevinveenbirkenbach/cymais) 📂
License: [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl) ⚖️

View File

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

View File

@ -18,5 +18,5 @@ galaxy_info:
issue_tracker_url: "https://s.veen.world/cymaisissues" issue_tracker_url: "https://s.veen.world/cymaisissues"
documentation: "https://s.veen.world/cymais" documentation: "https://s.veen.world/cymais"
logo: logo:
class: "fa-solid fa-briefcase" class: "fa-solid fa-phone"
dependencies: [] dependencies: []

View File

@ -16,3 +16,17 @@
- name: "copy docker-compose.yml and env file" - name: "copy docker-compose.yml and env file"
include_tasks: copy-docker-compose-and-env.yml include_tasks: copy-docker-compose-and-env.yml
- name: Set OIDC scopes in EspoCRM config (inside web container)
ansible.builtin.shell: |
docker compose exec -T web php -r '
require "/var/www/html/bootstrap.php";
$writer = (new \Espo\Core\Application())
->getContainer()
->get("injectableFactory")
->create("\Espo\Core\Utils\Config\ConfigWriter");
$writer->set("oidcScopes", ["openid", "profile", "email"]);
$writer->save();
'
args:
chdir: "{{ docker_compose.directories.instance }}"

View File

@ -5,7 +5,6 @@ services:
web: web:
image: espocrm/espocrm:{{ applications.espocrm.version }} image: espocrm/espocrm:{{ applications.espocrm.version }}
{% include 'roles/docker-compose/templates/services/base.yml.j2' %} {% include 'roles/docker-compose/templates/services/base.yml.j2' %}
command: "php-fpm"
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"] test: ["CMD", "curl", "-f", "http://localhost/"]
ports: ports:
@ -41,7 +40,7 @@ services:
volumes: volumes:
- data:/var/www/html - data:/var/www/html
ports: ports:
- "{{ ports.localhost.websocket[application_id] | default('127.0.0.1:8081') }}:8080" - "127.0.0.1:{{ ports.localhost.websocket[application_id] }}:8080"
{% include 'templates/docker/compose/volumes.yml.j2' %} {% include 'templates/docker/compose/volumes.yml.j2' %}
data: data:

View File

@ -35,8 +35,8 @@ ESPOCRM_CONFIG_TIME_ZONE={{ HOST_TIMEZONE }}
# ESPOCRM_CONFIG_WEEK_START: 0 = Sunday, 1 = Monday # ESPOCRM_CONFIG_WEEK_START: 0 = Sunday, 1 = Monday
ESPOCRM_CONFIG_WEEK_START=1 ESPOCRM_CONFIG_WEEK_START=1
ESPOCRM_CONFIG_DEFAULT_CURRENCY={{ HOST_CURRENCY }} ESPOCRM_CONFIG_DEFAULT_CURRENCY={{ HOST_CURRENCY }}
ESPOCRM_CONFIG_THOUSAND_SEPARATOR={{ HOST_THOUSAND_SEPARATOR }} #ESPOCRM_CONFIG_THOUSAND_SEPARATOR={{ HOST_THOUSAND_SEPARATOR }}
ESPOCRM_CONFIG_DECIMAL_MARK={{HOST_DECIMAL_MARK}} #ESPOCRM_CONFIG_DECIMAL_MARK={{HOST_DECIMAL_MARK}}
# ------------------------------------------------ # ------------------------------------------------
# Logger # Logger
@ -82,6 +82,7 @@ ESPOCRM_CONFIG_LDAP_USER_LOGIN_FILTER=(sAMAccountName=%USERNAME%)
# ------------------------------------------------ # ------------------------------------------------
# OpenID Connect settings # OpenID Connect settings
# ------------------------------------------------ # ------------------------------------------------
ESPOCRM_CONFIG_OIDC_ALLOW_ADMIN_USER=true
ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Oidc ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Oidc
ESPOCRM_CONFIG_OIDC_FALLBACK=false # set true if you want LDAP as fallback ESPOCRM_CONFIG_OIDC_FALLBACK=false # set true if you want LDAP as fallback
@ -94,5 +95,11 @@ ESPOCRM_CONFIG_OIDC_USER_INFO_ENDPOINT={{ oidc.client.user_info_url }}
ESPOCRM_CONFIG_OIDC_JWKS_ENDPOINT={{ oidc.client.certs }} ESPOCRM_CONFIG_OIDC_JWKS_ENDPOINT={{ oidc.client.certs }}
ESPOCRM_CONFIG_OIDC_AUTHORIZATION_REDIRECT_URI=https://{{ domains[application_id] }}/oidc/callback ESPOCRM_CONFIG_OIDC_AUTHORIZATION_REDIRECT_URI=https://{{ domains[application_id] }}/oidc/callback
ESPOCRM_CONFIG_OIDC_SCOPES=openid,profile,email #ESPOCRM_CONFIG_OIDC_SCOPES=openid,profile,email # Defined in main.yml
ESPOCRM_CONFIG_OIDC_CREATE_USER=true
ESPOCRM_CONFIG_OIDC_SYNC=true
ESPOCRM_CONFIG_OIDC_USERNAME_CLAIM={{oidc.attributes.username}}
# ESPOCRM_CONFIG_OIDC_SYNC_TEAMS=true # (optional) Gruppen-→-Team-Mapping
# ESPOCRM_CONFIG_OIDC_GROUP_CLAIM=group
{% endif %} {% endif %}

View File

@ -1,4 +1,4 @@
# Todos # Todos
- Implement auto password hash
- Implement auto memberof setup - Implement auto memberof setup
- Create a Dockerfile (may in an own repository) with memberOf - Create a Dockerfile (may in an own repository) with memberOf
- Find a better decoupling solution for nextcloud

View File

@ -37,7 +37,6 @@
include_tasks: create_ldif_files.yml include_tasks: create_ldif_files.yml
loop: loop:
- configuration - configuration
- schema
loop_control: loop_control:
loop_var: folder loop_var: folder
@ -50,6 +49,37 @@
- python-ldap - python-ldap
state: present state: present
- name: Create Nextcloud Schema
ldap_entry:
dn: "cn=nextcloud,cn=schema,cn=config"
objectClass:
- top
- olcSchemaConfig
attributes:
cn: nextcloud
olcAttributeTypes:
- >-
( 1.3.6.1.4.1.99999.1
NAME 'nextcloudQuota'
DESC 'Quota for Nextcloud'
EQUALITY integerMatch
ORDERING integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
)
olcObjectClasses:
- >-
( 1.3.6.1.4.1.99999.2
NAME 'nextcloudUser'
DESC 'Auxiliary class for Nextcloud attributes'
AUXILIARY
MAY ( nextcloudQuota )
)
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "cn={{ applications.ldap.users.administrator.username }},cn=config"
bind_pw: "{{ applications.ldap.administrator_password }}"
state: present
############################################################################### ###############################################################################
# 1) Create the LDAP entry if it does not yet exist # 1) Create the LDAP entry if it does not yet exist
############################################################################### ###############################################################################

View File

@ -1,19 +0,0 @@
# nextcloud.schema
dn: cn=nextcloud,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: nextcloud
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1
NAME 'nextcloudQuota'
DESC 'Quota for Nextcloud'
EQUALITY integerMatch
ORDERING integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
)
olcObjectClasses: ( 1.3.6.1.4.1.99999.2
NAME 'nextcloudUser'
DESC 'Auxiliary class for Nextcloud attributes'
AUXILIARY
MAY ( nextcloudQuota )
)

View File

@ -1 +0,0 @@
This folder contains schemas for the different applications to import

View File

@ -51,6 +51,7 @@
when: when:
- users[mailu_user].mailu_token is not defined - users[mailu_user].mailu_token is not defined
- mailu_user_existing_token is not none - mailu_user_existing_token is not none
- mailu_user_existing_token.id is defined
register: mailu_token_delete register: mailu_token_delete
changed_when: mailu_token_delete.rc == 0 changed_when: mailu_token_delete.rc == 0

View File

@ -22,4 +22,6 @@ galaxy_info:
- archlinux - archlinux
repository: https://s.veen.world/cymais repository: https://s.veen.world/cymais
issue_tracker_url: https://s.veen.world/cymaisissues issue_tracker_url: https://s.veen.world/cymaisissues
documentation: https://s.veen.world/cymais documentation: https://s.veen.world/cymais
dependencies:
- nginx

View File

@ -0,0 +1,25 @@
# nginx-domains-cleanup
## Description
This Ansible role removes Nginx configuration files and revokes and deletes Certbot certificates for domains marked as deprecated.
## Overview
Optimized for idempotent cleanup operations, this role:
- Deletes Nginx server configuration files in `/etc/nginx/conf.d/http/servers/` for each domain listed in `deprecated_domains`.
- Revokes and deletes corresponding Certbot certificates.
- Ensures cleanup tasks execute only once per playbook run.
- Notifies Nginx to restart after removing configurations.
## Purpose
Streamline the decommissioning of outdated or deprecated domains by automating the removal of Nginx server blocks and their SSL certificates.
## Features
- **Nginx Cleanup:** Safely removes server configuration files.
- **Certbot Integration:** Revokes and deletes certificates without manual intervention.
- **Idempotent Execution:** Utilizes a `run_once` flag to prevent repeated runs.
- **Service Notification:** Triggers an Nginx restart handler upon cleanup.

View File

@ -0,0 +1,24 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Remove Nginx configuration files and revoke/delete Certbot certificates for deprecated domains"
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
- cleanup
- certbot
- domains
repository: "https://s.veen.world/cymais"
issue_tracker_url: "https://s.veen.world/cymaisissues"
documentation: "https://s.veen.world/cymais"
dependencies:
- nginx

View File

@ -0,0 +1,39 @@
---
- name: "Remove Nginx configuration for deprecated domains"
ansible.builtin.file:
path: "/etc/nginx/conf.d/http/servers/{{ item }}"
state: absent
loop: "{{ deprecated_domains }}"
loop_control:
label: "{{ item }}"
notify: restart nginx
when:
- mode_cleanup | bool
- run_once_nginx_domains_cleanup is not defined
- name: "Revoke Certbot certificate for {{ item }}"
ansible.builtin.command:
cmd: "certbot revoke -n --cert-name {{ item }}"
become: true
loop: "{{ deprecated_domains }}"
loop_control:
label: "{{ item }}"
when:
- mode_cleanup | bool
- run_once_nginx_domains_cleanup is not defined
- name: "Delete Certbot certificate for {{ item }}"
ansible.builtin.command:
cmd: "certbot delete -n --cert-name {{ item }}"
become: true
loop: "{{ deprecated_domains }}"
loop_control:
label: "{{ item }}"
when:
- mode_cleanup | bool
- run_once_nginx_domains_cleanup is not defined
- name: run the nginx_domains_cleanup role once
set_fact:
run_once_nginx_domains_cleanup: true
when: run_once_nginx_domains_cleanup is not defined

View File

@ -28,7 +28,7 @@
command: >- command: >-
certbot delete --cert-name {{ domain }} --non-interactive certbot delete --cert-name {{ domain }} --non-interactive
when: when:
- mode_cleanup | bool - mode_cleanup | bool
# Cleanup mode is enabled # Cleanup mode is enabled
- enable_wildcard_certificate | bool - enable_wildcard_certificate | bool
# Wildcard certificate is enabled # Wildcard certificate is enabled

View File

@ -1,3 +1,4 @@
dependencies: dependencies:
- nginx - nginx
- nginx-domains-cleanup
- letsencrypt - letsencrypt

View File

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

View File

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

View File

@ -128,7 +128,7 @@ defaults_applications:
## EspoCRM ## EspoCRM
espocrm: espocrm:
version: "fpm-alpine" version: "latest"
users: users:
administrator: administrator:
username: "{{ users.administrator.username }}" username: "{{ users.administrator.username }}"
@ -142,9 +142,9 @@ defaults_applications:
{% endraw %}{{ features.render_features({ {% endraw %}{{ features.render_features({
'matomo': true, 'matomo': true,
'css': true, 'css': false,
'iframe': false, 'iframe': false,
'ldap': true, 'ldap': false,
'oidc': true, 'oidc': true,
'database': true 'database': true
}) }}{% raw %} }) }}{% raw %}