Huge role refactoring/cleanup. Other commits will propably follow. Because some bugs will exist. Still important for longrun and also for auto docs/help/slideshow generation

This commit is contained in:
2025-07-08 23:43:13 +02:00
parent 6b87a049d4
commit 563d5fd528
1242 changed files with 2301 additions and 1355 deletions

View File

@@ -0,0 +1,27 @@
# Administration Notes
## Check configuration
```bash
./launcher enter application
pry(main)> SiteSetting.all.each { |setting| puts "#{setting.name}: #{setting.value}" }
```
---
## Reinitialize Container
To reinitialize the container execute:
```bash
docker network connect discourse_default central-postgres && /opt/docker/discourse/services/discourse_repository/launcher rebuild discourse_application
```
### 🔍 Logging with `journalctl`
All build actions triggered by this role are logged to the system journal using `systemd-cat`. Output is simultaneously shown in the terminal and available via `journalctl`.
To view logs for a specific application:
```bash
journalctl -t rebuild-discourse -f
```

View File

@@ -0,0 +1,106 @@
## Discourse Debugging Guide for Docker Role
This document explains how to locate and use key log file paths on both the host and inside the container for a Docker-based Discourse installation deployed via the `web-app-discourse` role.
### 1. Host Paths
Discourse log files are stored in the Docker volume named `discourse_data`. On the host, you can find them at:
* **Rails Production Log**:
```bash
cat /var/lib/docker/volumes/discourse_data/_data/log/rails/production.log | grep -i mail
```
Filters for email-related entries:
* **Queued emails**: `Email::Sender: queued mail to user@example.com`
* **Errors**: e.g. `Net::SMTPAuthenticationError`, `SMTPConnectionError`
* **Sidekiq Log**:
```bash
cat /var/lib/docker/volumes/discourse_data/_data/log/sidekiq.log | grep -i mail
```
Shows asynchronous mail job executions, retries, and failures.
### 2. Inside the Container
To inspect logs within the container, enter it:
```bash
cd /var/discourse
./launcher enter app
```
Logs are mounted under `/var/log` inside the container:
* **Rails Production Log**:
```bash
tail -n 200 /var/log/rails/production.log | grep -i mail
```
* **Info**: `I, [timestamp] INFO -- : Email::Sender: queued mail to ...`
* **Error**: `E, [timestamp] ERROR -- : Net::SMTPSyntaxError ...`
* **Sidekiq Log**:
```bash
tail -n 200 /var/log/sidekiq.log | grep -i mail
```
* **Execution**: `Mail::MessageJob JID-...`
* **Retries/Exceptions** on delivery failure.
### 3. Live Streaming Logs
For real-time monitoring while reproducing an issue:
```bash
# On host:
tail -f \
/var/lib/docker/volumes/discourse_data/_data/log/rails/production.log \
/var/lib/docker/volumes/discourse_data/_data/log/sidekiq.log | grep -i mail
# Or inside container:
tail -f /var/log/rails/production.log /var/log/sidekiq.log | grep -i mail
```
### 4. Enabling Verbose Email Debugging
For detailed SMTP conversation logging:
```bash
# Inside container
rails c
> Discourse.debug_email = true
```
Send a test email:
```bash
rails c
> UserMailer.test_email("you@example.com").deliver_now
```
Then check logs for the full SMTP handshake details.
### 5. Flushing Redis Cache
After configuration changes, clear Redis to remove stale session or cached data:
```bash
# Inside container
rails r "Redis.new.flushall"
```
### 6. Sidekiq Web UI
In the Admin UI under **Plugins → Sidekiq**, monitor queues, retries, and failed jobs for additional context.
---
Use this guide to quickly locate and interpret Discourse logs on both host and container, enabling efficient debugging of email delivery and background job issues in a Docker deployment managed by the `web-app-discourse` role.

View File

