Optimized Docker Matrix Role in Preparation for use on CyMaIS.Cloud Server

This commit is contained in:
Kevin Veen-Birkenbach 2025-05-15 21:11:21 +02:00
parent 9c65c320f9
commit 76aef5949b
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
20 changed files with 219 additions and 84 deletions

View File

@ -0,0 +1,2 @@
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -1,3 +1,4 @@
CYMAIS_ENVIRONMENT: "production"
HOST_CURRENCY: "EUR" HOST_CURRENCY: "EUR"
HOST_TIMEZONE: "UTC" HOST_TIMEZONE: "UTC"

View File

@ -12,7 +12,7 @@ SSH_PORT={{ports.public.ssh[application_id]}}
SSH_LISTEN_PORT=22 SSH_LISTEN_PORT=22
DOMAIN={{domains[application_id]}} DOMAIN={{domains[application_id]}}
SSH_DOMAIN={{domains[application_id]}} SSH_DOMAIN={{domains[application_id]}}
RUN_MODE="{{run_mode}}" RUN_MODE="{{ 'dev' if (CYMAIS_ENVIRONMENT | lower) == 'development' else 'prod' }}"
ROOT_URL="{{ web_protocol }}://{{domains[application_id]}}/" ROOT_URL="{{ web_protocol }}://{{domains[application_id]}}/"
# Mail Configuration # Mail Configuration

View File

@ -14,4 +14,6 @@ features:
csp: csp:
flags: flags:
script-src: script-src:
unsafe-inline: true
style-src:
unsafe-inline: true unsafe-inline: true

View File

@ -0,0 +1,5 @@
# Todo
- Enable Whatsapp by default
- Enable Telegram by default
- Enable Slack by default
- Enable ChatGPT by default

View File

@ -0,0 +1,2 @@
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -0,0 +1,13 @@
def filter_enabled_bridges(bridges, plugins):
"""
Return only those bridge definitions whose 'bridge_name' is set to True in plugins.
:param bridges: list of dicts, each with a 'bridge_name' key
:param plugins: dict mapping bridge_name to a boolean
"""
return [b for b in bridges if plugins.get(b['bridge_name'], False)]
class FilterModule(object):
def filters(self):
return {
'filter_enabled_bridges': filter_enabled_bridges,
}

View File

@ -1,4 +1,13 @@
--- ---
- name: Load bridges configuration
include_vars:
file: "bridges.yml"
- name: Filter enabled bridges and register as fact
set_fact:
bridges: "{{ bridges_configuration | filter_enabled_bridges(applications[application_id].plugins) }}"
changed_when: false
- name: "include docker-central-database" - name: "include docker-central-database"
include_role: include_role:
name: docker-central-database name: docker-central-database

View File

