Compare commits

..

3 Commits

33 changed files with 400 additions and 84 deletions

View File

@ -1,29 +1,57 @@
# General # General
pause_duration: "120" # Database delay to wait for the central database before continue tasks 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 locale: "en" # Some applications are case sensitive
# Deployment mode # Deployment mode
deployment_mode: "single" # Use single, if you deploy on one server. Use cluster if you setup in cluster 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 ## Domain
primary_domain_tld: "localhost" # Top Level Domain of the server primary_domain_tld: "localhost" # Top Level Domain of the server
primary_domain_sld: "cymais" # Second Level Domain of the server primary_domain_sld: "cymais" # Second Level Domain of the server
primary_domain: "{{primary_domain_sld}}.{{primary_domain_tld}}" # Primary Domain of the server primary_domain: "{{primary_domain_sld}}.{{primary_domain_tld}}" # Primary Domain of the server
# Helper Variables # Helper Variables
# Helper Variables for administrator
_users_administrator_username: "{{ users.administrator.username | default('administrator') }}" _users_administrator_username: "{{ users.administrator.username | default('administrator') }}"
_users_administrator_email: "{{ users.administrator.email | default(_users_administrator_username ~ '@' ~ primary_domain) }}" _users_administrator_email: "{{ users.administrator.email | default(_users_administrator_username ~ '@' ~ primary_domain) }}"
# Helper Variables for bouncer
_users_bouncer_username: "{{ users.bouncer.username | default('bouncer') }}"
_users_bouncer_email: "{{ users.bouncer.email | default(_users_bouncer_username ~ '@' ~ primary_domain) }}"
# Helper Variables for no-reply
_users_no_reply_username: "{{ users.no-reply.username | default('no-reply') }}"
_users_no_reply_email: "{{ users.no-reply.email | default(_users_no_reply_username ~ '@' ~ primary_domain) }}"
# Administrator # Administrator
default_users: default_users:
administrator: administrator:
username: "{{_users_administrator_username}}" # Username of the administrator username: "{{_users_administrator_username}}" # Username of the administrator
email: "{{_users_administrator_email}}" # Email of the administrator email: "{{_users_administrator_email}}" # Email of the administrator
# initial_password: Null # Example initialisation password needs to be set in inventory file password: "{{ansible_become_password}}" # Example initialisation password needs to be set in inventory file
uid: 1001 # Posix User ID uid: 1001 # Posix User ID
gid: 1001 # Posix Group ID gid: 1001 # Posix Group ID
is_admin: true # Define as admin user
bouncer:
username: "{{ _users_bouncer_username }}" # Bounce-handler account username
email: "{{ _users_bouncer_email }}" # Email address for handling bounces
password: "{{ansible_become_password}}" # Example initialisation password needs to be set in inventory file
uid: 1002 # Posix User ID for bouncer
gid: 1002 # Posix Group ID for bouncer
no-reply:
username: "{{ _users_no_reply_username }}" # No-reply account username
email: "{{ _users_no_reply_email }}" # Email address for outgoing no-reply mails
password: "{{ansible_become_password}}" # Example initialisation password needs to be set in inventory file
uid: 1003 # Posix User ID for no-reply
gid: 1003 # Posix Group ID for no-reply
# Test Email # Test Email
test_email: "test@{{primary_domain}}" test_email: "test@{{primary_domain}}"

View File

@ -9,7 +9,7 @@
## Helper Variables: ## Helper Variables:
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}" _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: defaults_oidc:
client: client:

View File

@ -28,4 +28,4 @@ defaults_service_provider:
legal: legal:
editorial_responsible: "Johannes Gutenberg" editorial_responsible: "Johannes Gutenberg"
source_code: "https://github.com/kevinveenbirkenbach/cymais" 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 dockerfile: Dockerfile
# It doesn't compile yet with this parameters. @todo Fix it # It doesn't compile yet with this parameters. @todo Fix it
args: args:
REACT_APP_PDS_URL: "https://{{domains.bluesky_api}}" # URL des PDS REACT_APP_PDS_URL: "{{ web_protocol }}://{{domains.bluesky_api}}" # URL des PDS
REACT_APP_API_URL: "https://{{domains.bluesky_api}}" # 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_NAME: "{{primary_domain | upper}} - Bluesky"
REACT_APP_SITE_DESCRIPTION: "Decentral Social " REACT_APP_SITE_DESCRIPTION: "Decentral Social "
ports: ports:

View File

@ -124,7 +124,7 @@ run:
- exec: rails r "SiteSetting.username_change_period = 0" # Deactivate changing of username - exec: rails r "SiteSetting.username_change_period = 0" # Deactivate changing of username
# Activate Administrator User # Activate Administrator User
#- exec: printf '{{users.administrator.email}}\n{{users.administrator.initial_password}}\n{{users.administrator.initial_password}}\nY\n' | rake admin:create #- exec: printf '{{users.administrator.email}}\n{{users.administrator.password}}\n{{users.administrator.password}}\nY\n' | rake admin:create
#- exec: rails r "User.find_by_email('{{users.administrator.email}}').update(username: '{{users.administrator.username}}')" #- exec: rails r "User.find_by_email('{{users.administrator.email}}').update(username: '{{users.administrator.username}}')"
# The following code is just an inspiration, how to connect with the oidc account. as long as this is not set the admini account needs to be manually connected with oidc # The following code is just an inspiration, how to connect with the oidc account. as long as this is not set the admini account needs to be manually connected with oidc

View File

@ -13,7 +13,7 @@ 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="{{run_mode}}"
ROOT_URL="https://{{domains[application_id]}}/" ROOT_URL="{{ web_protocol }}://{{domains[application_id]}}/"
# Mail Configuration # Mail Configuration
# @see https://docs.gitea.com/next/installation/install-with-docker#managing-deployments-with-environment-variables # @see https://docs.gitea.com/next/installation/install-with-docker#managing-deployments-with-environment-variables

View File

@ -19,4 +19,4 @@ KC_DB_PASSWORD= {{database_password}}
# If the initial administrator already exists and the environment variables are still present at startup, an error message stating the failed creation of the initial administrator is shown in the logs. Keycloak ignores the values and starts up correctly. # If the initial administrator already exists and the environment variables are still present at startup, an error message stating the failed creation of the initial administrator is shown in the logs. Keycloak ignores the values and starts up correctly.
KC_BOOTSTRAP_ADMIN_USERNAME= {{users.administrator.username}} KC_BOOTSTRAP_ADMIN_USERNAME= {{users.administrator.username}}
KC_BOOTSTRAP_ADMIN_PASSWORD= {{users.administrator.initial_password}} KC_BOOTSTRAP_ADMIN_PASSWORD= {{users.administrator.password}}

View File

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

View File

@ -1,3 +1,8 @@
{##
# 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) # Container for Application Roles (if not already created)
####################################################################### #######################################################################
@ -6,28 +11,39 @@ objectClass: organizationalUnit
ou: roles ou: roles
description: Container for application access profiles description: Container for application access profiles
{% for user in users %}
####################################################################### #######################################################################
# Create Admin User # Create User {{ user.username }}
####################################################################### #######################################################################
dn: {{ldap.attributes.user_id}}={{users.administrator.username}},{{ldap.dn.users}} dn: {{ ldap.attributes.user_id }}={{ user.username }},{{ ldap.dn.users }}
objectClass: top objectClass: top
objectClass: inetOrgPerson objectClass: inetOrgPerson
objectClass: posixAccount objectClass: posixAccount
{{ldap.attributes.user_id}}: {{users.administrator.username}} {{ ldap.attributes.user_id }}: {{ user.username }}
sn: Administrator sn: {{ user.username }}
cn: Administrator cn: {{ user.username }}
userPassword: {SSHA}CHANGE_THIS_PASSWORD userPassword: {{ user.password }}
loginShell: /bin/bash loginShell: /bin/bash
homeDirectory: /home/admin homeDirectory: /home/{{ user.username }}
uidNumber: {{users.administrator.uid}} uidNumber: {{ user.uid }}
gidNumber: {{users.administrator.gid}} gidNumber: {{ user.gid }}
####################################################################### #######################################################################
# Add Admin User to All Application Role Groups # Assign {{ user.username }} to application user roles
####################################################################### #######################################################################
{# Loop over each application defined in defaults_applications #}
{% for app, config in defaults_applications.items() %} {% for app, config in defaults_applications.items() %}
dn: cn={{ app }}-user,{{ ldap.dn.application_roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ user.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 }} dn: cn={{ app }}-administrator,{{ ldap.dn.application_roles }}
changetype: modify changetype: modify
add: roleOccupant add: roleOccupant
@ -39,3 +55,4 @@ add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }} roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }}
{% endfor %} {% endfor %}

View File

