Optimized listmonk settings

This commit is contained in:
Kevin Veen-Birkenbach 2025-04-24 13:11:25 +02:00
parent c9ab0cd7cc
commit 250f26e03c
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
26 changed files with 292 additions and 42 deletions

View File

@ -1,12 +1,14 @@
# General
pause_duration: "120" # Database delay to wait for the central database before continue tasks
timezone: "UTC"
timezone: "Etc/UTC"
locale: "en" # Some applications are case sensitive
# Deployment mode
deployment_mode: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster mode.
web_protocol: "https" # Web protocol type. Use https or http. If you run local you need to change it to http
## Domain
primary_domain_tld: "localhost" # Top Level Domain of the server
primary_domain_sld: "cymais" # Second Level Domain of the server

View File

@ -9,7 +9,7 @@
## Helper Variables:
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
_oidc_client_issuer_url: "https://{{domains.keycloak}}/realms/{{_oidc_client_realm}}"
_oidc_client_issuer_url: "{{ web_protocol }}://{{domains.keycloak}}/realms/{{_oidc_client_realm}}"
defaults_oidc:
client:

View File

@ -28,4 +28,4 @@ defaults_service_provider:
legal:
editorial_responsible: "Johannes Gutenberg"
source_code: "https://github.com/kevinveenbirkenbach/cymais"
imprint: "https://{{domains.html_server}}/imprint.html"
imprint: "{{ web_protocol }}://{{domains.html_server}}/imprint.html"

View File

@ -22,8 +22,8 @@ services:
dockerfile: Dockerfile
# It doesn't compile yet with this parameters. @todo Fix it
args:
REACT_APP_PDS_URL: "https://{{domains.bluesky_api}}" # URL des PDS
REACT_APP_API_URL: "https://{{domains.bluesky_api}}" # API-URL des PDS
REACT_APP_PDS_URL: "{{ web_protocol }}://{{domains.bluesky_api}}" # URL des PDS
REACT_APP_API_URL: "{{ web_protocol }}://{{domains.bluesky_api}}" # API-URL des PDS
REACT_APP_SITE_NAME: "{{primary_domain | upper}} - Bluesky"
REACT_APP_SITE_DESCRIPTION: "Decentral Social "
ports:

View File

@ -13,7 +13,7 @@ SSH_LISTEN_PORT=22
DOMAIN={{domains[application_id]}}
SSH_DOMAIN={{domains[application_id]}}
RUN_MODE="{{run_mode}}"
ROOT_URL="https://{{domains[application_id]}}/"
ROOT_URL="{{ web_protocol }}://{{domains[application_id]}}/"
# Mail Configuration
# @see https://docs.gitea.com/next/installation/install-with-docker#managing-deployments-with-environment-variables

View File

@ -517,7 +517,7 @@
"/realms/{{realm}}/account/*"
],
"webOrigins": [
"https://{{domains.keycloak}}"
"{{ web_protocol }}://{{domains.keycloak}}"
],
"notBefore": 0,
"bearerOnly": false,
@ -825,9 +825,9 @@
"clientId": "{{realm}}",
"name": "",
"description": "",
"rootUrl": "https://{{realm}}/",
"adminUrl": "https://{{realm}}/",
"baseUrl": "https://{{realm}}/",
"rootUrl": "{{ web_protocol }}://{{realm}}/",
"adminUrl": "{{ web_protocol }}://{{realm}}/",
"baseUrl": "{{ web_protocol }}://{{realm}}/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
@ -837,10 +837,10 @@
{%- for application, domain in domains.items() %}
{%- if applications[application] is defined and (applications | get_oauth2_enabled(application) or applications | get_oidc_enabled(application)) %}
{%- if domain is string %}
{%- set _ = redirect_uris.append("https://{}/*".format(domain)) %}
{%- set _ = redirect_uris.append("{{ web_protocol }}://{}/*".format(domain)) %}
{%- else %}
{%- for d in domain %}
{%- set _ = redirect_uris.append("https://{}/*".format(d)) %}
{%- set _ = redirect_uris.append("{{ web_protocol }}://{}/*".format(d)) %}
{%- endfor %}
{%- endif %}
{%- endif %}
@ -848,7 +848,7 @@
"redirectUris": {{ redirect_uris | tojson }},
"webOrigins": [
"https://*.{{primary_domain}}"
"{{ web_protocol }}://*.{{primary_domain}}"
],
"notBefore": 0,
"bearerOnly": false,
@ -865,7 +865,7 @@
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "0",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "https://{{primary_domain}}/*##+",
"post.logout.redirect.uris": "{{ web_protocol }}://{{primary_domain}}/*##+",
"frontchannel.logout.session.required": "true",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",