@ -25,7 +25,11 @@ services:
interval: 1m interval: 1m
timeout: 10s timeout: 10s
retries: 3 retries: 3
{% if bridges | bool %}
{% include 'templates/docker/container/depends-on-also-database.yml.j2' %} {% include 'templates/docker/container/depends-on-also-database.yml.j2' %}
{% else %}
{% include 'templates/docker/container/depends-on-just-database.yml.j2' %}
{% endif %}
{% for item in bridges %} {% for item in bridges %}
mautrix-{{item.bridge_name}}: mautrix-{{item.bridge_name}}:
condition: service_healthy condition: service_healthy
@ -61,47 +65,47 @@ services:
retries: 3 retries: 3
{% include 'templates/docker/container/networks.yml.j2' %} {% include 'templates/docker/container/networks.yml.j2' %}
{% endfor %} {% endfor %}
# Deactivated chatgpt. {% if applications[application_id] | bool %}
# @todo needs to be reactivated as soon as bug is found matrix-chatgpt-bot:
# matrix-chatgpt-bot: restart: {{docker_restart_policy}}
# restart: {{docker_restart_policy}} container_name: matrix-chatgpt
# container_name: matrix-chatgpt image: ghcr.io/matrixgpt/matrix-chatgpt-bot:latest
# image: ghcr.io/matrixgpt/matrix-chatgpt-bot:latest volumes:
# volumes: - chatgpt_data:/storage
# - chatgpt_data:/storage environment:
# environment: OPENAI_API_KEY: '{{applications[application_id].credentials.chatgpt_bridge_openai_api_key}}'
# OPENAI_API_KEY: '{{applications[application_id].credentials.chatgpt_bridge_openai_api_key}}' # Uncomment the next two lines if you are using Azure OpenAI API
# # Uncomment the next two lines if you are using Azure OpenAI API # OPENAI_AZURE: 'false'
# # OPENAI_AZURE: 'false' # CHATGPT_REVERSE_PROXY: 'your-completion-endpoint-here'
# # CHATGPT_REVERSE_PROXY: 'your-completion-endpoint-here' CHATGPT_CONTEXT: 'thread'
# CHATGPT_CONTEXT: 'thread' CHATGPT_API_MODEL: 'gpt-3.5-turbo'
# CHATGPT_API_MODEL: 'gpt-3.5-turbo' # Uncomment and edit the next line if needed
# # Uncomment and edit the next line if needed # CHATGPT_PROMPT_PREFIX: 'Instructions:\nYou are ChatGPT, a large language model trained by OpenAI.'
# # CHATGPT_PROMPT_PREFIX: 'Instructions:\nYou are ChatGPT, a large language model trained by OpenAI.' # CHATGPT_IGNORE_MEDIA: 'false'
# # CHATGPT_IGNORE_MEDIA: 'false' CHATGPT_REVERSE_PROXY: 'https://api.openai.com/v1/chat/completions'
# CHATGPT_REVERSE_PROXY: 'https://api.openai.com/v1/chat/completions' # Uncomment and edit the next line if needed
# # Uncomment and edit the next line if needed # CHATGPT_TEMPERATURE: '0.8'
# # CHATGPT_TEMPERATURE: '0.8' # Uncomment and edit the next line if needed
# # Uncomment and edit the next line if needed #CHATGPT_MAX_CONTEXT_TOKENS: '4097'
# #CHATGPT_MAX_CONTEXT_TOKENS: '4097' CHATGPT_MAX_PROMPT_TOKENS: '3000'
# CHATGPT_MAX_PROMPT_TOKENS: '3000' KEYV_BACKEND: 'file'
# KEYV_BACKEND: 'file' KEYV_URL: ''
# KEYV_URL: '' KEYV_BOT_ENCRYPTION: 'false'
# KEYV_BOT_ENCRYPTION: 'false' KEYV_BOT_STORAGE: 'true'
# KEYV_BOT_STORAGE: 'true' MATRIX_HOMESERVER_URL: 'https://{{domains.synapse}}'
# MATRIX_HOMESERVER_URL: 'https://{{domains.synapse}}' MATRIX_BOT_USERNAME: '@chatgptbot:{{applications.matrix.server_name}}'
# MATRIX_BOT_USERNAME: '@chatgptbot:{{applications.matrix.server_name}}' MATRIX_ACCESS_TOKEN: '{{ applications[application_id].credentials.chatgpt_bridge_access_token | default('') }}'
# MATRIX_ACCESS_TOKEN: '{{ applications[application_id].credentials.chatgpt_bridge_access_token | default('') }}' MATRIX_BOT_PASSWORD: '{{applications[application_id].credentials.chatgpt_bridge_user_password}}'
# MATRIX_BOT_PASSWORD: '{{applications[application_id].credentials.chatgpt_bridge_user_password}}' MATRIX_DEFAULT_PREFIX: '!chatgpt'
# MATRIX_DEFAULT_PREFIX: '!chatgpt' MATRIX_DEFAULT_PREFIX_REPLY: 'false'
# MATRIX_DEFAULT_PREFIX_REPLY: 'false' #MATRIX_BLACKLIST: ''
# #MATRIX_BLACKLIST: '' MATRIX_WHITELIST: ':{{applications.matrix.server_name}}'
# MATRIX_WHITELIST: ':{{applications.matrix.server_name}}' MATRIX_AUTOJOIN: 'true'
# MATRIX_AUTOJOIN: 'true' MATRIX_ENCRYPTION: 'true'
# MATRIX_ENCRYPTION: 'true' MATRIX_THREADS: 'true'
# MATRIX_THREADS: 'true' MATRIX_PREFIX_DM: 'false'
# MATRIX_PREFIX_DM: 'false' MATRIX_RICH_TEXT: 'true'
# MATRIX_RICH_TEXT: 'true' {% endif %}
{% include 'templates/docker/compose/volumes.yml.j2' %} {% include 'templates/docker/compose/volumes.yml.j2' %}
synapse_data: synapse_data:

View File

@ -61,7 +61,9 @@ oidc_providers:
backchannel_logout_enabled: true backchannel_logout_enabled: true
{% endif %} {% endif %}
{% if bridges | bool %}
app_service_config_files: app_service_config_files:
{% for item in bridges %} {% for item in bridges %}
- {{registration_file_folder}}{{item.bridge_name}}.registration.yaml - {{registration_file_folder}}{{item.bridge_name}}.registration.yaml
{% endfor %} {% endfor %}
{% endif %}

View File