@ -19,4 +19,5 @@ galaxy_info:
documentation: "https://s.veen.world/cymais" documentation: "https://s.veen.world/cymais"
logo: logo:
class: "fa-solid fa-list" class: "fa-solid fa-list"
dependencies: [] dependencies:
- docker-mailu

View File

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

View File

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

View File

@ -4,7 +4,10 @@ database_type: "postgres"
listmonk_settings: listmonk_settings:
- key: "app.root_url" - 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) # OIDC integration (conditional)
- key: "security.oidc" - key: "security.oidc"
@ -17,9 +20,178 @@ listmonk_settings:
} | to_json }} } | to_json }}
when: applications[application_id].features.oidc | bool when: applications[application_id].features.oidc | bool
# hCaptcha toggles and credentials\ n - key: "security.enable_captcha" # hCaptcha toggles and credentials
value: "true" - key: "security.enable_captcha"
value: 'true'
- key: "security.captcha_key" - key: "security.captcha_key"
value: '"{{ applications[application_id].credentials.hcaptcha.site_key }}"' value: '"{{ applications[application_id].credentials.hcaptcha.site_key }}"'
- key: "security.captcha_secret" - 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: '"{{ service_provider.company.titel }} 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,72 @@
---
# tasks/create-mailu-user-and-token.yml
#
# Ensures a Mailu user exists and idempotently creates an API token for them,
# storing tokens in a dictionary for targeted access.
#
# 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_tokens: Dictionary of existing tokens, e.g. { "alice": "secret" }
- name: "Ensure Mailu user {{ mailu_user }}@{{ mailu_domain }} exists"
command: >
docker compose exec admin flask mailu {{ mailu_action }} {{ 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: "{{ mailu_token_ip }}"
status_code: 201
register: mailu_token_creation
when: mailu_user_existing_token is not defined
- name: "Set mailu_user_tokens dictionary"
set_fact:
mailu_user_tokens: >
{{ (mailu_user_tokens | default({}))
| combine({ mailu_user: ((mailu_token_creation is defined)
| ternary(mailu_token_creation.json.secret,
mailu_user_existing_token.secret)) }) }}
# 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.
# - Tokens are stored in the mailu_user_tokens dictionary for targeted access.
# - Persist mailu_user_tokens securely (e.g., in Ansible Vault) for future use.

View File

@ -1,29 +1,48 @@
--- ---
- name: "include docker-central-database" - name: "Include docker-central-database"
include_role: include_role:
name: docker-central-database name: docker-central-database
when: run_once_docker_mailu is not defined
- name: "include role nginx-domain-setup for {{application_id}}" - name: "Include role nginx-domain-setup for {{ application_id }}"
include_role: include_role:
name: nginx-domain-setup name: nginx-domain-setup
vars: vars:
domain: "{{ domains[application_id] }}" domain: "{{ domains[application_id] }}"
http_port: "{{ ports.localhost.http[application_id] }}" http_port: "{{ ports.localhost.http[application_id] }}"
vars:
nginx_docker_reverse_proxy_extra_configuration: "client_max_body_size 31M;" nginx_docker_reverse_proxy_extra_configuration: "client_max_body_size 31M;"
when: run_once_docker_mailu is not defined
- name: "Include the nginx-docker-cert-deploy role" - name: "Include the nginx-docker-cert-deploy role"
include_role: include_role:
name: nginx-docker-cert-deploy name: nginx-docker-cert-deploy
when: run_once_docker_mailu is not defined
- 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
when: run_once_docker_mailu is not defined
- name: flush docker service - name: Flush docker service handlers
meta: flush_handlers meta: flush_handlers
when: run_once_docker_mailu is not defined
- name: create administrator account - name: "Create Mailu accounts and API tokens"
command: include_tasks: create-mailu-user-and-token.yml
cmd: "docker compose -p mailu exec admin flask mailu admin {{users.administrator.username}} {{primary_domain}} {{applications.mailu.initial_administrator_password}}" vars:
chdir: "{{docker_compose.directories.instance}}" mailu_compose_dir: "{{ docker_compose.directories.instance }}"
ignore_errors: true mailu_domain: "{{ domain }}"
mailu_api_base_url: "{{ web_protocol }}://{{ domain }}/api/v1"
mailu_global_api_token: "{{ applications.mailu.credentials.api_token }}"
mailu_action: "{{ item.value.is_admin | default(false) | ternary('admin','user') }}"
mailu_user: "{{ item.key }}"
mailu_password: "{{ item.value.password }}"
mailu_token_ip: "{{ item.value.ip | default('') }}"
loop: "{{ users | dict2items }}"
loop_control:
loop_var: item
when: run_once_docker_mailu is not defined
- name: Run the docker_mailu tasks once
set_fact:
run_once_docker_mailu: true
when: run_once_docker_mailu is not defined

View File

@ -182,7 +182,7 @@ OIDC_CHANGE_PASSWORD_REDIRECT_ENABLED=True
# Redirect URL for password change. Defaults to provider issuer url appended by /.well-known/change-password # Redirect URL for password change. Defaults to provider issuer url appended by /.well-known/change-password
OIDC_CHANGE_PASSWORD_REDIRECT_URL={{oidc.client.change_credentials}} OIDC_CHANGE_PASSWORD_REDIRECT_URL={{oidc.client.change_credentials}}
{% if applications[application_id].features.oidc | bool %} {% if applications[application_id].oidc.email_by_username | bool %}
# The OIDC claim used as the username. If the selected claim contains an email address, it will be used as is. If it is not an email (e.g., sub), the email address will be constructed as <OIDC_USERNAME_CLAIM>@<OIDC_USER_DOMAIN>. Defaults to email. # The OIDC claim used as the username. If the selected claim contains an email address, it will be used as is. If it is not an email (e.g., sub), the email address will be constructed as <OIDC_USERNAME_CLAIM>@<OIDC_USER_DOMAIN>. Defaults to email.
OIDC_USERNAME_CLAIM={{oidc.attributes.username}} OIDC_USERNAME_CLAIM={{oidc.attributes.username}}

View File

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

View File

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

View File

@ -25,14 +25,14 @@ MAIL_DOMAIN= "{{system_email.domain}}"
# Initial Admin Data # Initial Admin Data
NEXTCLOUD_ADMIN_USER= "{{applications[application_id].users.administrator.username}}" NEXTCLOUD_ADMIN_USER= "{{applications[application_id].users.administrator.username}}"
NEXTCLOUD_ADMIN_PASSWORD= "{{applications[application_id].users.administrator.initial_password}}" NEXTCLOUD_ADMIN_PASSWORD= "{{applications[application_id].users.administrator.password}}"
# Security # Security
NEXTCLOUD_TRUSTED_DOMAINS= "{{domains[application_id]}}" NEXTCLOUD_TRUSTED_DOMAINS= "{{domains[application_id]}}"
# Whitelist local docker gateway in Nextcloud to prevent brute-force throtteling # Whitelist local docker gateway in Nextcloud to prevent brute-force throtteling
TRUSTED_PROXIES= "192.168.102.65" TRUSTED_PROXIES= "192.168.102.65"
OVERWRITECLIURL= "https://{{domains[application_id]}}" OVERWRITECLIURL= "{{ web_protocol }}://{{domains[application_id]}}"
OVERWRITEPROTOCOL= "https" OVERWRITEPROTOCOL= "https"
# Redis Configuration # Redis Configuration

View File

@ -18,4 +18,4 @@ nextcloud_system_config:
value: "{{domains[application_id]}}" value: "{{domains[application_id]}}"
- parameter: "overwrite.cli.url" - 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 # keycloak provider
client_secret = "{{oidc.client.secret}}" client_secret = "{{oidc.client.secret}}"
client_id = "{{oidc.client.id}}" 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}}" oidc_issuer_url = "{{oidc.client.issuer_url}}"
provider = "oidc" provider = "oidc"
provider_display_name = "Keycloak" provider_display_name = "Keycloak"

