Compare commits

...

8 Commits

Author SHA1 Message Date
983287a84a Finished mediawiki oidc implementation 2025-08-29 04:24:50 +02:00
dd9a9b6d84 feat(mediawiki): Refactor OIDC + debug; install Composer deps in-container; modularize role
Discussion: https://chatgpt.com/share/68b10c0a-c308-800f-93ac-2ffb386cf58b

- Split tasks into 01_install, 02_debug, 03_admin, 04_extensions, 05_oidc.
- Ensure unzip+git+composer on demand in the container; run Composer as www-data with COMPOSER_HOME=/tmp/composer.
- Idempotently unpack/install PluggableAuth & OpenIDConnect; run composer install only if vendor/ is missing.
- Add sanity check for Jumbojett\OpenIDConnectClient.
- Copy oidc.php only when changed and append a single require_once to LocalSettings.php.
- Use REL1_44-compatible numeric array for $wgPluggableAuth_Config; set $wgPluggableAuth_ButtonLabelMessage.
- Debug: add debug.php that logs to STDERR (visible via docker logs); toggle cleanly with MODE_DEBUG.
- Enable OIDC feature in config; add paths/OIDC/extension vars in vars/main.yml.

fix(services): include SYS_SERVICE_GROUP_CLEANUP in StartPre lock (ssd-hdd, docker-hard).

fix(desktop/joomla): simplify MODE_DEBUG templating.

chore: minor cleanups and renames.
2025-08-29 04:10:46 +02:00
23a2e081bf Optimized services 2025-08-29 01:11:06 +02:00
4cbd848026 Set SYS_TIMER_ALL_ENABLED ny default to DEBUG_MODE 2025-08-29 01:06:09 +02:00
d67f660152 Enabled CSS and Desktop for Mediawiki 2025-08-29 00:46:29 +02:00
5c6349321b Removed MyBB role, because it's deprecated and Discourse takes over 2025-08-29 00:12:35 +02:00
af1ee64246 web-app-mediawiki: installer-driven bootstrap, DB readiness, idempotent admin; drop LocalSettings bind-mount
Tasks:
- Enable docker_compose_flush_handlers=true so services come up immediately.
- Add DB readiness guard via maintenance/sql.php (SELECT 1).
- Run maintenance/install.php on empty schema with robust changed_when/failed_when (merge stdout+stderr); keep secrets hidden.
- Run maintenance/update.php for migrations with neutral changed_when unless work is done.
- Make admin creation idempotent: tolerate 'already exists' and 'Account exists', keep async+no_log.

Config changes:
- Remove LocalSettings.php template and its host bind-mount from compose.
- Drop MediaWiki settings path variables and META namespace variable (unused after switch).

Result: First boot is fully automated (schema + admin), subsequent runs are cleanly idempotent.

Ref: ChatGPT conversation (Aug 28, 2025, Europe/Berlin) — https://chatgpt.com/share/68b0d2e1-9bc0-800f-81a5-db03ce0b81e3.
2025-08-29 00:07:00 +02:00
d96bfc64a6 added possibility to deactivate docker service loading for performance 2025-08-28 22:47:05 +02:00
36 changed files with 469 additions and 307 deletions

View File

@@ -2,7 +2,7 @@
# Service Timers
## Meta
SYS_TIMER_ALL_ENABLED: "{{ not MODE_DEBUG }}" # Runtime Variables for Process Control - Activates all timers, independend if the handlers had been triggered
SYS_TIMER_ALL_ENABLED: "{{ MODE_DEBUG }}" # Runtime Variables for Process Control - Activates all timers, independend if the handlers had been triggered
## Server Tact Variables

View File

@@ -48,8 +48,8 @@ defaults_networks:
subnet: 192.168.102.16/28
web-app-moodle:
subnet: 192.168.102.32/28
web-app-mybb:
subnet: 192.168.102.48/28
# Free:
# subnet: 192.168.102.48/28
web-app-nextcloud:
subnet: 192.168.102.64/28
web-app-openproject:

View File

@@ -26,7 +26,7 @@ ports:
web-app-gitea: 8002
web-app-wordpress: 8003
web-app-mediawiki: 8004
web-app-mybb: 8005
# Free: 8005
web-app-yourls: 8006
web-app-mailu: 8007
web-app-elk: 8008

View File

@@ -25,16 +25,6 @@
dest: "{{ NGINX.FILES.CONFIGURATION }}"
notify: docker compose up
- name: Include health dependencies
include_role:
name: "{{ item }}"
loop:
- sys-ctl-hlth-webserver
- sys-ctl-hlth-csp
vars:
# Extra flush is for performance reasons not necessary
flush_handlers: false
- name: Include openresty
# Outside of run_once block is necessary for handler loading
# Otherwise the when: condition from the block is added to the handlers
@@ -49,4 +39,13 @@
vars:
# Flush openresty handler on first run, so that openresty is up, before openresty related handlers are triggered
flush_handlers: true
when: run_once_svc_prx_openresty is not defined
when: run_once_svc_prx_openresty is not defined
- name: Include health dependencies
include_role:
name: "{{ item }}"
loop:
- sys-ctl-hlth-webserver
- sys-ctl-hlth-csp
vars:
flush_handlers: false