View File

@ -54,6 +54,7 @@
shell: |
docker exec -i {{ database_host }} psql \
-U {{ database_username }} \
-v ON_ERROR_STOP=1 \
-d {{ database_name }} << 'EOSQL'
UPDATE settings
SET value = '{{ item.value }}'::jsonb

View File

@ -1,4 +1,4 @@
TZ=Etc/UTC
TZ={{timezone}}
# Administrator setup

View File

@ -4,7 +4,10 @@ database_type: "postgres"
listmonk_settings:
- key: "app.root_url"
value: '"https://{{ domains[application_id] }}"'
value: '"{{ web_protocol }}://{{ domains[application_id] }}"'
- key: "app.notify_emails"
value: "{{ [ users.administrator.email ] | to_json }}"
# OIDC integration (conditional)
- key: "security.oidc"
@ -17,9 +20,178 @@ listmonk_settings:
} | to_json }}
when: applications[application_id].features.oidc | bool
# hCaptcha toggles and credentials\ n - key: "security.enable_captcha"
value: "true"
# hCaptcha toggles and credentials
- key: "security.enable_captcha"
value: 'true'
- key: "security.captcha_key"
value: '"{{ applications[application_id].credentials.hcaptcha.site_key }}"'
- key: "security.captcha_secret"
value: '"{{ applications[application_id].credentials.hcaptcha.secret }}"'
value: '"{{ applications[application_id].credentials.hcaptcha.secret }}"'
# SMTP servers
- key: "smtp"
value: >-
{{ [
{
"host": system_email.host,
"port": system_email.port,
"enabled": system_email.smtp,
"username": system_email.username,
"password": system_email.password,
"tls_type": (
system_email.tls
| ternary("TLS",
system_email.start_tls
| ternary("STARTTLS","NONE")
)
),
"email_headers": [],
"hello_hostname": "",
"max_conns": 10,
"idle_timeout": "15s",
"wait_timeout": "5s",
"auth_protocol": "login",
"max_msg_retries": 2,
"tls_skip_verify": false
}
] | to_json }}
when: system_email.smtp | bool
- key: "app.lang"
value: '"{{ locale }}"'
# - key: "messengers"
# value: '[]'
- key: "app.logo_url"
value: '"{{ service_provider.platform.logo }}"'
- key: "app.site_name"
value: '"Mailing list"'
# - key: "bounce.enabled"
# value: 'false'
#
# - key: "upload.max_file_size"
# value: '5000'
#
# - key: "upload.s3.aws_secret_access_key"
# value: '""'
#
# - key: "app.batch_size"
# value: '1000'
- key: "app.from_email"
value: '"{{ service_provider.company.titel }} Newsletter <{{ system_email.from }}>"'
- key: "bounce.actions"
value: >-
{"hard": {"count": 1, "action": "blocklist"}, "soft": {"count": 2, "action": "none"}, "complaint": {"count": 1, "action": "blocklist"}}
- key: "app.concurrency"
value: '10'
- key: "app.favicon_url"
value: '"{{ service_provider.platform.favicon }}"'
# - key: "bounce.postmark"
# value: '{"enabled": false, "password": "", "username": ""}'
#
# - key: "upload.provider"
# value: '"filesystem"'
# - key: "app.message_rate"
# value: '10'
#
# - key: "bounce.mailboxes"
# value: >-
# [{"host": "pop.yoursite.com", "port": 995, "type": "pop", "uuid": "471fd0e9-8c33-4e4a-9183-c4679699faca", "enabled": false, "password": "password", "username": "username", "return_path": "bounce@listmonk.yoursite.com", "tls_enabled": true, "auth_protocol": "userpass", "scan_interval": "15m", "tls_skip_verify": false}]
# - key: "upload.s3.url"
# value: '"https://ap-south-1.s3.amazonaws.com"'
#
# - key: "upload.s3.bucket"
# value: '""'
#
# - key: "upload.s3.expiry"
# value: '"167h"'
- key: "app.check_updates"
value: 'true'
# - key: "upload.extensions"
# value: '["jpg", "jpeg", "png", "gif", "svg", "*"]'
#
# - key: "bounce.ses_enabled"
# value: 'false'
#
# - key: "privacy.allow_wipe"
# value: 'true'
#
# - key: "privacy.exportable"
# value: '["profile", "subscriptions", "campaign_views", "link_clicks"]'
#
# - key: "app.max_send_errors"
# value: '1000'
#
# - key: "bounce.forwardemail"
# value: '{"key": "", "enabled": false}'
#
# - key: "bounce.sendgrid_key"
# value: '""'
#
# - key: "privacy.allow_export"
# value: 'true'
#
# - key: "upload.s3.public_url"
# value: '""'
#
# - key: "upload.s3.bucket_path"
# value: '"/"'
#
# - key: "upload.s3.bucket_type"
# value: '"public"'
#
# - key: "app.cache_slow_queries"
# value: 'false'
#
# - key: "bounce.sendgrid_enabled"
# value: 'false'
#
# - key: "bounce.webhooks_enabled"
# value: 'false'
#
# - key: "privacy.domain_blocklist"
# value: '[]'
#
# - key: "privacy.allow_blocklist"
# value: 'true'
#
# - key: "privacy.record_optin_ip"
# value: 'false'
#
# - key: "app.enable_public_archive"
# value: 'true'
#
# - key: "privacy.allow_preferences"
# value: 'true'
#
# - key: "app.message_sliding_window"
# value: 'false'
#
# - key: "app.message_sliding_window_rate"
# value: '10000'
#
# - key: "app.enable_public_subscription_page"
# value: 'true'
#
# - key: "app.message_sliding_window_duration"
# value: '"1h"'
- key: "app.enable_public_archive_rss_content"
value: 'true'