View File

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

View File

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

View File

@ -11,7 +11,7 @@ discourse_settings:
- name: discourse_connect - name: discourse_connect
key: url key: url
value: "https://{{ domains.discourse }}" value: "{{ web_protocol }}://{{ domains.discourse }}"
- name: discourse_connect - name: discourse_connect
key: api-key key: api-key
value: "{{ vault_discourse_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_USER: "{{database_username}}"
YOURLS_DB_PASS: "{{database_password}}" YOURLS_DB_PASS: "{{database_password}}"
YOURLS_DB_NAME: "{{database_name}}" 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_USER: "{{applications.yourls.users.administrator.username}}"
YOURLS_PASS: "{{yourls_administrator_password}}" YOURLS_PASS: "{{yourls_administrator_password}}"

View File

@ -20,7 +20,7 @@ for filename in os.listdir(config_path):
parts = domain.split('.') parts = domain.split('.')
# Prepare the URL and expected status codes # 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 # Default: Expect status code 200 or 302 for a domain
expected_statuses = [200,302] expected_statuses = [200,302]

View File

@ -28,7 +28,7 @@
- name: Add site to Matomo and get ID if not exists - name: Add site to Matomo and get ID if not exists
uri: uri:
url: "https://{{ domains.matomo }}/index.php" url: "{{ web_protocol }}://{{ domains.matomo }}/index.php"
method: POST 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: "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 body_format: form-urlencoded