@@ -0,0 +1,35 @@
# Discourse
## Description
Discourse is a popular open-source discussion platform designed to foster community engagement through modern, user-friendly features and robust moderation tools. It creates a dynamic space for discussions, offering seamless notifications and customizable interfaces to keep your community active and engaged.
## Overview
This role deploys Discourse using Docker, automating tasks such as container orchestration, service configuration, and routine administrative operations. It integrates key components like Redis and PostgreSQL, sets up domain routing with Nginx, and ensures streamlined updates for a reliable forum experience.
For detailed usage and configuration, please refer to the following files in this directory:
- [Administration.md](./Administration.md)
## Features
- **Modern Forum Experience:** Engage in interactive, real-time discussions with a responsive, mobile-friendly design.
- **Robust Moderation Tools:** Benefit from comprehensive tools for content management and community moderation.
- **Customizable Layouts & Themes:** Tailor your forums look and functionality to suit your communitys unique style.
- **Scalable Architecture:** Utilize a Docker-based deployment that adapts easily to increasing traffic and community size.
- **Extensive Plugin Support:** Enhance your forum with a wide range of plugins and integrations for additional functionality.
## Further Resources
- [Discourse Official Website](https://www.discourse.org/)
- [Discourse GitHub Repository](https://github.com/discourse/discourse_docker.git)
- [Discourse Meta Forum](https://meta.discourse.org/)
- [Discourse Documentation](https://meta.discourse.org/t/discourse-setup-guide/21966)
## Credits
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [veen.world](https://www.veen.world).
Part of the [CyMaIS Project](https://github.com/kevinveenbirkenbach/cymais)
Licensed under [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl).

View File

@@ -0,0 +1,2 @@
# Todo
- Finish LDAP implementation

View File

@@ -0,0 +1,24 @@
---
- name: "stop and remove discourse container if it exist"
docker_container:
name: "{{applications[application_id].container}}"
state: absent
register: container_action
failed_when: container_action.failed and 'No such container' not in container_action.msg
listen: recreate discourse
- name: "add central database temporary to {{application_id}}_default"
command: docker network connect {{applications[application_id].network}} {{ database_host }}
failed_when: >
result.rc != 0 and
'already exists in network' not in result.stderr
register: result
when: applications | is_feature_enabled('central_database', application_id)
listen: recreate discourse
- name: rebuild discourse
shell: ./launcher rebuild {{applications[application_id].container}}
args:
executable: /bin/bash
chdir: "{{docker_repository_directory }}"
listen: recreate discourse

View File

@@ -0,0 +1,24 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Discourse is a popular open-source discussion platform designed to foster community engagement with modern, user-friendly features and robust moderation tools."
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
galaxy_tags:
- discourse
- docker
- discussion
- forum
- open-source
repository: https://s.veen.world/cymais
issue_tracker_url: https://s.veen.world/cymaisissues
documentation: https://s.veen.world/cymais
logo:
class: "fa-solid fa-comments"
run_after:
- web-app-wordpress
dependencies: []

View File

View File

@@ -0,0 +1,100 @@
---
# Necessary for building: https://chat.openai.com/share/99d258cc-294b-4924-8eef-02fe419bb838
- name: install which
pacman:
name: which
state: present
when: run_once_docker_discourse is not defined
- name: "include service-rdbms-central"
include_role:
name: service-rdbms-central
when: run_once_docker_discourse is not defined
- name: "include role webserver-proxy-domain for {{application_id}}"
include_role:
name: webserver-proxy-domain
vars:
domain: "{{ domains | get_domain(application_id) }}"
http_port: "{{ ports.localhost.http[application_id] }}"
when: run_once_docker_discourse is not defined
- name: "cleanup central database from {{application_id}}_default network"
command:
cmd: "docker network disconnect {{applications[application_id].network}} {{ database_host }}"
ignore_errors: true
when:
- mode_reset | bool
- run_once_docker_discourse is not defined
- name: add docker-compose.yml
template:
src: docker-compose.yml.j2
dest: "{{docker_compose.directories.instance}}docker-compose.yml"
notify:
- docker compose up
when: run_once_docker_discourse is not defined
- name: flush, to recreate discourse docker compose
meta: flush_handlers
when: run_once_docker_discourse is not defined
- name: pull docker repository
git:
repo: "https://github.com/discourse/discourse_docker.git"
dest: "{{docker_repository_directory }}"
update: yes
notify: recreate discourse
become: true
ignore_errors: true
when: run_once_docker_discourse is not defined
- name: set chmod 700 for {{docker_repository_directory }}containers
ansible.builtin.file:
path: "{{docker_repository_directory }}/containers"
mode: '700'
state: directory
when: run_once_docker_discourse is not defined
- name: "copy configuration to {{discourse_application_yml_destination}}"
template:
src: discourse_application.yml.j2
dest: "{{discourse_application_yml_destination}}"
notify: recreate discourse
when: run_once_docker_discourse is not defined
- name: "destroy container discourse_application"
command:
cmd: "./launcher destroy discourse_application"
chdir: "{{docker_repository_directory }}"
ignore_errors: true
notify: recreate discourse
when:
- mode_reset | bool
- run_once_docker_discourse is not defined
- name: flush, to recreate discourse app
meta: flush_handlers
when: run_once_docker_discourse is not defined
- name: "add {{applications[application_id].container}} to network central_postgres"
command:
cmd: "docker network connect central_postgres {{applications[application_id].container}}"
ignore_errors: true
when:
- applications | is_feature_enabled('central_database',application_id)
- run_once_docker_discourse is not defined
- name: "remove central database from {{application_id}}_default"
command:
cmd: "docker network disconnect {{applications[application_id].network}} {{ database_host }}"
ignore_errors: true
when:
- applications | is_feature_enabled('central_database',application_id)
- run_once_docker_discourse is not defined
- name: run the docker_discourse tasks once
set_fact:
run_once_docker_discourse: true
when: run_once_docker_discourse is not defined

View File

@@ -0,0 +1,181 @@
templates:
{% if not applications | is_feature_enabled('central_database',application_id) %}
- "templates/postgres.template.yml"
{% endif %}
#- "templates/redis.template.yml"
- "templates/web.template.yml"
## Uncomment the next line to enable the IPv6 listener
#- "templates/web.ipv6.template.yml"
- "templates/web.ratelimited.template.yml"
## Uncomment these two lines if you wish to add Lets Encrypt (https)
#- "templates/web.ssl.template.yml"
#- "templates/web.letsencrypt.ssl.template.yml"
## which TCP/IP ports should this container expose?
## If you want Discourse to share a port with another webserver like Apache or nginx,
## see https://meta.discourse.org/t/17247 for details
expose:
- "127.0.0.1:{{ports.localhost.http[application_id]}}:80" # http
params:
db_default_text_search_config: "pg_catalog.english"
## Set db_shared_buffers to a max of 25% of the total memory.
## will be set automatically by bootstrap based on detected RAM, or you can override
db_shared_buffers: "4096MB"
## can improve sorting performance, but adds memory usage per-connection
#db_work_mem: "40MB"
## Which Git revision should this container use? (default: tests-passed)
#version: tests-passed
env:
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LANGUAGE: en_US.UTF-8
#DISCOURSE_DEFAULT_LOCALE: {{ HOST_LL }} # Deactivated because not right format was selected @todo find right format
## How many concurrent web requests are supported? Depends on memory and CPU cores.
## will be set automatically by bootstrap based on detected CPUs, or you can override
UNICORN_WORKERS: 8
## Required. Discourse will not work with a bare IP number.
DISCOURSE_HOSTNAME: {{domains | get_domain(application_id)}}
## Uncomment if you want the container to be started with the same
## hostname (-h option) as specified above (default "$hostname-$config")
#DOCKER_USE_HOSTNAME: true
## on initial signup example 'user1@example.com,user2@example.com'
DISCOURSE_DEVELOPER_EMAILS: {{ users.administrator.email }}
# Set Logo
{% if service_provider.platform.logo | bool %}
DISCOURSE_LOGO_URL: "{{ service_provider.platform.logo }}"
DISCOURSE_LOGO_SMALL_URL: "{{ service_provider.platform.logo }}"
{% endif %}
# SMTP ADDRESS, username, and password are required
# WARNING the char '#' in SMTP password can cause problems!
DISCOURSE_SMTP_ADDRESS: {{ system_email.host }}
DISCOURSE_SMTP_PORT: {{ system_email.port }}
DISCOURSE_SMTP_USER_NAME: {{ users['no-reply'].email }}
DISCOURSE_SMTP_PASSWORD: {{ users['no-reply'].mailu_token }}
DISCOURSE_SMTP_ENABLE_START_TLS: {{ system_email.start_tls }}
DISCOURSE_SMTP_FORCE_TLS: {{ system_email.tls }}
DISCOURSE_SMTP_DOMAIN: {{ system_email.domain }}
DISCOURSE_NOTIFICATION_EMAIL: {{ users['no-reply'].email }}
# Database Configuration
DISCOURSE_DB_USERNAME: {{ database_username }}
DISCOURSE_DB_PASSWORD: {{ database_password }}
DISCOURSE_DB_HOST: {{ database_host }}
DISCOURSE_DB_NAME: {{ database_name }}
# Redis Configuration
DISCOURSE_REDIS_HOST: {{application_id}}-redis
## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
#LETSENCRYPT_ACCOUNT_EMAIL: administrator@veen.world
## The http or https CDN address for this Discourse instance (configured to pull)
## see https://meta.discourse.org/t/14857 for details
#DISCOURSE_CDN_URL: https://discourse-cdn.example.com
## The maxmind geolocation IP address key for IP address lookup
## see https://meta.discourse.org/t/-/137387/23 for details
#DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456
## The Docker container is stateless; all data is stored in /shared
volumes:
- volume:
host: discourse_data
guest: /shared
- volume:
host: /var/discourse/shared/standalone/log/var-log
guest: /var/log
## Plugins go here
## see https://meta.discourse.org/t/19157 for details
hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone --depth=1 https://github.com/discourse/docker_manager.git
- git clone --depth=1 https://github.com/discourse/discourse-activity-pub.git
- git clone --depth=1 https://github.com/discourse/discourse-calendar.git
- git clone --depth=1 https://github.com/discourse/discourse-akismet.git
- git clone --depth=1 https://github.com/discourse/discourse-cakeday.git
- git clone --depth=1 https://github.com/discourse/discourse-solved.git
- git clone --depth=1 https://github.com/discourse/discourse-voting.git
- git clone --depth=1 https://github.com/discourse/discourse-oauth2-basic.git
{% if applications | is_feature_enabled('oidc',application_id) %}
- git clone --depth=1 https://github.com/discourse/discourse-openid-connect.git
{% endif %}
{% if applications | is_feature_enabled('ldap',application_id) %}
- git clone --depth=1 https://github.com/jonmbake/discourse-ldap-auth.git
{% endif %}
## Any custom commands to run after building
run:
- exec: echo "Beginning of custom commands"
- exec: rails r "SiteSetting.force_https = true"
## If you want to set the 'From' email address for your first registration, uncomment and change:
## After getting the first signup email, re-comment the line. It only needs to run once.
#- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'"
{% if applications | is_feature_enabled('oidc',application_id) %}
# Deactivate Default Login
- exec: rails r "SiteSetting.enable_local_logins = false"
- exec: rails r "SiteSetting.enable_passkeys = false" # https://meta.discourse.org/t/passwordless-login-using-passkeys/285589
- exec: rails r "SiteSetting.username_change_period = 0" # Deactivate changing of username
# Activate Administrator User
#- 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}}')"
# 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
# docker exec -it discourse_application rails runner "user = User.find_by_email('test@cymais.cloud'); UserAuth.create(user_id: user.id, provider: 'oidc', uid: 'eindeutige_oidc_id', info: { name: user.username, email: user.email })"
# OIDC Activation
- exec: rails r "SiteSetting.openid_connect_enabled = true"
- exec: rails r "SiteSetting.openid_connect_discovery_document = '{{oidc.client.discovery_document}}'"
- exec: rails r "SiteSetting.openid_connect_client_id = '{{oidc.client.id}}'"
- exec: rails r "SiteSetting.openid_connect_client_secret = '{{oidc.client.secret}}'"
- exec: rails r "SiteSetting.openid_connect_rp_initiated_logout_redirect = 'https://{{domains | get_domain(application_id)}}'"
- exec: rails r "SiteSetting.openid_connect_allow_association_change = false"
- exec: rails r "SiteSetting.openid_connect_rp_initiated_logout = true"
{% endif %}
{% if applications | is_feature_enabled('ldap',application_id) %}
# Enable LDAP authentication
- exec: rails r "SiteSetting.ldap_auth_enabled = true"
- exec: rails r "SiteSetting.ldap_sync_enabled = true"
# LDAP connection settings
- exec: rails r "SiteSetting.ldap_sync_host = '{{ ldap.server.domain }}'"
- exec: rails r "SiteSetting.ldap_sync_port = {{ ldap.server.port }}"
- exec: rails r "SiteSetting.ldap_encryption = 'simple_tls'"
- exec: rails r "SiteSetting.ldap_base_dn = '{{ ldap.dn.root }}'"
- exec: rails r "SiteSetting.ldap_bind_dn = '{{ ldap.dn.administrator.data }}'"
- exec: rails r "SiteSetting.ldap_bind_password = '{{ ldap.bind_credential }}'"
# LDAP additional configuration
- exec: rails r "SiteSetting.ldap_user_filter = '{{ ldap.filters.users.login }}'"
- exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.ou.groups }}'"
- exec: rails r "SiteSetting.ldap_group_member_check = 'memberUid'"
- exec: rails r "SiteSetting.ldap_sync_period = 1"
- exec: rails r "SiteSetting.ldap_sync_unit = 'hours'"
{% endif %}
- exec: echo "End of custom commands"
docker_args:
- --network={{application_id}}_default
- --name={{applications[application_id].container}}

View File

@@ -0,0 +1,8 @@
{% include 'roles/docker-compose/templates/base.yml.j2' %}
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
redis:
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
discourse_default:
external: true

View File

@@ -0,0 +1,28 @@
network: "discourse_default" # Name of the docker network
container: "discourse_application" # Name of the container application
repository: "discourse_repository" # Name of the repository folder
features:
matomo: true
css: true
portfolio_iframe: true
oidc: true
central_database: true
ldap: false # @todo implement and activate
csp:
flags:
style-src:
unsafe-inline: true
script-src-elem:
unsafe-inline: true
whitelist:
font-src:
- "http://*.{{primary_domain}}"
domains:
canonical:
- "forum.{{ primary_domain }}"
docker:
services:
database:
enabled: true
redis:
enabled: true

View File

@@ -0,0 +1,5 @@
application_id: "discourse"
database_password: "{{ applications[application_id].credentials.database_password }}"
database_type: "postgres"
docker_repository_directory : "{{docker_compose.directories.services}}{{applications[application_id].repository}}/"
discourse_application_yml_destination: "{{docker_repository_directory }}containers/{{applications[application_id].container}}.yml"