View File

@ -0,0 +1,2 @@
# Todo
- Implement create-mailu-user-and-token.yml for no-reply and bounce

View File

@ -0,0 +1,69 @@
---
# tasks/create-mailu-user-and-token.yml
#
# Ensures a Mailu user exists and idempotently creates an API token for them.
#
# Required variables:
# mailu_compose_dir: Path to your docker-compose.yml directory
# mailu_user: Local part of the user (e.g., "alice")
# mailu_domain: Domain for the user (e.g., "example.com")
# mailu_password: Password for the new user
# mailu_api_base_url: Base URL of the Mailu API (e.g., "https://mail.example.com/api/v1")
# mailu_global_api_token: Global API token (from API_TOKEN environment variable)
#
# Optional variable:
# mailu_user_token: Pre-existing API token for the user (if already created)
- name: "Ensure Mailu user {{ mailu_user }}@{{ mailu_domain }} exists"
command: >
docker compose exec admin flask mailu user {{ mailu_user }} {{ mailu_domain }} '{{ mailu_password }}'
args:
chdir: "{{ mailu_compose_dir }}"
register: mailu_user_creation
failed_when: false
changed_when: mailu_user_creation.rc == 0 and 'User added' in mailu_user_creation.stdout
- name: "Fetch existing API tokens"
uri:
url: "{{ mailu_api_base_url }}/tokens"
method: GET
headers:
Authorization: "Bearer {{ mailu_global_api_token }}"
return_content: yes
register: mailu_tokens_response
failed_when: mailu_tokens_response.status not in [200]
- name: "Extract existing token info for {{ mailu_user }}"
set_fact:
mailu_user_existing_token: >
{{ mailu_tokens_response.json
| selectattr('comment', 'equalto', mailu_user)
| list
| first }}
- name: "Create API token for {{ mailu_user }} if none exists"
uri:
url: "{{ mailu_api_base_url }}/tokens"
method: POST
headers:
Authorization: "Bearer {{ mailu_global_api_token }}"
Content-Type: "application/json"
body_format: json
body:
comment: "{{ mailu_user }}"
ip: "0.0.0.0/0"
status_code: 201
register: mailu_token_creation
when: mailu_user_existing_token is not defined
- name: "Set mailu_user_token fact"
set_fact:
mailu_user_token: >
{{ (mailu_token_creation is defined)
? mailu_token_creation.json.secret
: (mailu_user_existing_token.secret | default('')) }}
# Note:
# - GET /tokens returns only metadata (id, comment, ip, created), not the secret itself.
# - The secret is returned only by the POST request and must be captured when created.
# - Store mailu_user_token securely (e.g., in Ansible Vault) for future use.