View File

@ -1,2 +1,2 @@
base_domain: "{{ domain | regex_replace('^(?:.*\\.)?(.+\\..+)$', '\\1') }}" 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> <!DOCTYPE html>
<html lang="en"> <html lang="{{ locale }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -2,7 +2,7 @@
user: user:
name: administrator name: administrator
update_password: on_create update_password: on_create
password: "{{ users.administrator.initial_password | password_hash('sha512') }}" password: "{{ users.administrator.password | password_hash('sha512') }}"
create_home: yes create_home: yes
generate_ssh_key: yes generate_ssh_key: yes
ssh_key_type: rsa ssh_key_type: rsa

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
assets_server: assets_server:
source_directory: "{{ playbook_dir }}/assets" # Directory from which the assets will be copied 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
attendize: attendize:
version: "latest" version: "latest"
@ -76,7 +76,7 @@ defaults_applications:
# fsesl_password: # Needs to be defined in inventory file # fsesl_password: # Needs to be defined in inventory file
# turn_secret: # Needs to be defined in inventory file # turn_secret: # Needs to be defined in inventory file
urls: 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
bluesky: bluesky:
@ -231,7 +231,7 @@ defaults_applications:
# LDAP Account Manager # LDAP Account Manager
lam: lam:
version: "latest" version: "latest"
# administrator_password: "{{users.administrator.initial_password}}" # CHANGE for security reasons # administrator_password: "{{users.administrator.password}}" # CHANGE for security reasons
oauth2_proxy: oauth2_proxy:
application: application # Needs to be the same as webinterface application: application # Needs to be the same as webinterface
port: 80 # application port port: 80 # application port
@ -305,11 +305,12 @@ defaults_applications:
{% endraw %}{{ features.render_features({ {% endraw %}{{ features.render_features({
'matomo': true, 'matomo': true,
'css': true, 'css': true,
'iframe': true, 'iframe': false,
'oidc': true, 'oidc': true,
'database': false 'database': false
}) }}{% raw %} }) }}{% raw %}
# Deactivate central database for mailu, I don't know why the database deactivation is necessary # 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
mariadb: mariadb:
@ -427,7 +428,7 @@ defaults_applications:
users: users:
administrator: administrator:
username: "{{users.administrator.username}}" username: "{{users.administrator.username}}"
initial_password: "{{users.administrator.initial_password}}" # Keep in mind to change the password fast after creation and activate 2FA password: "{{users.administrator.password}}" # Keep in mind to change the password fast after creation and activate 2FA
default_quota: '1000000000' # Quota to assign if no quota is specified in the OIDC response (bytes) default_quota: '1000000000' # Quota to assign if no quota is specified in the OIDC response (bytes)
legacy_login_mask: legacy_login_mask:
enabled: False # If true, then legacy login mask is shown. Otherwise just SSO enabled: False # If true, then legacy login mask is shown. Otherwise just SSO
@ -633,7 +634,7 @@ defaults_applications:
oauth2_proxy: oauth2_proxy:
configuration_file: "oauth2-proxy-keycloak.cfg" # Needs to be set true in the roles which use it configuration_file: "oauth2-proxy-keycloak.cfg" # Needs to be set true in the roles which use it
version: "latest" # Docker Image version 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 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({ {% endraw %}{{ features.render_features({
'matomo': true, 'matomo': true,
@ -679,7 +680,7 @@ defaults_applications:
users: users:
administrator: administrator:
email: "{{ users.administrator.email }}" # Initial login email address email: "{{ users.administrator.email }}" # Initial login email address
password: "{{ users.administrator.initial_password }}" # Initial login password should be overridden in inventory for security password: "{{ users.administrator.password }}" # Initial login password should be overridden in inventory for security
oauth2_proxy: oauth2_proxy:
application: "application" application: "application"
port: "80" port: "80"