@ -0,0 +1,30 @@
bridges_configuration:
- database_password: "{{ applications[application_id].credentials.mautrix_whatsapp_bridge_database_password }}"
database_username: "mautrix_whatsapp_bridge"
database_name: "mautrix_whatsapp_bridge"
bridge_name: "whatsapp"
- database_password: "{{ applications[application_id].credentials.mautrix_telegram_bridge_database_password }}"
database_username: "mautrix_telegram_bridge"
database_name: "mautrix_telegram_bridge"
bridge_name: "telegram"
- database_password: "{{ applications[application_id].credentials.mautrix_signal_bridge_database_password }}"
database_username: "mautrix_signal_bridge"
database_name: "mautrix_signal_bridge"
bridge_name: "signal"
- database_password: "{{ applications[application_id].credentials.mautrix_slack_bridge_database_password }}"
database_username: "mautrix_slack_bridge"
database_name: "mautrix_slack_bridge"
bridge_name: "slack"
- database_password: "{{ applications[application_id].credentials.mautrix_facebook_bridge_database_password }}"
database_username: "mautrix_facebook_bridge"
database_name: "mautrix_facebook_bridge"
bridge_name: "facebook"
- database_password: "{{ applications[application_id].credentials.mautrix_instagram_bridge_database_password }}"
database_username: "mautrix_instagram_bridge"
database_name: "mautrix_instagram_bridge"
bridge_name: "instagram"

View File

@ -3,7 +3,6 @@ users:
administrator: administrator:
username: "{{users.administrator.username}}" # Accountname of the matrix admin username: "{{users.administrator.username}}" # Accountname of the matrix admin
playbook_tags: "setup-all,start" # For the initial update use: install-all,ensure-matrix-users-created,start playbook_tags: "setup-all,start" # For the initial update use: install-all,ensure-matrix-users-created,start
role: "compose" # Role to setup Matrix. Valid values: ansible, compose
server_name: "{{primary_domain}}" # Adress for the account names etc. server_name: "{{primary_domain}}" # Adress for the account names etc.
synapse: synapse:
version: "latest" version: "latest"
@ -30,3 +29,14 @@ csp:
script-src: script-src:
- "{{ domains.synapse }}" - "{{ domains.synapse }}"
- "https://cdn.jsdelivr.net" - "https://cdn.jsdelivr.net"
plugins:
# You need to enable them in the inventory file
chatgpt: false
facebook: false
immesage: false
instagram: false
signal: false
slack: false
telegram: false
whatsapp: false

View File

@ -1,38 +1,5 @@
--- ---
application_id: "matrix" application_id: "matrix"
database_type: "postgres" database_type: "postgres"
registration_file_folder: "/data/" registration_file_folder: "/data/"
well_known_directory: "{{nginx.directories.data.well_known}}/matrix/" well_known_directory: "{{nginx.directories.data.well_known}}/matrix/"
bridges:
- database_password: "{{ applications[application_id].credentials.mautrix_whatsapp_bridge_database_password }}"
database_username: "mautrix_whatsapp_bridge"
database_name: "mautrix_whatsapp_bridge"
bridge_name: "whatsapp"
- database_password: "{{ applications[application_id].credentials.mautrix_telegram_bridge_database_password }}"
database_username: "mautrix_telegram_bridge"
database_name: "mautrix_telegram_bridge"
bridge_name: "telegram"
- database_password: "{{ applications[application_id].credentials.mautrix_signal_bridge_database_password }}"
database_username: "mautrix_signal_bridge"
database_name: "mautrix_signal_bridge"
bridge_name: "signal"
# Deactivated temporary, due to bug which is hard to find
# @todo Reactivate
# - database_password: "{{ applications[application_id].credentials.mautrix_slack_bridge_database_password }}"
# database_username: "mautrix_slack_bridge"
# database_name: "mautrix_slack_bridge"
# bridge_name: "slack"
- database_password: "{{ applications[application_id].credentials.mautrix_facebook_bridge_database_password }}"
database_username: "mautrix_facebook_bridge"
database_name: "mautrix_facebook_bridge"
bridge_name: "facebook"
- database_password: "{{ applications[application_id].credentials.mautrix_instagram_bridge_database_password }}"
database_username: "mautrix_instagram_bridge"
database_name: "mautrix_instagram_bridge"
bridge_name: "instagram"

View File

@ -96,7 +96,7 @@
shell: > shell: >
docker compose exec web bash -c " docker compose exec web bash -c "
cd /app && cd /app &&
RAILS_ENV=production bundle exec rails runner \" RAILS_ENV={{ CYMAIS_ENVIRONMENT | lower }} bundle exec rails runner \"
user = User.find_by(mail: '{{ users.administrator.email }}'); user = User.find_by(mail: '{{ users.administrator.email }}');
if user.nil?; if user.nil?;
puts 'User with email {{ users.administrator.email }} not found.'; puts 'User with email {{ users.administrator.email }} not found.';