View File

@ -1,11 +1,11 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://{{domains.matrix_synapse}}",
"base_url": "{{ web_protocol }}://{{domains.matrix_synapse}}",
"server_name": "{{domains.matrix_synapse}}"
},
"m.identity_server": {
"base_url": "https://{{primary_domain}}"
"base_url": "{{ web_protocol }}://{{primary_domain}}"
}
},
"brand": "Element",

View File

@ -24,8 +24,8 @@ report_stats: true
macaroon_secret_key: "{{matrix_macaroon_secret_key}}"
form_secret: "{{matrix_form_secret}}"
signing_key_path: "/data/{{domains.matrix_synapse}}.signing.key"
web_client_location: "https://{{domains.matrix_element}}"
public_baseurl: "https://{{domains.matrix_synapse}}"
web_client_location: "{{ web_protocol }}://{{domains.matrix_element}}"
public_baseurl: "{{ web_protocol }}://{{domains.matrix_synapse}}"
trusted_key_servers:
- server_name: "matrix.org"
admin_contact: 'mailto:{{users.administrator.email}}'

View File

@ -32,7 +32,7 @@ NEXTCLOUD_ADMIN_PASSWORD= "{{applications[application_id].users.administra
NEXTCLOUD_TRUSTED_DOMAINS= "{{domains[application_id]}}"
# Whitelist local docker gateway in Nextcloud to prevent brute-force throtteling
TRUSTED_PROXIES= "192.168.102.65"
OVERWRITECLIURL= "https://{{domains[application_id]}}"
OVERWRITECLIURL= "{{ web_protocol }}://{{domains[application_id]}}"
OVERWRITEPROTOCOL= "https"
# Redis Configuration

View File

@ -18,4 +18,4 @@ nextcloud_system_config:
value: "{{domains[application_id]}}"
- parameter: "overwrite.cli.url"
value: "https://{{domains[application_id]}}"
value: "{{ web_protocol }}://{{domains[application_id]}}"

View File

@ -9,7 +9,7 @@ whitelist_domains = [".{{primary_domain}}"]
# keycloak provider
client_secret = "{{oidc.client.secret}}"
client_id = "{{oidc.client.id}}"
redirect_url = "https://{{domain}}/oauth2/callback"
redirect_url = "{{ web_protocol }}://{{domain}}/oauth2/callback"
oidc_issuer_url = "{{oidc.client.issuer_url}}"
provider = "oidc"
provider_display_name = "Keycloak"

View File