View File

@@ -1,5 +1,5 @@
- include_role:
name: sys-service
vars:
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_OPTIMIZE_DRIVE }} {{ SYS_SERVICE_BACKUP_RMT_2_LOC }} --timeout "{{ SYS_TIMEOUT_STORAGE_OPTIMIZER }}"'
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_OPTIMIZE_DRIVE }} {{ SYS_SERVICE_BACKUP_RMT_2_LOC }} {{ SYS_SERVICE_GROUP_CLEANUP | join(" ") }} --timeout "{{ SYS_TIMEOUT_STORAGE_OPTIMIZER }}"'
system_service_tpl_exec_start: '{{ system_service_script_exec }} --mass-storage-path {{ OPT_DRIVE_MASS_STORAGE_PATH }} --rapid-storage-path {{ OPT_DRIVE_RAPID_STORAGE_PATH }}'

View File

@@ -13,6 +13,7 @@
- include_role:
name: sys-service
vars:
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_CSP_CRAWLER }}"
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_on_calendar: "{{ SYS_SCHEDULE_HEALTH_CSP_CRAWLER }}"
system_service_timer_enabled: true
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_timeout_start_sec: 15min

View File

@@ -8,7 +8,7 @@
vars:
system_service_on_calendar: "{{ SYS_SCHEDULE_REPAIR_DOCKER_HARD }}"
system_service_timer_enabled: true
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_REPAIR_DOCKER_HARD }} --timeout "{{ SYS_TIMEOUT_RESTART_DOCKER }}"'
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_REPAIR_DOCKER_HARD }} {{ SYS_SERVICE_GROUP_CLEANUP | join(" ") }} --timeout "{{ SYS_TIMEOUT_RESTART_DOCKER }}"'
system_service_tpl_exec_start: '{{ system_service_script_exec }} {{ PATH_DOCKER_COMPOSE_INSTANCES }}'
system_service_tpl_exec_start_post: "/usr/bin/systemctl start {{ SYS_SERVICE_CLEANUP_ANONYMOUS_VOLUMES }}"
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"

View File

@@ -12,4 +12,3 @@
system_service_tpl_exec_start_pre: "/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(' ') }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP| join(' ') }} {{ SYS_SERVICE_REPAIR_DOCKER_SOFT }} --timeout '{{ SYS_TIMEOUT_HEAL_DOCKER }}'"
system_service_tpl_exec_start: >
/bin/sh -c '{{ system_service_script_exec }} --manipulation-string "{{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }}" {{ PATH_DOCKER_COMPOSE_INSTANCES }}'

View File

@@ -0,0 +1 @@
SYS_SVC_DOCKER_LOAD_SERVICES: true # Implemented to deploy roles during development faster by skipping dependencies

View File

@@ -22,4 +22,5 @@
- sys-ctl-hlth-docker-container
- sys-ctl-hlth-docker-volumes
- sys-ctl-rpr-docker-soft
- sys-ctl-rpr-docker-hard
- sys-ctl-rpr-docker-hard
when: SYS_SVC_DOCKER_LOAD_SERVICES | bool

View File