View File

@ -49,7 +49,7 @@
- name: Set settings in OpenProject - name: Set settings in OpenProject
shell: > shell: >
docker compose exec web bash -c "cd /app && docker compose exec web bash -c "cd /app &&
RAILS_ENV=production bundle exec rails runner \"Setting[:{{ item.key }}] = '{{ item.value }}'\"" RAILS_ENV={{ CYMAIS_ENVIRONMENT | lower }} bundle exec rails runner \"Setting[:{{ item.key }}] = '{{ item.value }}'\""
args: args:
chdir: "{{ docker_compose.directories.instance }}" chdir: "{{ docker_compose.directories.instance }}"
loop: "{{ openproject_rails_settings | dict2items }}" loop: "{{ openproject_rails_settings | dict2items }}"

View File

@ -149,7 +149,7 @@ def update_mastodon():
Runs the database migration for Mastodon to ensure all required tables are up to date. Runs the database migration for Mastodon to ensure all required tables are up to date.
""" """
print("Starting Mastodon database migration.") print("Starting Mastodon database migration.")
run_command("docker compose exec -T web bash -c 'RAILS_ENV=production bin/rails db:migrate'") run_command("docker compose exec -T web bash -c 'RAILS_ENV={{ CYMAIS_ENVIRONMENT | lower }} bin/rails db:migrate'")
print("Mastodon database migration complete.") print("Mastodon database migration complete.")
def upgrade_listmonk(): def upgrade_listmonk():

View File

@ -0,0 +1,64 @@
import os
import sys
import unittest
# Add the filter_plugins directory from the docker-matrix role to the import path
sys.path.insert(
0,
os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../roles/docker-matrix")
),
)
from filter_plugins.bridge_filters import filter_enabled_bridges
class TestBridgeFilters(unittest.TestCase):
def test_no_bridges_returns_empty_list(self):
# When there are no bridges defined, result should be an empty list
self.assertEqual(filter_enabled_bridges([], {}), [])
def test_all_bridges_disabled(self):
# Define two bridges, but plugins dict has them disabled or missing
bridges = [
{'bridge_name': 'whatsapp', 'config': {}},
{'bridge_name': 'telegram', 'config': {}},
]
plugins = {
'whatsapp': False,
'telegram': False,
}
result = filter_enabled_bridges(bridges, plugins)
self.assertEqual(result, [])
def test_some_bridges_enabled(self):
# Only bridges with True in plugins should be returned
bridges = [
{'bridge_name': 'whatsapp', 'version': '1.0'},
{'bridge_name': 'telegram', 'version': '1.0'},
{'bridge_name': 'signal', 'version': '1.0'},
]
plugins = {
'whatsapp': True,
'telegram': False,
'signal': True,
}
result = filter_enabled_bridges(bridges, plugins)
expected = [
{'bridge_name': 'whatsapp', 'version': '1.0'},
{'bridge_name': 'signal', 'version': '1.0'},
]
self.assertEqual(result, expected)
def test_bridge_without_plugin_entry_defaults_to_disabled(self):
# If a bridge_name is not present in plugins, it should be treated as disabled
bridges = [
{'bridge_name': 'facebook', 'enabled': True},
]
plugins = {} # no entries
result = filter_enabled_bridges(bridges, plugins)
self.assertEqual(result, [])
if __name__ == "__main__":
unittest.main()

View File

@ -1,6 +1,16 @@
# tests/unit/test_configuration_filters.py # tests/unit/test_configuration_filters.py
import unittest import unittest
import sys
import os
sys.path.insert(
0,
os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../")
),
)
from filter_plugins.configuration_filters import ( from filter_plugins.configuration_filters import (
is_feature_enabled, is_feature_enabled,
) )

View File

@ -1,6 +1,16 @@
import unittest import unittest
import hashlib import hashlib
import base64 import base64
import sys
import os
sys.path.insert(
0,
os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../")
),
)
from filter_plugins.csp_filters import FilterModule, AnsibleFilterError from filter_plugins.csp_filters import FilterModule, AnsibleFilterError
class TestCspFilters(unittest.TestCase): class TestCspFilters(unittest.TestCase):

View File

@ -2,8 +2,12 @@ import os
import sys import sys
import unittest import unittest
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) sys.path.insert(
sys.path.insert(0, PROJECT_ROOT) 0,
os.path.abspath(
os.path.join(os.path.dirname(__file__), "../../")
),
)
from filter_plugins.redirect_filters import FilterModule from filter_plugins.redirect_filters import FilterModule