@ -26,7 +26,7 @@ accounts:
description: Follow {{ 'our' if service_provider.type == 'legal' else 'my' }} updates on Mastodon.
icon:
class: fa-brands fa-mastodon
url: "https://{{ service_provider.contact.mastodon.split('@')[2] }}/@{{ service_provider.contact.mastodon.split('@')[1] }}"
url: "{{ web_protocol }}://{{ service_provider.contact.mastodon.split('@')[2] }}/@{{ service_provider.contact.mastodon.split('@')[1] }}"
identifier: "{{service_provider.contact.mastodon}}"
iframe: {{ applications | get_features_iframe('mastodon') }}
@ -51,7 +51,7 @@ accounts:
icon:
class: fa-solid fa-camera
identifier: "{{service_provider.contact.pixelfed}}"
url: "https://{{ service_provider.contact.pixelfed.split('@')[2] }}/@{{ service_provider.contact.pixelfed.split('@')[1] }}"
url: "{{ web_protocol }}://{{ service_provider.contact.pixelfed.split('@')[2] }}/@{{ service_provider.contact.pixelfed.split('@')[1] }}"
iframe: {{ applications | get_features_iframe('pixelfed') }}
{% endif %}
@ -63,7 +63,7 @@ accounts:
icon:
class: fa-solid fa-video
identifier: "{{service_provider.contact.peertube}}"
url: "https://{{ service_provider.contact.peertube.split('@')[2] }}/@{{ service_provider.contact.peertube.split('@')[1] }}"
url: "{{ web_protocol }}://{{ service_provider.contact.peertube.split('@')[2] }}/@{{ service_provider.contact.peertube.split('@')[1] }}"
iframe: {{ applications | get_features_iframe('peertube') }}
{% endif %}
@ -75,7 +75,7 @@ accounts:
icon:
class: fa-solid fa-blog
identifier: "{{service_provider.contact.wordpress}}"
url: "https://{{ service_provider.contact.wordpress.split('@')[2] }}/@{{ service_provider.contact.wordpress.split('@')[1] }}"
url: "{{ web_protocol }}://{{ service_provider.contact.wordpress.split('@')[2] }}/@{{ service_provider.contact.wordpress.split('@')[1] }}"
iframe: {{ applications | get_features_iframe('wordpress') }}
{% endif %}
@ -97,7 +97,7 @@ accounts:
icon:
class: fas fa-network-wired
identifier: "{{service_provider.contact.friendica}}"
url: "https://{{ service_provider.contact.friendica.split('@')[2] }}/@{{ service_provider.contact.friendica.split('@')[1] }}"
url: "{{ web_protocol }}://{{ service_provider.contact.friendica.split('@')[2] }}/@{{ service_provider.contact.friendica.split('@')[1] }}"
iframe: {{ applications | get_features_iframe('friendica') }}
{% endif %}

View File

@ -2,7 +2,7 @@
command: >
docker-compose exec -T -u www-data application
wp core install
--url="https://{{ domains[application_id][0] }}"
--url="{{ web_protocol }}://{{ domains[application_id][0] }}"
--title="{{ applications[application_id].title }}"
--admin_user="{{ applications[application_id].credentials.administrator.username }}"
--admin_password="{{ applications[application_id].credentials.administrator.password }}"

View File

@ -11,7 +11,7 @@ discourse_settings:
- name: discourse_connect
key: url
value: "https://{{ domains.discourse }}"
value: "{{ web_protocol }}://{{ domains.discourse }}"
- name: discourse_connect
key: api-key
value: "{{ vault_discourse_api_key }}"

View File

@ -2,6 +2,6 @@ YOURLS_DB_HOST: "{{database_host}}"
YOURLS_DB_USER: "{{database_username}}"
YOURLS_DB_PASS: "{{database_password}}"
YOURLS_DB_NAME: "{{database_name}}"
YOURLS_SITE: "https://{{domains[application_id]}}"
YOURLS_SITE: "{{ web_protocol }}://{{domains[application_id]}}"
YOURLS_USER: "{{applications.yourls.users.administrator.username}}"
YOURLS_PASS: "{{yourls_administrator_password}}"