@@ -16,7 +16,7 @@ const KC_CONFIG = {
silentCheckSsoRedirectUri: window.location.origin + "{{ DESKTOP_LOCATION_SILENT_CHECK }}"
};
const DEBUG = {{ 'true' if MODE_DEBUG | default(false) else 'false' }};
const DEBUG = {{ 'true' if MODE_DEBUG else 'false' }};
/* ==============================================
2) Helpers for menu manipulation

View File

@@ -4,9 +4,9 @@
- docker
- exec
- -e
- "J_MODE_DEBUG={{ MODE_DEBUG | default(false) | bool | ternary('1','0') }}"
- "J_MODE_DEBUG={{ MODE_DEBUG | bool | ternary('1','0') }}"
- -e
- "J_ERR_LEVEL={{ MODE_DEBUG | default(false) | bool | ternary('maximum','default') }}"
- "J_ERR_LEVEL={{ MODE_DEBUG | bool | ternary('maximum','default') }}"
- "{{ JOOMLA_CONTAINER }}"
- php
- -r

View File

@@ -1,5 +1,4 @@
sitename: "Wiki on {{ PRIMARY_DOMAIN | upper }}"
meta_namespace: "Meta"
server:
domains:
canonical:
@@ -17,5 +16,8 @@ docker:
volumes:
data: mediawiki_data
features:
logout: true
logout: true
central_database: true
css: true
desktop: true
oidc: true

View File

@@ -0,0 +1,44 @@
- name: "Wait for DB to be reachable"
command: >
docker exec {{ MEDIAWIKI_CONTAINER }}
php /var/www/html/maintenance/sql.php --query "SELECT 1;"
register: mw_db_ready
retries: 15
delay: 2
until: mw_db_ready.rc == 0
changed_when: false
failed_when: false
- name: "Install MediaWiki if no schema exists"
command: >
docker exec -u {{ MEDIAWIKI_USER }} {{ MEDIAWIKI_CONTAINER }}
php /var/www/html/maintenance/install.php
--dbname="{{ database_name }}"
--dbuser="{{ database_username }}"
--dbpass="{{ database_password }}"
--dbserver="{{ database_host }}:{{ database_port }}"
--installdbuser="{{ database_username }}"
--installdbpass="{{ database_password }}"
--server="{{ MEDIAWIKI_URL }}"
--scriptpath=""
--lang={{ HOST_LL }}
--pass="{{ MEDIAWIKI_ADMINISTRATOR_PASSWORD }}"
"{{ MEDIAWIKI_SITENAME }}"
"{{ MEDIAWIKI_ADMINISTRATOR_NAME }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
register: mw_install
changed_when: mw_install.rc == 0
failed_when: >
mw_install.rc != 0 and
('LocalSettings.php file has been detected' not in (((mw_install.stdout | default('')) ~ (mw_install.stderr | default(''))))) and
('run update.php instead' not in (((mw_install.stdout | default('')) ~ (mw_install.stderr | default('')))))
- name: "Initialize / migrate MediaWiki database schema"
command: >
docker exec
-u {{ MEDIAWIKI_USER }}
{{ MEDIAWIKI_CONTAINER }}
php /var/www/html/maintenance/update.php --quick
register: mw_update
changed_when: "'...done.' in (mw_update.stdout | default(''))"
failed_when: mw_update.rc != 0

View File

@@ -0,0 +1,11 @@
---
# Aktiviert Debug, wenn MODE_DEBUG=true; entfernt es sauber, wenn false.
- name: "DEBUG | Enable block when MODE_DEBUG=true"
when: MODE_DEBUG | bool
include_tasks: _debug_enable.yml
- name: "DEBUG | Disable block when MODE_DEBUG=false"
when: not (MODE_DEBUG | bool)
include_tasks: _debug_disable.yml

View File

@@ -0,0 +1,19 @@
- name: "Create MediaWiki admin user"
command: >
docker exec
-u {{ MEDIAWIKI_USER }}
{{ MEDIAWIKI_CONTAINER }}
php /var/www/html/maintenance/createAndPromote.php
--bureaucrat --sysop
{{ MEDIAWIKI_ADMINISTRATOR_NAME }}
{{ MEDIAWIKI_ADMINISTRATOR_PASSWORD }}
{{ MEDIAWIKI_ADMINISTRATOR_EMAIL }}
register: create_admin
changed_when: >
('created' in ((create_admin.stdout | default('')) ~ (create_admin.stderr | default('')))) or
('Created' in ((create_admin.stdout | default('')) ~ (create_admin.stderr | default(''))))
failed_when: >
create_admin.rc != 0 and
('already exists' not in ((create_admin.stdout | default('')) ~ (create_admin.stderr | default('')))) and
('Account exists' not in ((create_admin.stdout | default('')) ~ (create_admin.stderr | default(''))))
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@@ -0,0 +1,147 @@
---
# Install PluggableAuth + OpenIDConnect INTO the running container (idempotent)
# Downloads on host (config dir), copy+extract inside container.
- name: "EXT | Ensure local download dir exists"
file:
path: "{{ MEDIAWIKI_EXT_CFG_BASE }}"
state: directory
mode: "0755"
- name: "EXT | Download extension tarballs ({{ MEDIAWIKI_EXT_BRANCH }})"
get_url:
url: "{{ ext.url }}"
dest: "{{ MEDIAWIKI_EXT_CFG_BASE }}/{{ ext.name }}.tar.gz"
mode: "0644"
loop: "{{ MEDIAWIKI_EXT_LIST }}"
loop_control:
loop_var: ext
label: "{{ ext.name }}"
- name: "EXT | Copy & extract into container if not installed"
shell: >
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc '
set -e
dst="{{ MEDIAWIKI_HTML_DIR }}/extensions/{{ ext.name }}"
if [ ! -f "$dst/extension.json" ]; then
rm -rf "$dst" && mkdir -p "$dst"
fi
'
&& docker cp "{{ MEDIAWIKI_EXT_CFG_BASE }}/{{ ext.name }}.tar.gz" "{{ MEDIAWIKI_CONTAINER }}:/tmp/{{ ext.name }}.tar.gz"
&& docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc '
set -e
dst="{{ MEDIAWIKI_HTML_DIR }}/extensions/{{ ext.name }}"
if [ ! -f "$dst/extension.json" ]; then
tar -xzf /tmp/{{ ext.name }}.tar.gz -C "$dst" --strip-components=1
chown -R {{ MEDIAWIKI_USER }}:{{ MEDIAWIKI_USER }} "$dst"
rm -f /tmp/{{ ext.name }}.tar.gz
echo INSTALLED:{{ ext.name }}
else
rm -f /tmp/{{ ext.name }}.tar.gz
echo PRESENT:{{ ext.name }}
fi
'
args:
executable: /bin/bash
loop: "{{ MEDIAWIKI_EXT_LIST }}"
loop_control:
loop_var: ext
label: "{{ ext.name }}"
register: _install_results
changed_when: "'INSTALLED:' in (stdout | default(''))"
- name: "EXT | Determine if any extension was installed"
set_fact:
_any_installed: >-
{{ _install_results.results
| map(attribute='stdout')
| select('search', 'INSTALLED:')
| list | length > 0 }}
# Ensure unzip + git are available in the container (idempotent)
- name: "EXT | Ensure unzip+git available in container"
shell: |
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc '
set -e
need=0
command -v unzip >/dev/null 2>&1 || need=1
command -v git >/dev/null 2>&1 || need=1
if [ "$need" -eq 1 ]; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y --no-install-recommends unzip git ca-certificates
rm -rf /var/lib/apt/lists/*
echo INSTALLED_TOOLS
fi
'
args:
executable: /bin/bash
register: _tools
changed_when: "'INSTALLED_TOOLS' in (_tools.stdout | default(''))"
# Ensure Composer is available inside the container (idempotent)
- name: "EXT | Ensure Composer available in container"
shell: |
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc '
if ! command -v composer >/dev/null 2>&1; then
php -r "copy(\"https://getcomposer.org/installer\", \"composer-setup.php\");"
php composer-setup.php --install-dir=/usr/local/bin --filename=composer
rm -f composer-setup.php
echo INSTALLED_COMPOSER
fi
'
args:
executable: /bin/bash
register: _composer
changed_when: "'INSTALLED_COMPOSER' in (_composer.stdout | default(''))"
# Install dependencies per extension (only if vendor is missing)
# Use /tmp/composer for HOME/CACHE to avoid /var/www permission issues.
- name: "EXT | composer install in each extension when needed"
shell: |
docker exec -u {{ MEDIAWIKI_USER }} {{ MEDIAWIKI_CONTAINER }} bash -lc '
set -e
d="{{ MEDIAWIKI_HTML_DIR }}/extensions/{{ ext.name }}"
if [ -f "$d/composer.json" ] && [ ! -f "$d/vendor/autoload.php" ]; then
install -d -m 0775 /tmp/composer/cache
export COMPOSER_HOME=/tmp/composer
export COMPOSER_CACHE_DIR=/tmp/composer/cache
cd "$d"
composer install --no-dev -n --prefer-dist
echo COMPOSER_INSTALLED:{{ ext.name }}
fi
'
args:
executable: /bin/bash
loop: "{{ MEDIAWIKI_EXT_LIST }}"
loop_control:
loop_var: ext
label: "{{ ext.name }}"
register: _ext_composer
changed_when: "'COMPOSER_INSTALLED:' in (stdout | default(''))"
# Sanity check: Jumbojett OIDC client must be loadable
- name: "EXT | Sanity check: Jumbojett OpenIDConnect client present"
shell: >
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc
'php -r "(@require \"{{ MEDIAWIKI_HTML_DIR }}/vendor/autoload.php\"); @require \"{{ MEDIAWIKI_HTML_DIR }}/extensions/OpenIDConnect/vendor/autoload.php\"; exit(class_exists(\"Jumbojett\\\\OpenIDConnectClient\")?0:1);"'
args:
executable: /bin/bash
register: _oidc_class
changed_when: false
failed_when: _oidc_class.rc != 0
# Run MediaWiki updates (changed if something installed)
- name: "EXT | Run update.php (safe to run repeatedly)"
shell: >
docker exec -u {{ MEDIAWIKI_USER }} {{ MEDIAWIKI_CONTAINER }}
php {{ MEDIAWIKI_HTML_DIR }}/maintenance/update.php --quick
args:
executable: /bin/bash
register: _mw_upd
changed_when: >
(_any_installed) or
(_ext_composer is defined and
(_ext_composer.results | map(attribute='stdout')
| select('search','COMPOSER_INSTALLED:')
| list | length > 0))

View File

@@ -0,0 +1,61 @@
---
# All operations remain INSIDE the running container.
# Template is rendered into docker_compose.directories.config on the host.
# Change detection is based on checksum comparison vs. container file.
- name: "OIDC | Ensure local config directory exists"
file:
path: "{{ MEDIAWIKI_CONFIG_DIR }}"
state: directory
mode: "0755"
- name: "OIDC | Render oidc.php locally (template into config dir)"
template:
src: "oidc.php.j2"
dest: "{{ MEDIAWIKI_OIDC_FILE }}"
mode: "0644"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "OIDC | Compute local checksum"
stat:
path: "{{ MEDIAWIKI_OIDC_FILE }}"
checksum_algorithm: sha256
register: _local_oidc
- name: "OIDC | Compute container checksum (if exists)"
shell: >
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc
"test -f {{ MEDIAWIKI_HTML_DIR }}/oidc.php &&
sha256sum {{ MEDIAWIKI_HTML_DIR }}/oidc.php | awk '{print $1}' || echo MISSING"
args:
executable: /bin/bash
register: _remote_oidc
changed_when: false
- name: "OIDC | Copy oidc.php into container docroot only if different"
shell: >
if [ "{{ (_remote_oidc.stdout | default('') | trim) }}" != "{{ _local_oidc.stat.checksum }}" ]; then
docker cp "{{ MEDIAWIKI_OIDC_FILE }}" "{{ MEDIAWIKI_CONTAINER }}:{{ MEDIAWIKI_HTML_DIR }}/oidc.php" &&
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc "chown {{ MEDIAWIKI_USER }}:{{ MEDIAWIKI_USER }} {{ MEDIAWIKI_HTML_DIR }}/oidc.php && chmod 0644 {{ MEDIAWIKI_HTML_DIR }}/oidc.php" &&
echo COPIED;
fi
args:
executable: /bin/bash
register: _cp_oidc
changed_when: "'COPIED' in (_cp_oidc.stdout | default(''))"
- name: "OIDC | Require oidc.php once inside LocalSettings.php"
shell: |
docker exec -u {{ MEDIAWIKI_USER }} {{ MEDIAWIKI_CONTAINER }} bash -lc '
LSP={{ MEDIAWIKI_HTML_DIR }}/LocalSettings.php
LINE="require_once __DIR__ . '\''/oidc.php'\'';"
if ! grep -Fqx -- "$LINE" "$LSP"; then
printf "%s\n" "$LINE" >> "$LSP"
echo ADDED_REQUIRE
fi
'
args:
executable: /bin/bash
register: _mw_oidc_req
changed_when: "'ADDED_REQUIRE' in (_mw_oidc_req.stdout | default(''))"

View File

@@ -0,0 +1,27 @@
- name: "Remove require_once line from LocalSettings.php (if present)"
shell: |
docker exec -u {{ MEDIAWIKI_USER }} {{ MEDIAWIKI_CONTAINER }} bash -lc '
LSP={{ MEDIAWIKI_HTML_DIR }}/LocalSettings.php
if [ -f "$LSP" ]; then
if grep -Fqx -- "require_once __DIR__ . '\''/debug.php'\'';" "$LSP"; then
sed -i "\#require_once __DIR__ . '/debug.php';#d" "$LSP"
echo REMOVED_REQUIRE
fi
fi
'
args: { executable: /bin/bash }
register: _dbg_rm_req
changed_when: "'REMOVED_REQUIRE' in (_dbg_rm_req.stdout | default(''))"
- name: "Remove debug.php from container (if present)"
shell: >
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc
"if [ -f {{ MEDIAWIKI_HTML_DIR }}/debug.php ]; then rm -f {{ MEDIAWIKI_HTML_DIR }}/debug.php; echo REMOVED_FILE; fi"
args: { executable: /bin/bash }
register: _dbg_rm_file
changed_when: "'REMOVED_FILE' in (_dbg_rm_file.stdout | default(''))"
- name: "Remove local debug.php (if present)"
file:
path: "{{ MEDIAWIKI_CONFIG_DIR }}/debug.php"
state: absent

View File

@@ -0,0 +1,45 @@
- name: "Render debug.php locally"
template:
src: "debug.php.j2"
dest: "{{ MEDIAWIKI_CONFIG_DIR }}/debug.php"
mode: "0644"
- name: "Compute local checksum"
stat:
path: "{{ MEDIAWIKI_CONFIG_DIR }}/debug.php"
checksum_algorithm: sha256
register: _dbg_local
- name: "Compute container checksum (if exists)"
shell: >
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc
"test -f {{ MEDIAWIKI_HTML_DIR }}/debug.php &&
sha256sum {{ MEDIAWIKI_HTML_DIR }}/debug.php | awk '{print $1}' || echo MISSING"
args: { executable: /bin/bash }
register: _dbg_remote
changed_when: false
- name: "Copy debug.php into container only if different"
shell: >
if [ "{{ (_dbg_remote.stdout | default('') | trim) }}" != "{{ _dbg_local.stat.checksum }}" ]; then
docker cp "{{ MEDIAWIKI_CONFIG_DIR }}/debug.php" "{{ MEDIAWIKI_CONTAINER }}:{{ MEDIAWIKI_HTML_DIR }}/debug.php" &&
docker exec {{ MEDIAWIKI_CONTAINER }} bash -lc "chown {{ MEDIAWIKI_USER }}:{{ MEDIAWIKI_USER }} {{ MEDIAWIKI_HTML_DIR }}/debug.php && chmod 0644 {{ MEDIAWIKI_HTML_DIR }}/debug.php" &&
echo COPIED;
fi
args: { executable: /bin/bash }
register: _dbg_cp
changed_when: "'COPIED' in (_dbg_cp.stdout | default(''))"
- name: "require_once debug.php in LocalSettings.php"
shell: |
docker exec -u {{ MEDIAWIKI_USER }} {{ MEDIAWIKI_CONTAINER }} bash -lc '
LSP={{ MEDIAWIKI_HTML_DIR }}/LocalSettings.php
LINE="require_once __DIR__ . '\''/debug.php'\'';"
if ! grep -Fqx -- "$LINE" "$LSP"; then
printf "%s\n" "$LINE" >> "$LSP"
echo ADDED_DEBUG_REQUIRE
fi
'
args: { executable: /bin/bash }
register: _dbg_req
changed_when: "'ADDED_DEBUG_REQUIRE' in (_dbg_req.stdout | default(''))"

View File

@@ -1,33 +1,22 @@
---
- name: "load docker, db and proxy for {{ application_id }}"
- name: "load docker, db and proxy for '{{ application_id }}'"
include_role:
name: sys-stk-full-stateful
vars:
docker_compose_flush_handlers: false
docker_compose_flush_handlers: true
- name: "Deploy MediaWiki LocalSettings.php"
template:
src: "LocalSettings.php.j2"
dest: "{{ MEDIAWIKI_SETTINGS_HOST_PATH }}"
mode: '0644'
- name: "Load install procedures for '{{ application_id }}''"
include_tasks: 01_install.yml
- name: "Flush docker compose handlers"
meta: flush_handlers
- name: "Load debug procedures for '{{ application_id }}''"
include_tasks: 02_debug.yml
- name: "Create MediaWiki admin user"
command: >
docker exec
-u {{ MEDIAWIKI_USER }}
{{ MEDIAWIKI_CONTAINER }}
php /var/www/html/maintenance/createAndPromote.php
--bureaucrat --sysop
{{ MEDIAWIKI_ADMINISTRATOR_NAME }}
{{ MEDIAWIKI_ADMINISTRATOR_PASSWORD }}
{{ MEDIAWIKI_ADMINISTRATOR_EMAIL }}
register: create_admin
changed_when: >
'created' in (create_admin.stdout | default('')) or
'Created' in (create_admin.stdout | default(''))
failed_when: >
create_admin.rc != 0 and
('already exists' not in (create_admin.stdout | default('') ~ create_admin.stderr | default('')))
- name: "Load admin setup procedures for '{{ application_id }}''"
include_tasks: 03_admin.yml
- name: "Load OIDC procedures for '{{ application_id }}''"
include_tasks: "{{ item }}"
loop:
- 04_extensions.yml
- 05_oidc.yml
when: MEDIAWIKI_OIDC_ENABLED | bool

View File

@@ -1,29 +0,0 @@
<?php
# This file was automatically generated by the MediaWiki installer.
# Managed by Ansible in your setup.
# Basic settings
$wgSitename = "{{ MEDIAWIKI_SITENAME }}";
$wgMetaNamespace = "{{ MEDIAWIKI_META_NAMESPACE }}";
$wgScriptPath = "";
$wgServer = "{{ MEDIAWIKI_URL }}";
# Database settings
$wgDBtype = "mysql";
$wgDBserver = "{{ database_host }}:{{ database_port }}";
$wgDBname = "{{ database_name }}";
$wgDBuser = "{{ database_username }}";
$wgDBpassword = "{{ database_password }}";
# Email settings
$wgEnableEmail = true;
$wgEnableUserEmail = true;
$wgEmergencyContact = "{{ users.administrator.email }}";
$wgPasswordSender = "{{ users['no-reply'].email }}";
# Default skin
$wgDefaultSkin = "vector";
# Extensions (examples)
wfLoadExtension( 'ParserFunctions' );
wfLoadExtension( 'Cite' );

View File

@@ -0,0 +1,21 @@
<?php
$wgShowExceptionDetails = true;
$wgShowDBErrorBacktrace = true;
$wgShowSQLErrors = true;
$wgDevelopmentWarnings = true;
@ini_set('display_errors', '1');
@ini_set('display_startup_errors', '1');
@ini_set('log_errors', '1');
@ini_set('error_log', '/proc/self/fd/2');
$wgDebugLogGroups = array_merge($wgDebugLogGroups ?? [], [
'exception' => 'php://stderr',
'error' => 'php://stderr',
'authentication' => 'php://stderr',
'session' => 'php://stderr',
'resourceloader' => 'php://stderr',
'PluggableAuth' => 'php://stderr',
'OpenIDConnect' => 'php://stderr',
]);

View File

@@ -5,7 +5,6 @@
image: "{{ MEDIAWIKI_IMAGE }}:{{ MEDIAWIKI_VERSION }}"
volumes:
- "data:/var/www/html/"
- "{{ MEDIAWIKI_SETTINGS_HOST_PATH }}:{{ MEDIAWIKI_SETTINGS_DOCK_PATH }}:ro"
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %}

View File

@@ -0,0 +1,27 @@
<?php
// ### OIDC (PluggableAuth) BEGIN (managed by Ansible)
wfLoadExtension( 'PluggableAuth' );
wfLoadExtension( 'OpenIDConnect' );
$wgPluggableAuth_EnableAutoLogin = true; // dont auto-redirect to IdP
$wgPluggableAuth_EnableLocalLogin = false; // keep local user/pass login
$wgPluggableAuth_ButtonLabel = '{{ MEDIAWIKI_OIDC_BUTTON_TEXT }}';
// PluggableAuth expects a list of providers (numeric array) on REL1_44
$wgPluggableAuth_Config = [
[
'plugin' => 'OpenIDConnect',
'data' => [
// For Keycloak, use the REALM URL, e.g. https://auth.example/realms/<realm>
'providerURL' => '{{ MEDIAWIKI_OIDC_ISSUER }}',
'clientID' => '{{ MEDIAWIKI_OIDC_CLIENT_ID }}',
'clientsecret' => '{{ MEDIAWIKI_OIDC_CLIENT_SECRET }}',
'scope' => [ 'openid', 'profile', 'email' ],
],
],
];
// Helpful defaults
$wgOpenIDConnect_UseEmailNameAsUserName = true;
$wgOpenIDConnect_MigrateUsers = true;
// ### OIDC (PluggableAuth) END

View File

@@ -5,19 +5,37 @@ container_port: 80
# Mediawiki
MEDIAWIKI_SITENAME: "{{ applications | get_app_conf(application_id, 'sitename') }}"
MEDIAWIKI_META_NAMESPACE: "{{ applications | get_app_conf(application_id, 'meta_namespace') }}"
MEDIAWIKI_URL: "{{ domains | get_url(application_id, WEB_PROTOCOL) }}"
## Folders
MEDIAWIKI_HTML_DIR: "/var/www/html"
MEDIAWIKI_CONFIG_DIR: "{{ docker_compose.directories.config }}"
MEDIAWIKI_OIDC_FILE: "{{ docker_compose.directories.config }}/oidc.php"
## Docker
MEDIAWIKI_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.mediawiki.version') }}"
MEDIAWIKI_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.mediawiki.image') }}"
MEDIAWIKI_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.mediawiki.name') }}"
MEDIAWIKI_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}"
MEDIAWIKI_SETTINGS_HOST_PATH: "{{ [docker_compose.directories.volumes, 'LocalSettings.php'] | path_join }}"
MEDIAWIKI_SETTINGS_DOCK_PATH: "/var/www/html/LocalSettings.php"
MEDIAWIKI_USER: "www-data"
# User
MEDIAWIKI_ADMINISTRATOR_NAME: "{{ users.administrator.username }}"
MEDIAWIKI_ADMINISTRATOR_PASSWORD: "{{ users.administrator.password }}"
MEDIAWIKI_ADMINISTRATOR_EMAIL: "{{ users.administrator.email }}"
MEDIAWIKI_ADMINISTRATOR_EMAIL: "{{ users.administrator.email }}"
# OIDC
MEDIAWIKI_OIDC_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc') }}"
MEDIAWIKI_OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}"
MEDIAWIKI_OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}"
MEDIAWIKI_OIDC_ISSUER: "{{ OIDC.CLIENT.ISSUER_URL }}"
MEDIAWIKI_OIDC_BUTTON_TEXT: "{{ OIDC.BUTTON_TEXT }}"
# Extensions
MEDIAWIKI_EXT_BRANCH: "REL1_44" # passend zu MediaWiki 1.44
MEDIAWIKI_EXT_CFG_BASE: "{{ MEDIAWIKI_CONFIG_DIR }}/mwext/{{ MEDIAWIKI_EXT_BRANCH }}"
MEDIAWIKI_EXT_LIST:
- name: "PluggableAuth"
url: "https://codeload.github.com/wikimedia/mediawiki-extensions-PluggableAuth/tar.gz/refs/heads/{{ MEDIAWIKI_EXT_BRANCH }}"
- name: "OpenIDConnect"
url: "https://codeload.github.com/wikimedia/mediawiki-extensions-OpenIDConnect/tar.gz/refs/heads/{{ MEDIAWIKI_EXT_BRANCH }}"

View File

@@ -1,29 +0,0 @@
# MyBB
## Description
Transform your community engagement with MyBB, a feature-rich forum solution that combines modern design with robust functionality. MyBB fosters dynamic discussions, intuitive moderation, and an energetic user interface that brings people together, creating a vibrant online community.
## Overview
This role deploys MyBB using Docker, leveraging Docker Compose to manage both the MyBB application and its underlying MariaDB database. It also integrates with an Nginx reverse proxy for secure, flexible multi-domain access. Additionally, the role supports the manual installation of MyBB plugins for added extensibility. For detailed installation and configuration instructions, please refer to the [Installation.md](./Installation.md) file.
## Features
- **Multi-Domain Support:** Configure MyBB for multi-domain installations by setting the correct cookie domain and board URL.
- **Plugin Extensibility:** Manually install and activate plugins to extend forum functionality and tailor the user experience.
- **Robust Deployment:** Achieve reliable and scalable deployment of your forum via Docker Compose, ensuring seamless service continuity.
- **Secure and Flexible Access:** Integrate with an Nginx reverse proxy to securely manage traffic and domain access.
## Further Resources
- [MyBB Docker Repository](https://github.com/mybb/docker)
- [MyBB Official Website](https://mybb.com/)
## Credits
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [veen.world](https://www.veen.world).
Part of the [Infinito.Nexus Project](https://s.infinito.nexus/code)
Licensed under [Infinito.Nexus NonCommercial License](https://s.infinito.nexus/license).

View File

@@ -1,2 +0,0 @@
# Todo
- Optimize the role for the new role structure. But propably discourse is sufficient and this role isn't needed anymore.

View File

@@ -1,21 +0,0 @@
features:
matomo: true
css: true
desktop: true
central_database: true
logout: true
docker:
services:
database:
enabled: true
mybb:
image: "mybb/mybb"
version: "latest"
name: "mybb"
volumes:
data: "mybb_data"
server:
domains:
canonical:
- mybb.{{ PRIMARY_DOMAIN }}

View File

@@ -1,24 +0,0 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Transform your community engagement with MyBB, a feature-rich forum solution that combines modern design with robust functionality. Enjoy dynamic discussions, intuitive moderation, and a vibrant user interface that brings your community together."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
galaxy_tags:
- mybb
- docker
- forum
- community
- cms
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://docs.infinito.nexus"
logo:
class: "fa-solid fa-comments"
run_after:
- web-app-matomo
- web-app-keycloak
- web-app-mailu

View File

@@ -1,8 +0,0 @@
credentials:
secret_pin:
description: >
Optional secret PIN required for accessing the MyBB Admin Control Panel.
If set, administrators must enter this PIN in addition to their username
and password when logging into the ACP.
algorithm: "sha256"
validation: "^[a-f0-9]{64}$"

View File

@@ -1,25 +0,0 @@
- name: "load docker, db and proxy for {{ application_id }}"
include_role:
name: sys-stk-full-stateful
vars:
docker_compose_flush_handlers: false
proxy_extra_configuration: "sub_filter '{{MYBB_DOMAIN}}' '{{ MYBB_DOMAIN }}';"
- name: "create {{ MYBB_NGINX_CONF_DIR_HOST }} and parent directories"
file:
path: "{{ MYBB_NGINX_CONF_DIR_HOST }}"
state: directory
mode: "0755"
recurse: yes
- name: "Deploy {{ MYBB_NGINX_DEFAULT_CONF_HOST }}"
template:
src: "default.conf.j2"
dest: "{{ MYBB_NGINX_DEFAULT_CONF_HOST }}"
notify: docker compose up
- name: "Deploy {{ MYBB_CONFIG_HOST_PATH }}"
template:
src: "config.php.j2"
dest: "{{ MYBB_CONFIG_HOST_PATH }}"
mode: '0644'

View File

@@ -1,13 +0,0 @@
<?php
$config = array();
$config['database']['type'] = 'mysqli';
$config['database']['database'] = '{{ database_name }}';
$config['database']['table_prefix'] = 'mybb_';
$config['database']['hostname'] = '{{ database_host }}';
$config['database']['port'] = '{{ database_port }}';
$config['database']['username'] = '{{ database_username }}';
$config['database']['password'] = '{{ database_password }}';
$config['database']['encoding'] = 'utf8mb4';
$config['super_admins'] = '{{ MYBB_ADMIN_USER_ID }}';
$config['secret_pin'] = '{{ MYBB_SECRET_PIN }}';
$config['admin_dir'] = 'admin';

View File

@@ -1,44 +0,0 @@
upstream mybb {
server application:9000 weight=5;
}
server {
listen 80;
error_log /proc/self/fd/2 {% if MODE_DEBUG | bool %}debug{% else %}warn{% endif %};
root /var/www/html;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ inc/ {
internal;
}
location ~ ^/(images|cache|jscripts|uploads)/ {
access_log off;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass mybb;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# proxy timeouts
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
# fastcgi timeouts
fastcgi_read_timeout 300s;
fastcgi_send_timeout 300s;
fastcgi_connect_timeout 300s;
}
}

View File

@@ -1,29 +0,0 @@
{% include 'roles/docker-compose/templates/base.yml.j2' %}
application:
{% include 'roles/docker-container/templates/base.yml.j2' %}
image: {{ MYBB_IMAGE }}:{{ MYBB_VERSION }}
container_name: {{ MYBB_CONTAINER }}
restart: {{ DOCKER_RESTART_POLICY }}
volumes:
- data:/var/www/html
{% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %}
{% include 'roles/docker-container/templates/networks.yml.j2' %}
server:
{% include 'roles/docker-container/templates/base.yml.j2' %}
image: nginx:mainline
restart: {{ DOCKER_RESTART_POLICY }}
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
volumes:
- "{{ MYBB_NGINX_CONF_DIR_HOST }}:{{ MYBB_NGINX_CONF_DIR_DOCK }}:ro"
- "{{ MYBB_CONFIG_HOST_PATH }}:{{ MYBB_CONFIG_DOCK_PATH }}:ro"
- "data:/var/www/html:ro"
{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %}
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
data:
name: {{ MYBB_VOLUME }}
{% include 'roles/docker-compose/templates/networks.yml.j2' %}

View File

@@ -1,25 +0,0 @@
---
# General
application_id: "web-app-mybb"
database_type: "mariadb"
container_port: 80
# MyBB
## Credentials
MYBB_SECRET_PIN: "{{ applications | get_app_conf(application_id, 'credentials.secret_pin') }}"
## Server
MYBB_NGINX_CONF_DIR_HOST: "{{ docker_compose.directories.instance }}conf.d/"
MYBB_NGINX_CONF_DIR_DOCK: "{{ NGINX.DIRECTORIES.CONFIGURATION }}"
MYBB_NGINX_DEFAULT_CONF_HOST: "{{ MYBB_NGINX_CONF_DIR_HOST }}default.conf"
## Application
MYBB_ADMIN_USER_ID: 1
MYBB_DOMAIN: "{{ domains | get_domain(application_id) }}"
MYBB_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.mybb.version') }}"
MYBB_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.mybb.image') }}"
MYBB_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.mybb.name') }}"
MYBB_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}"
MYBB_CONFIG_HOST_PATH: "{{ [docker_compose.directories.instance, 'config.php'] | path_join }}"
MYBB_CONFIG_DOCK_PATH: "/var/www/html/inc/config.php"