View File

@ -20,7 +20,7 @@ for filename in os.listdir(config_path):
parts = domain.split('.')
# Prepare the URL and expected status codes
url = f"https://{domain}"
url = f"{{ web_protocol }}://{domain}"
# Default: Expect status code 200 or 302 for a domain
expected_statuses = [200,302]

View File

@ -28,7 +28,7 @@
- name: Add site to Matomo and get ID if not exists
uri:
url: "https://{{ domains.matomo }}/index.php"
url: "{{ web_protocol }}://{{ domains.matomo }}/index.php"
method: POST
body: "module=API&method=SitesManager.addSite&siteName={{ base_domain }}&urls=https://{{ base_domain }}&token_auth={{ applications.matomo.credentials.auth_token }}&format=json"
body_format: form-urlencoded

View File

@ -1,2 +1,2 @@
base_domain: "{{ domain | regex_replace('^(?:.*\\.)?(.+\\..+)$', '\\1') }}"
verification_url: "https://{{domains.matomo}}/index.php?module=API&method=SitesManager.getSitesIdFromSiteUrl&url=https://{{base_domain}}&format=json&token_auth={{applications.matomo.credentials.auth_token}}"
verification_url: "{{ web_protocol }}://{{domains.matomo}}/index.php?module=API&method=SitesManager.getSitesIdFromSiteUrl&url=https://{{base_domain}}&format=json&token_auth={{applications.matomo.credentials.auth_token}}"

View File

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

3
templates/vars/Todo.md Normal file
View File

@ -0,0 +1,3 @@
# Todo
- Implement Keycloak iframe so that mailu can be used in iframe
- Activate mailu in iframe

View File

@ -34,7 +34,7 @@ defaults_applications:
## Assets Server
assets_server:
source_directory: "{{ playbook_dir }}/assets" # Directory from which the assets will be copied
url: "https://{{domains.file_server}}/assets" # Public address of the assets directory
url: "{{ web_protocol }}://{{domains.file_server}}/assets" # Public address of the assets directory
## Attendize
attendize:
version: "latest"
@ -76,7 +76,7 @@ defaults_applications:
# fsesl_password: # Needs to be defined in inventory file
# turn_secret: # Needs to be defined in inventory file
urls:
api: "https://{{domains.bigbluebutton}}/bigbluebutton/" # API Address used by Nextcloud Integration
api: "{{ web_protocol }}://{{domains.bigbluebutton}}/bigbluebutton/" # API Address used by Nextcloud Integration
## Bluesky
bluesky:
@ -305,11 +305,12 @@ defaults_applications:
{% endraw %}{{ features.render_features({
'matomo': true,
'css': true,
'iframe': true,
'iframe': false,
'oidc': true,
'database': false
}) }}{% raw %}
# Deactivate central database for mailu, I don't know why the database deactivation is necessary
# Deactivated mailu iframe loading until keycloak supports it
## MariaDB
mariadb:
@ -633,7 +634,7 @@ defaults_applications:
oauth2_proxy:
configuration_file: "oauth2-proxy-keycloak.cfg" # Needs to be set true in the roles which use it
version: "latest" # Docker Image version
redirect_url: "https://{{domains.keycloak}}/auth/realms/{{primary_domain}}/protocol/openid-connect/auth" # The redirect URL for the OAuth2 flow. It should match the redirect URL configured in Keycloak.
redirect_url: "{{ web_protocol }}://{{domains.keycloak}}/auth/realms/{{primary_domain}}/protocol/openid-connect/auth" # The redirect URL for the OAuth2 flow. It should match the redirect URL configured in Keycloak.
allowed_roles: admin # Restrict it default to admin role. Use the vars/main.yml to open the specific role for other groups
{% endraw %}{{ features.render_features({
'matomo': true,