1 Commits

Author SHA1 Message Date
2f46b99e4e XWiki: add diagnostic and modern AuthService handling
- Added 05_set_authservice.yml to set XWikiPreferences.authenticationService
  to modern component hints (standard, oidc, ldap).
- Added _auth_diag.yml to introspect registered AuthService components and
  verify the active preference.
- Updated docker-compose.yml.j2 to use -Dxwiki.authentication.authservice
  instead of deprecated authclass syntax.
- Temporarily included AuthDiag task in 01_core.yml for runtime verification.

Context: https://chatgpt.com/share/69005d88-6bf8-800f-af41-73b0e5dc9c13
2025-10-28 07:07:42 +01:00
42 changed files with 174 additions and 991 deletions

View File

@@ -114,8 +114,6 @@ defaults_networks:
subnet: 192.168.104.48/28
web-app-mini-qr:
subnet: 192.168.104.64/28
web-app-shopware:
subnet: 192.168.104.80/28
# /24 Networks / 254 Usable Clients
web-app-bigbluebutton:

View File

@@ -81,7 +81,6 @@ ports:
web-app-minio_api: 8057
web-app-minio_console: 8058
web-app-mini-qr: 8059
web-app-shopware: 8060
web-app-bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port
public:
# The following ports should be changed to 22 on the subdomain via stream mapping

View File

@@ -1,31 +0,0 @@
{# ------------------------------------------------------------------------------
Healthcheck: HTTP Local
------------------------------------------------------------------------------
This template defines a generic HTTP healthcheck for containers exposing
a web service on a local port (e.g., Nginx, Apache, PHP-FPM, Shopware, etc.).
It uses `wget` or `curl` (as fallback) to test if the container responds on
http://127.0.0.1:{{ container_port }}/. If the request succeeds, Docker marks
the container as "healthy"; otherwise, as "unhealthy".
Parameters:
- container_port: The internal port the service listens on.
Timing:
- interval: 30s → Check every 30 seconds
- timeout: 5s → Each check must complete within 5 seconds
- retries: 5 → Mark unhealthy after 5 consecutive failures
- start_period: 20s → Grace period before health checks begin
Usage:
{% filter indent(4) %}
{% include 'roles/docker-container/templates/healthcheck/http.yml.j2' %}
{% endfilter %}
------------------------------------------------------------------------------
#}
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:{{ container_port }}/ >/dev/null || curl -fsS http://127.0.0.1:{{ container_port }}/ >/dev/null"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s

View File

@@ -6,7 +6,7 @@ entity_name: "{{ application_id | get_entity_name }
docker_compose_flush_handlers: true
# Docker Compose
database_type: "{{ entity_name }}"
database_type: "{{ application_id | get_entity_name }}"
## Postgres
POSTGRES_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}"

View File

@@ -4,13 +4,11 @@ __metaclass__ = type
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
"""
Group the given cards into categorized and uncategorized lists
based on the tags from menu_categories.
Categories are sorted alphabetically before returning.
"""
if len(terms) < 2:
raise AnsibleError("Missing required arguments")
@@ -21,7 +19,6 @@ class LookupModule(LookupBase):
categorized = {}
uncategorized = []
# Categorize cards
for card in cards:
found = False
for category, data in menu_categories.items():
@@ -32,14 +29,10 @@ class LookupModule(LookupBase):
if not found:
uncategorized.append(card)
# Sort categories alphabetically
sorted_categorized = {
k: categorized[k] for k in sorted(categorized.keys(), key=str.lower)
}
return [
{
'categorized': sorted_categorized,
'categorized': categorized,
'uncategorized': uncategorized,
}
]

View File

@@ -25,6 +25,7 @@ portfolio_menu_categories:
- ollama
- openwebui
- flowise
- minio
- qdrant
- litellm
@@ -101,12 +102,14 @@ portfolio_menu_categories:
- fusiondirectory
- user-management
Customer Relationship:
description: "Customer Relationship Management (CRM) software for managing customer relationships, sales pipelines, marketing, and support activities."
Customer Relationship Management:
description: "Tools for managing customer relationships, sales pipelines, marketing, and support activities."
icon: "fa-solid fa-address-book"
tags:
- crm
- customer
- relationship
- sales
- marketing
- support
- espocrm
@@ -219,7 +222,7 @@ portfolio_menu_categories:
- snipe-it
Content Management:
description: "Content Management Systems (CMS) and web publishing platforms"
description: "CMS and web publishing platforms"
icon: "fa-solid fa-file-alt"
tags:
- cms
@@ -228,27 +231,4 @@ portfolio_menu_categories:
- website
- joomla
- wordpress
- blog
Commerce:
description: "Platforms for building and managing online shops, product catalogs, and digital sales channels — including payment, inventory, and customer features."
icon: "fa-solid fa-cart-shopping"
tags:
- commerce
- ecommerce
- shopware
- shop
- sales
- store
- magento
- pretix
Storage:
description: "High-performance, self-hosted storage solutions for managing, scaling, and accessing unstructured data — including object storage compatible with Amazon S3 APIs."
icon: "fa-solid fa-database"
tags:
- storage
- object-storage
- s3
- minio
- datasets
- blog

View File

@@ -27,7 +27,3 @@ server:
domains:
canonical:
- lab.git.{{ PRIMARY_DOMAIN }}
csp:
flags:
script-src-elem:
unsafe-inline: true

View File

@@ -11,7 +11,7 @@
# (Optional) specifically wait for the CLI installer script
- name: "Check for CLI installer"
command:
argv: [ docker, exec, "{{ JOOMLA_CONTAINER }}", test, -f, "{{ JOOMLA_INSTALLER_CLI_FILE }}" ]
argv: [ docker, exec, "{{ JOOMLA_CONTAINER }}", test, -f, /var/www/html/installation/joomla.php ]
register: has_installer
changed_when: false
failed_when: false
@@ -32,7 +32,7 @@
- exec
- "{{ JOOMLA_CONTAINER }}"
- php
- "{{ JOOMLA_INSTALLER_CLI_FILE }}"
- /var/www/html/installation/joomla.php
- install
- "--db-type={{ JOOMLA_DB_CONNECTOR }}"
- "--db-host={{ database_host }}"

View File

@@ -1,18 +0,0 @@
---
# Reset Joomla admin password via CLI (inside the container)
- name: "Reset Joomla admin password (non-interactive CLI)"
command:
argv:
- docker
- exec
- "{{ JOOMLA_CONTAINER }}"
- php
- "{{ JOOMLA_CLI_FILE }}"
- user:reset-password
- "--username"
- "{{ JOOMLA_USER_NAME }}"
- "--password"
- "{{ JOOMLA_USER_PASSWORD }}"
register: j_password_reset
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
changed_when: j_password_reset.rc == 0

View File

@@ -24,7 +24,3 @@
- name: Include assert routines
include_tasks: "04_assert.yml"
when: MODE_ASSERT | bool
- name: Reset Admin Password
include_tasks: 05_reset_admin_password.yml

View File

@@ -13,8 +13,6 @@ JOOMLA_DOMAINS: "{{ applications | get_app_conf(application_id
JOOMLA_SITE_NAME: "{{ SOFTWARE_NAME }} Joomla - CMS"
JOOMLA_DB_CONNECTOR: "{{ 'pgsql' if database_type == 'postgres' else 'mysqli' }}"
JOOMLA_CONFIG_FILE: "/var/www/html/configuration.php"
JOOMLA_INSTALLER_CLI_FILE: "/var/www/html/installation/joomla.php"
JOOMLA_CLI_FILE: "/var/www/html/cli/joomla.php"
# User
JOOMLA_USER_NAME: "{{ users.administrator.username }}"

View File

@@ -13,16 +13,6 @@ server:
aliases: []
status_codes:
default: 404
csp:
flags:
script-src-elem:
unsafe-inline: true
whitelist:
script-src-elem:
- "https://www.hcaptcha.com"
- "https://js.hcaptcha.com"
frame-src:
- "https://newassets.hcaptcha.com/"
docker:
services:
database:

View File

@@ -9,9 +9,6 @@ server:
script-src-attr:
unsafe-eval: true
whitelist:
script-src-elem:
- "https://www.hcaptcha.com"
- "https://js.hcaptcha.com"
font-src:
- "data:"
connect-src:
@@ -22,7 +19,6 @@ server:
frame-src:
- "{{ WEBSOCKET_PROTOCOL }}://collabora.{{ PRIMARY_DOMAIN }}"
- "{{ WEB_PROTOCOL }}://collabora.{{ PRIMARY_DOMAIN }}"
- "https://newassets.hcaptcha.com/"
worker-src:
- "blob:"
domains:

View File

@@ -7,9 +7,6 @@
command: "{{ NEXTCLOUD_DOCKER_EXEC_OCC }} maintenance:repair --include-expensive"
register: occ_repair
changed_when: "'No repairs needed' not in occ_repair.stdout"
retries: 3
delay: 10
until: occ_repair.rc == 0
- name: Nextcloud | App update (retry once)
command: "{{ NEXTCLOUD_DOCKER_EXEC_OCC }} app:update --all"

View File

@@ -16,13 +16,6 @@
- name: Flush all handlers immediately so that occ can be used
meta: flush_handlers
- name: Wait until Redis is ready (PONG)
command: "docker exec {{ NEXTCLOUD_REDIS_CONTAINER }} redis-cli ping"
register: redis_ping
retries: 60
delay: 2
until: (redis_ping.stdout | default('')) is search('PONG')
- name: Update\Upgrade Nextcloud
include_tasks: 03_upgrade.yml
when: MODE_UPDATE | bool

View File

@@ -141,7 +141,4 @@ NEXTCLOUD_DOCKER_USER: "www-data" # Name of the www-data user
## Execution
NEXTCLOUD_INTERNAL_OCC_COMMAND: "{{ [ NEXTCLOUD_DOCKER_WORK_DIRECTORY, 'occ'] | path_join }}"
NEXTCLOUD_DOCKER_EXEC: "docker exec -u {{ NEXTCLOUD_DOCKER_USER }} {{ NEXTCLOUD_CONTAINER }}" # General execute composition
NEXTCLOUD_DOCKER_EXEC_OCC: "{{ NEXTCLOUD_DOCKER_EXEC }} {{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" # Execute docker occ command
## Redis
NEXTCLOUD_REDIS_CONTAINER: "{{ entity_name }}-redis"
NEXTCLOUD_DOCKER_EXEC_OCC: "{{ NEXTCLOUD_DOCKER_EXEC }} {{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}" # Execute docker occ command

View File

@@ -1,34 +0,0 @@
# Shopware
## Description
Empower your e-commerce vision with **Shopware 6**, a modern, flexible, and open-source commerce platform built on **Symfony and Vue.js**. Designed for growth and innovation, it enables seamless integration, outstanding customer experiences, and complete control over your digital business. Build, scale, and sell with confidence.
## Overview
This role deploys **Shopware 6** using **Docker**. It automates installation, migration, and configuration of your storefront, integrating with a central **MariaDB** database.
Optional components like **Redis** and **OpenSearch** enhance performance and search capabilities, while **OIDC** and **LDAP** support integration with centralized identity systems such as **Keycloak**.
With automated setup, update handling, variable management, and plugin-based authentication, this role simplifies the deployment and maintenance of your Shopware instance.
## Features
* **Modern and Scalable:** A robust Symfony-based framework optimized for commerce innovation.
* **Automated Setup & Maintenance:** Installs, migrates, and configures Shopware automatically.
* **Extensible Architecture:** Optional Redis, OpenSearch, and plugin-based IAM integrations.
* **Centralized Database Access:** Connects seamlessly to the shared MariaDB service.
* **Integrated Configuration:** Environment and Docker Compose variables managed automatically.
## Further Resources
* [Shopware Official Website](https://www.shopware.com/en/)
* [Shopware Developer Documentation](https://developer.shopware.com/)
* [Shopware Store (Plugins)](https://store.shopware.com/en/)
## 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,3 +0,0 @@
# to-dos
- Implement OIDC
- Implement LDAP

View File

@@ -1,80 +0,0 @@
title: "{{ SOFTWARE_NAME }} Shop"
features:
central_database: true
redis: true
ldap: false # Not implemented yet
oidc: false # Not implemented yet
logout: true
desktop: true
css: true
server:
csp:
flags:
script-src-elem:
unsafe-inline: true
unsafe-eval: true
whitelist:
font-src:
- "data:"
domains:
aliases: []
canonical:
- shop.{{ PRIMARY_DOMAIN }}
docker:
services:
database:
enabled: true
init:
name: software-init
cpus: 1.0
mem_reservation: 1g
mem_limit: 2g
# Base PHP image used by all app services
shopware:
image: "ghcr.io/shopware/docker-base"
version: "8.3"
web:
name: "shopware-web"
port: 8000
cpus: 1.0
mem_reservation: 1g
mem_limit: 2g
worker:
name: "shopware-worker"
entrypoint: [ "php", "bin/console", "messenger:consume", "async", "low_priority", "--time-limit=300", "--memory-limit=512M" ]
replicas: 3
cpus: 1.0
mem_reservation: 1g
mem_limit: 2g
scheduler:
name: "shopware-scheduler"
entrypoint: [ "php", "bin/console", "scheduled-task:run" ]
cpus: 0.5
mem_reservation: 512m
mem_limit: 1g
redis:
enabled: true
image: "redis"
version: "7-alpine"
cpus: 0.25
mem_reservation: 256m
mem_limit: 512m
opensearch:
enabled: true
image: "opensearchproject/opensearch"
version: "2.12.0"
name: "shopware-opensearch"
cpus: 1.0
mem_reservation: 2g
mem_limit: 4g
volumes:
data: "shopware_data"

View File

@@ -1,140 +0,0 @@
#!/bin/sh
# Shopware initialization script (POSIX sh)
# - Root phase: fix volumes & permissions, then switch to www-data
# - First run: perform system:install
# - Every run: run DB migrations + rebuild cache + compile assets & themes
# - Verifies admin bundles exist, otherwise exits with error
set -eu
APP_ROOT="/var/www/html"
MARKER="$APP_ROOT/.infinito/installed"
LOG_PREFIX="[INIT]"
PHP_BIN="php"
log() { printf "%s %s\n" "$LOG_PREFIX" "$1"; }
fail() { printf "%s [ERROR] %s\n" "$LOG_PREFIX" "$1" >&2; exit 1; }
# ---------------------------
# 0) Root phase (if running as root)
# ---------------------------
if [ "$(id -u)" -eq 0 ]; then
# Prepare required folders and shared volumes
mkdir -p "$APP_ROOT/.infinito" \
"$APP_ROOT/public/bundles" \
"$APP_ROOT/public/media" \
"$APP_ROOT/public/theme" \
"$APP_ROOT/public/thumbnail" \
"$APP_ROOT/public/sitemap" \
"$APP_ROOT/var"
log "Fixing permissions on shared volumes..."
chown -R www-data:www-data "$APP_ROOT/public" "$APP_ROOT/var" || true
chmod -R 775 "$APP_ROOT/public" "$APP_ROOT/var" || true
# Switch to www-data for all subsequent operations
exec su -s /bin/sh www-data "$0" "$@"
fi
# From here on: running as www-data
cd "$APP_ROOT" || fail "Cannot cd to $APP_ROOT"
# Optional environment hints
APP_ENV_STR=$($PHP_BIN -r 'echo getenv("APP_ENV") ?: "";' 2>/dev/null || true)
APP_URL_STR=$($PHP_BIN -r 'echo getenv("APP_URL") ?: "";' 2>/dev/null || true)
[ -n "$APP_ENV_STR" ] || log "APP_ENV not set (using defaults)"
[ -n "$APP_URL_STR" ] || log "APP_URL not set (reverse proxy must set headers)"
# ---------------------------
# 1) Database reachability check (PDO)
# ---------------------------
log "Checking database via PDO..."
$PHP_BIN -r '
$url = getenv("DATABASE_URL");
if (!$url) { fwrite(STDERR, "DATABASE_URL not set\n"); exit(1); }
$p = parse_url($url);
if (!$p || !isset($p["scheme"])) { fwrite(STDERR, "Invalid DATABASE_URL\n"); exit(1); }
$host = $p["host"] ?? "localhost";
$port = $p["port"] ?? 3306;
$db = ltrim($p["path"] ?? "", "/");
$user = $p["user"] ?? "";
$pass = $p["pass"] ?? "";
$dsn = "mysql:host=".$host.";port=".$port.";dbname=".$db.";charset=utf8mb4";
$retries = 60;
while ($retries-- > 0) {
try { new PDO($dsn, $user, $pass, [PDO::ATTR_TIMEOUT => 3]); exit(0); }
catch (Exception $e) { sleep(2); }
}
fwrite(STDERR, "DB not reachable\n"); exit(1);
' || fail "Database not reachable"
# ---------------------------
# 2) First-time install detection
# ---------------------------
FIRST_INSTALL=0
if [ ! -f "$MARKER" ]; then
log "Checking if database is empty..."
if $PHP_BIN -r '
$url = getenv("DATABASE_URL");
$p = parse_url($url);
$db = ltrim($p["path"] ?? "", "/");
$dsn = "mysql:host=".($p["host"]??"localhost").";port=".($p["port"]??3306).";dbname=".$db.";charset=utf8mb4";
$pdo = new PDO($dsn, $p["user"] ?? "", $p["pass"] ?? "");
$q = $pdo->query("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=".$pdo->quote($db));
$cnt = (int)$q->fetchColumn();
exit($cnt === 0 ? 0 : 100);
'; then
FIRST_INSTALL=1
else
ST=$?
if [ "$ST" -eq 100 ]; then
log "Database not empty → skipping install"
else
fail "Database check failed (exit code $ST)"
fi
fi
fi
if [ "$FIRST_INSTALL" -eq 1 ]; then
log "Performing first-time Shopware installation..."
$PHP_BIN -d memory_limit=1024M bin/console system:install --basic-setup --create-database
mkdir -p "$(dirname "$MARKER")"
: > "$MARKER"
fi
# ---------------------------
# 3) Always run migrations
# ---------------------------
log "Running database migrations..."
$PHP_BIN -d memory_limit=1024M bin/console database:migrate --all
$PHP_BIN -d memory_limit=1024M bin/console database:migrate-destructive --all
# ---------------------------
# 4) Always rebuild caches, bundles, and themes
# ---------------------------
log "Rebuilding caches and assets..."
$PHP_BIN bin/console cache:clear
$PHP_BIN bin/console bundle:dump
# Use --copy if symlinks cause issues
$PHP_BIN bin/console assets:install --no-interaction --force
$PHP_BIN bin/console theme:refresh
$PHP_BIN bin/console theme:compile
# Best-effort: not critical if it fails
$PHP_BIN bin/console dal:refresh:index || log "dal:refresh:index failed (non-critical)"
# ---------------------------
# 5) Verify admin bundles
# ---------------------------
if [ ! -d "public/bundles/administration" ]; then
fail "Missing directory public/bundles/administration (asset build failed)"
fi
if ! ls public/bundles/administration/* >/dev/null 2>&1; then
fail "No files found in public/bundles/administration (asset build failed)"
fi
# ---------------------------
# 6) Show version info
# ---------------------------
$PHP_BIN bin/console system:version 2>/dev/null || log "system:version not available"
log "Initialization complete."

View File

@@ -1,22 +0,0 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Shopware is a modern open-source eCommerce platform built on PHP and Symfony. It enables businesses to create scalable online stores with flexible product management, intuitive administration, customizable storefronts, and powerful APIs for headless and omnichannel commerce."
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:
- shopware
- ecommerce
repository: https://s.infinito.nexus/code
issue_tracker_url: https://s.infinito.nexus/issues
documentation: "https://docs.infinito.nexus/"
logo:
class: "fa-solid fa-cart-shopping"
run_after:
- web-app-keycloak
- web-app-mailu
dependencies: []

View File

@@ -1,2 +0,0 @@
# Minimal schema placeholder (extend with your own config contract if desired)
credentials: {}

View File

@@ -1,38 +0,0 @@
- name: "Rename default Shopware admin user to {{ users.administrator.username }}"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
set -e
cd {{ SHOPWARE_ROOT }}
old_user="admin"
new_user="{{ users.administrator.username }}"
if php bin/console user:list | grep -q "^$old_user "; then
echo "[INFO] Renaming Shopware user: $old_user -> $new_user"
php bin/console user:update "$old_user" --username="$new_user" || true
else
echo "[INFO] No user named $old_user found (already renamed or custom setup)"
fi
'
args:
chdir: "{{ docker_compose.directories.instance }}"
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Ensure Shopware admin exists and has the desired password"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
set -e
cd {{ SHOPWARE_ROOT }}
php bin/console user:create "{{ users.administrator.username }}" \
--admin \
--password="{{ users.administrator.password }}" \
--firstName="{{ users.administrator.username }}" \
--lastName="{{ PRIMARY_DOMAIN | lower }}" \
--email="{{ users.administrator.email }}" || true
php bin/console user:change-password "{{ users.administrator.username }}" \
--password="{{ users.administrator.password }}" || true
php bin/console user:update "{{ users.administrator.username }}" \
--email="{{ users.administrator.email }}" 2>/dev/null || true
'
args:
chdir: "{{ docker_compose.directories.instance }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@@ -1,7 +0,0 @@
- name: Install & configure OIDC plugin (if enabled)
include_tasks: setup/oidc.yml
when: applications | get_app_conf(application_id, 'features.oidc')
- name: Install & configure LDAP plugin (if enabled)
include_tasks: setup/ldap.yml
when: applications | get_app_conf(application_id, 'features.ldap')

View File

@@ -1,7 +0,0 @@
- name: Remove OIDC plugin if disabled
include_tasks: cleanup/oidc.yml
when: not (applications | get_app_conf(application_id, 'features.oidc'))
- name: Remove LDAP plugin if disabled
include_tasks: cleanup/ldap.yml
when: not (applications | get_app_conf(application_id, 'features.ldap'))

View File

@@ -1,10 +0,0 @@
- name: "Deactivate/uninstall LDAP plugin if present"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
cd {{ SHOPWARE_ROOT }}
php bin/console plugin:deactivate INFX_LDAP_PLUGIN || true
php bin/console plugin:uninstall INFX_LDAP_PLUGIN --keep-user-data || true
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"

View File

@@ -1,10 +0,0 @@
- name: "Deactivate/uninstall OIDC plugin if present"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
cd {{ SHOPWARE_ROOT }}
php bin/console plugin:deactivate INFX_OIDC_PLUGIN || true
php bin/console plugin:uninstall INFX_OIDC_PLUGIN --keep-user-data || true
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"

View File

@@ -1,35 +0,0 @@
---
- name: "Load docker, DB and proxy for {{ application_id }}"
include_role:
name: sys-stk-full-stateful
vars:
docker_compose_flush_handlers: false
- name: "Deploy {{ SHOPWARE_INIT_HOST }}"
copy:
src: init.sh
dest: "{{ SHOPWARE_INIT_HOST }}"
mode: "0755"
notify:
- docker compose up
- docker compose build
- name: "Flush docker compose handlers"
meta: flush_handlers
- name: Wait for Shopware HTTP endpoint
wait_for:
host: "127.0.0.1"
port: "{{ ports.localhost.http[application_id] }}"
delay: 5
timeout: 300
- name: "Ensure admin user exists with correct password"
include_tasks: 01_admin.yml
#- name: Execute setup routines (OIDC/LDAP)
# include_tasks: 02_setup.yml
#
#- name: Execute cleanup routines
# include_tasks: 03_cleanup.yml
# when: MODE_CLEANUP

View File

@@ -1,27 +0,0 @@
# Replace INFX_LDAP_PLUGIN with the actual plugin name you use
- name: "Install LDAP admin plugin & activate"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
set -e
cd {{ SHOPWARE_ROOT }}
php bin/console plugin:refresh
php bin/console plugin:install --activate INFX_LDAP_PLUGIN || true
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"
- name: "Configure LDAP connection"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
set -e
cd {{ SHOPWARE_ROOT }}
php bin/console system:config:set "InfxLdap.config.host" "{{ LDAP.SERVER.DOMAIN }}"
php bin/console system:config:set "InfxLdap.config.port" "{{ LDAP.SERVER.PORT }}"
php bin/console system:config:set "InfxLdap.config.bindDn" "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
php bin/console system:config:set "InfxLdap.config.password" "{{ LDAP.BIND_CREDENTIAL }}"
php bin/console system:config:set "InfxLdap.config.userBase" "{{ LDAP.DN.OU.USERS }}"
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"

View File

@@ -1,26 +0,0 @@
# Replace INFX_OIDC_PLUGIN with the actual plugin name (Composer or local)
- name: "Install OIDC plugin & activate"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
set -e
cd {{ SHOPWARE_ROOT }}
php bin/console plugin:refresh
php bin/console plugin:install --activate INFX_OIDC_PLUGIN || true
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"
- name: "Configure OIDC via system:config"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} sh -lc '
set -e
cd {{ SHOPWARE_ROOT }}
php bin/console system:config:set "InfxOidc.config.clientId" "{{ OIDC.CLIENT.ID }}"
php bin/console system:config:set "InfxOidc.config.clientSecret" "{{ OIDC.CLIENT.SECRET }}"
php bin/console system:config:set "InfxOidc.config.discoveryUrl" "{{ OIDC.CLIENT.DISCOVERY_DOCUMENT }}"
php bin/console system:config:set "InfxOidc.config.scopes" "openid profile email"
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"

View File

@@ -1,92 +0,0 @@
# ------------------------------------------------------------------------------
# Shopware Application Image (Alpine-compatible)
# ------------------------------------------------------------------------------
# - Stage 1 (builder): use Composer to fetch Shopware while ignoring build-time
# PHP extensions (we'll install them in the runtime image).
# - Stage 2 (runtime): install required PHP extensions and copy the app + init.sh
# ------------------------------------------------------------------------------
############################
# Stage 1: Builder
############################
FROM composer:2.7 AS builder
ENV COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_NO_INTERACTION=1 \
COMPOSER_PROCESS_TIMEOUT=900
WORKDIR /app
ARG SHOPWARE_PROD_VERSION=shopware/production:6.7.3.1
# 1) Scaffold project without installing dependencies
RUN set -eux; \
composer create-project "${SHOPWARE_PROD_VERSION}" /app --no-install
# 2) Install dependencies (ignoring build-time extension checks) + add Redis transport
RUN set -eux; \
composer install \
--no-dev \
--optimize-autoloader \
--no-progress \
--no-scripts \
--ignore-platform-req=ext-gd \
--ignore-platform-req=ext-intl \
--ignore-platform-req=ext-pdo_mysql; \
composer require symfony/redis-messenger:^6.4 \
-W \
--no-scripts \
--no-progress \
--update-no-dev \
--ignore-platform-req=ext-gd \
--ignore-platform-req=ext-intl \
--ignore-platform-req=ext-pdo_mysql \
--ignore-platform-req=ext-redis
############################
# Stage 2: Runtime
############################
FROM ghcr.io/shopware/docker-base:8.3
WORKDIR /var/www/html
# Install required PHP extensions in the Alpine-based runtime
# (try php83-*, fall back to php82-*, then to generic)
USER root
RUN set -eux; \
apk add --no-cache php83-gd || apk add --no-cache php82-gd || apk add --no-cache php-gd || true; \
apk add --no-cache php83-intl || apk add --no-cache php82-intl || apk add --no-cache php-intl || true; \
apk add --no-cache php83-pdo_mysql || apk add --no-cache php82-pdo_mysql || apk add --no-cache php-pdo_mysql || true; \
apk add --no-cache php83-redis || apk add --no-cache php82-redis || apk add --no-cache php-redis || true
# Copy built application from the builder
COPY --chown=www-data:www-data --from=builder /app /var/www/html
# Optional: snapshot of pristine app to seed an empty volume (used by init container)
RUN mkdir -p /usr/src/shopware \
&& cp -a /var/www/html/. /usr/src/shopware/. \
&& chown -R www-data:www-data /var/www/html /usr/src/shopware
# Ensure writable directories exist with correct ownership
RUN set -eux; \
mkdir -p \
/var/www/html/files \
/var/www/html/var \
/var/www/html/public/media \
/var/www/html/public/thumbnail \
/var/www/html/public/sitemap \
/var/www/html/public/theme; \
chown -R www-data:www-data /var/www/html
# Add trusted proxies wiring (Symfony reads env TRUSTED_PROXIES)
RUN set -eux; \
mkdir -p /var/www/html/config/packages; \
if [ ! -f /var/www/html/config/packages/framework.yaml ]; then \
printf "framework:\n trusted_proxies: '%%env(TRUSTED_PROXIES)%%'\n" > /var/www/html/config/packages/framework.yaml; \
fi
# Drop back to the app user
USER www-data
# Expose internal port & add a lightweight healthcheck
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --retries=5 --start-period=20s \
CMD php -r '$s=@fsockopen("127.0.0.1", 8000, $e, $t, 3); if(!$s) exit(1); fclose($s);'

View File

@@ -1,121 +0,0 @@
x-environment: &shopware
image: "{{ SHOPWARE_CUSTOM_IMAGE }}"
volumes:
- files:/var/www/html/files
- theme:/var/www/html/public/theme
- media:/var/www/html/public/media
- thumbnail:/var/www/html/public/thumbnail
- sitemap:/var/www/html/public/sitemap
- "{{ SHOPWARE_INIT_HOST }}:{{ SHOPWARE_INIT_DOCKER }}:ro"
- bundles:/var/www/html/public/bundles
working_dir: {{ SHOPWARE_ROOT }}
{% include 'roles/docker-compose/templates/base.yml.j2' %}
# -------------------------
# INIT (runs once per deployment)
# -------------------------
{% set service_name = 'init' %}
{% set docker_restart_policy = 'no' %}
{{ service_name }}:
{% include 'roles/docker-container/templates/base.yml.j2' %}
{% set docker_restart_policy = DOCKER_RESTART_POLICY %}
<<: *shopware
container_name: "{{ SHOPWARE_INIT_CONTAINER }}"
entrypoint: [ "sh", "{{ SHOPWARE_INIT_DOCKER }}" ]
user: "0:0"
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{# -------------------------
WEB (serves HTTP on 8000)
------------------------- #}
{% set service_name = 'web' %}
{% set container_port = applications | get_app_conf(application_id, 'docker.services.web.port') %}
{{ service_name }}:
{% include 'roles/docker-container/templates/base.yml.j2' %}
<<: *shopware
{{ lookup('template', 'roles/docker-container/templates/build.yml.j2') | indent(4) }}
container_name: "{{ SHOPWARE_WEB_CONTAINER }}"
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
depends_on:
init:
condition: service_completed_successfully
{% filter indent(4) %}
{% include 'roles/docker-container/templates/healthcheck/http.yml.j2' %}
{% endfilter %}
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{# -------------------------
WORKER (async queues)
------------------------- #}
{% set service_name = 'worker' %}
{{ service_name }}:
{% include 'roles/docker-container/templates/base.yml.j2' %}
<<: *shopware
container_name: "{{ SHOPWARE_WORKER_CONTAINER }}"
pull_policy: never
entrypoint: {{ SHOPWARE_WORKER_ENTRYPOINT }}
depends_on:
init:
condition: service_completed_successfully
# @todo Activate for swarm deploy
# deploy:
# replicas: {{ SHOPWARE_WORKER_REPLICAS }}
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{# -------------------------
SCHEDULER (cron-like)
------------------------- #}
{% set service_name = 'scheduler' %}
{{ service_name }}:
{% include 'roles/docker-container/templates/base.yml.j2' %}
<<: *shopware
container_name: "{{ SHOPWARE_SCHED_CONTAINER }}"
pull_policy: never
entrypoint: {{ SHOPWARE_SCHED_ENTRYPOINT }}
depends_on:
init:
condition: service_completed_successfully
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% if SHOPWARE_OPENSEARCH_ENABLED %}
{% set service_name = 'opensearch' %}
{{ service_name }}:
{% include 'roles/docker-container/templates/base.yml.j2' %}
image: "{{ SHOPWARE_OPENSEARCH_IMAGE }}:{{ SHOPWARE_OPENSEARCH_VERSION }}"
container_name: "{{ SHOPWARE_OPENSEARCH_CONTAINER }}"
environment:
- discovery.type=single-node
- plugins.security.disabled=true
- bootstrap.memory_lock=true
- OPENSEARCH_JAVA_OPTS=-Xms{{ SHOPWARE_OPENSEARCH_MEM_RESERVATION }} -Xmx{{ SHOPWARE_OPENSEARCH_MEM_RESERVATION }}
ulimits:
memlock: { soft: -1, hard: -1 }
depends_on:
init:
condition: service_completed_successfully
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% endif %}
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
data:
name: {{ SHOPWARE_VOLUME }}
files:
name: {{ entity_name }}_files
theme:
name: {{ entity_name }}_theme
media:
name: {{ entity_name }}_media
thumbnail:
name: {{ entity_name }}_thumbnail
sitemap:
name: {{ entity_name }}_sitemap
bundles:
name: {{ entity_name }}_bundles
{% include 'roles/docker-compose/templates/networks.yml.j2' %}

View File

@@ -1,33 +0,0 @@
# DOMAIN/URL
DOMAIN={{ domains | get_domain(application_id) }}
APP_URL="{{ domains | get_url(application_id, WEB_PROTOCOL) }}"
APP_DEBUG="{{ MODE_DEBUG | ternary(1, 0) }}"
# Shopware
APP_ENV={{ 'dev' if (ENVIRONMENT | lower) == 'development' else 'prod' }}
#TRUSTED_PROXIES=127.0.0.1
INSTANCE_ID={{ application_id }}
# Database
DATABASE_URL="mysql://{{ database_username }}:{{ database_password }}@{{ database_host }}:{{ database_port }}/{{ database_name }}"
# Redis (optional)
{% if SHOPWARE_REDIS_ENABLED | bool %}
REDIS_URL="redis://{{ SHOPWARE_REDIS_ADDRESS }}/0"
CACHE_URL="redis://{{ SHOPWARE_REDIS_ADDRESS }}/1"
MESSENGER_TRANSPORT_DSN="redis://{{ SHOPWARE_REDIS_ADDRESS }}/2"
{% else %}
CACHE_URL="file://cache"
{% endif %}
{% if SHOPWARE_OPENSEARCH_ENABLED %}
# Search
ELASTICSEARCH_URL="http://opensearch:9200"
OPENSEARCH_URL="http://opensearch:9200"
OPENSEARCH_HOST="opensearch"
OPENSEARCH_PORT_NUMBER="9200"
OPENSEARCH_INITIAL_ADMIN_PASSWORD="{{ users.administrator.password }}"
{% endif %}
# Mail (Mailu)
MAILER_DSN="smtps://{{ users['no-reply'].email }}:{{ users['no-reply'].mailu_token }}@{{ SYSTEM_EMAIL.HOST }}:{{ SYSTEM_EMAIL.PORT }}"

View File

@@ -1,43 +0,0 @@
# General
application_id: "web-app-shopware"
database_type: "mariadb"
entity_name: "{{ application_id | get_entity_name }}"
# Docker
container_port: "{{ applications | get_app_conf(application_id, 'docker.services.web.port') }}"
docker_compose_flush_handlers: true
# Shopware container/image vars
SHOPWARE_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.shopware.version') }}"
SHOPWARE_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.shopware.image') }}"
SHOPWARE_CUSTOM_IMAGE: "{{ SHOPWARE_IMAGE }}:{{ SHOPWARE_VERSION }}"
SHOPWARE_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}"
SHOPWARE_USER: "www-data"
SHOPWARE_ROOT: "/var/www/html"
# Split service container names
SHOPWARE_INIT_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.init.name') }}"
SHOPWARE_WEB_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.web.name') }}"
SHOPWARE_WORKER_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.worker.name') }}"
SHOPWARE_SCHED_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.scheduler.name') }}"
SHOPWARE_INIT_HOST: "{{ [ docker_compose.directories.volumes, 'init.sh' ] | path_join }}"
SHOPWARE_INIT_DOCKER: "/usr/local/bin/init.sh"
# Entrypoints & replicas
SHOPWARE_WORKER_ENTRYPOINT: "{{ applications | get_app_conf(application_id, 'docker.services.worker.entrypoint') }}"
SHOPWARE_SCHED_ENTRYPOINT: "{{ applications | get_app_conf(application_id, 'docker.services.scheduler.entrypoint') }}"
SHOPWARE_WORKER_REPLICAS: "{{ applications | get_app_conf(application_id, 'docker.services.worker.replicas') }}"
# Search/Cache
SHOPWARE_REDIS_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.redis.enabled') }}"
SHOPWARE_REDIS_ADDRESS: "redis:6379"
SHOPWARE_OPENSEARCH_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.enabled') }}"
SHOPWARE_OPENSEARCH_ENGINE: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.engine') }}"
SHOPWARE_OPENSEARCH_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.image') }}"
SHOPWARE_OPENSEARCH_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.version') }}"
SHOPWARE_OPENSEARCH_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.name') }}"
SHOPWARE_OPENSEARCH_MEM_RESERVATION: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.mem_reservation') }}"
SHOPWARE_OPENSEARCH_MEM_LIMIT: "{{ applications | get_app_conf(application_id, 'docker.services.opensearch.mem_limit') }}"
# IAM (true if either OIDC or LDAP is enabled)
SHOPWARE_IAM_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc') or applications | get_app_conf(application_id, 'features.ldap') }}"

View File

@@ -88,15 +88,16 @@
taiga:
{% set service_name = TAIGA_FRONT_SERVICE %}
{% set container_port = 80 %}
{{ service_name }}:
container_name: {{ TAIGA_CONTAINER }}-{{ service_name }}
image: "{{TAIGA_DOCKER_IMAGE_FRONTEND}}:{{ TAIGA_VERSION }}"
{% include 'roles/docker-container/templates/base.yml.j2' %}
{% filter indent(4) %}
{% include 'roles/docker-container/templates/healthcheck/http.yml.j2' %}
{% endfilter %}
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/ >/dev/null || curl -fsS http://127.0.0.1/ >/dev/null"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
{% include 'roles/docker-container/templates/networks.yml.j2' %}
taiga:
# volumes:
@@ -151,21 +152,22 @@
taiga:
{% set service_name = 'gateway' %}
{% set container_port = 80 %}
{{ service_name }}:
container_name: {{ TAIGA_CONTAINER }}-{{ service_name }}
image: nginx:alpine
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:80"
volumes:
- {{ docker_repository_path }}taiga-gateway/taiga.conf:/etc/nginx/conf.d/default.conf
- static-data:/taiga/static
- media-data:/taiga/media
{% include 'roles/docker-container/templates/base.yml.j2' %}
{% filter indent(4) %}
{% include 'roles/docker-container/templates/healthcheck/http.yml.j2' %}
{% endfilter %}
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/ >/dev/null || curl -fsS http://127.0.0.1/ >/dev/null"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
{% include 'roles/docker-container/templates/networks.yml.j2' %}
taiga:
depends_on:

View File

@@ -36,6 +36,12 @@
- name: Load setup procedures for extensions
include_tasks: 04_extensions.yml
- name: "Set authentication service according to feature toggles"
include_tasks: 05_set_authservice.yml
- name: "Run AuthDiag (temporary)"
include_tasks: _auth_diag.yml
- block:
- name: "Create Final Docker Compose File"
template:

View File

@@ -0,0 +1,73 @@
---
# Sets XWikiPreferences.authenticationService to modern component hint (standard, oidc, ldap)
- name: "XWIKI | Compute target authservice hint"
set_fact:
_target_authservice: >-
{{
'oidc' if (XWIKI_OIDC_ENABLED | bool)
else ('ldap' if (XWIKI_LDAP_ENABLED | bool)
else 'standard')
}}
- name: "XWIKI | PUT Groovy page SetAuthService"
uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'SetAuthService'] | url_join }}"
method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200,201,202,204]
headers:
Content-Type: "application/xml"
Accept: "application/xml"
body: |
<page xmlns="http://www.xwiki.org">
<title>SetAuthService</title>
<content><![CDATA[
{% raw %}{{groovy}}{% endraw %}
try {
def doc = xwiki.getDocument('XWiki.XWikiPreferences')
def obj = doc.getObject('XWiki.XWikiPreferences', true)
obj.set('authenticationService', '{{ _target_authservice }}')
def engine = xcontext.context.getWiki()
engine.saveDocument(doc.getDocument(), "Set authentication service to {{ _target_authservice }}", true, xcontext.context)
print "OK::{{ _target_authservice }}"
} catch (Throwable t) {
print "ERROR::" + (t?.message ?: t?.toString())
}
{% raw %}{{/groovy}}{% endraw %}
]]></content>
<syntax>xwiki/2.1</syntax>
</page>
register: _put_auth_page
- name: "XWIKI | Execute SetAuthService"
uri:
url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/bin/view/XWiki/SetAuthService?xpage=plain"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200]
return_content: yes
register: _exec_auth_page
retries: 10
delay: 3
until: _exec_auth_page is succeeded
- name: "ASSERT | Auth service set"
assert:
that:
- _exec_auth_page.content is search("OK::")
fail_msg: "Failed to set XWikiPreferences.authenticationService: {{ _exec_auth_page.content | default('no content') }}"
- name: "XWIKI | Delete SetAuthService page"
uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'SetAuthService'] | url_join }}"
method: DELETE
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [204,200,202,404]
changed_when: false

View File

@@ -0,0 +1,68 @@
# roles/web-app-xwiki/tasks/_auth_diag.yml
- name: "XWIKI | PUT page XWiki.AuthDiag (Groovy)"
uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'AuthDiag'] | url_join }}"
method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200,201,202,204]
headers:
Content-Type: "application/xml"
Accept: "application/xml"
body: |
<page xmlns="http://www.xwiki.org">
<title>AuthDiag</title>
<content><![CDATA[
{% raw %}{{groovy}}{% endraw %}
import org.xwiki.security.authservice.AuthService
try {
def cm = services.component.componentManager
def hints = cm.getComponentDescriptorList(AuthService).collect{ it.roleHint }.sort()
def doc = xwiki.getDocument('XWiki.XWikiPreferences')
def obj = doc.getObject('XWiki.XWikiPreferences', true)
def pref = (obj.get('authenticationService') ?: 'unset')
println "HINTS::" + hints
println "PREF::" + pref
def chosenHint = (pref ?: 'standard')
def hasChosen = hints.contains(chosenHint)
println "HAS_CHOSEN::" + hasChosen + "::" + chosenHint
} catch (Throwable t) {
println "ERROR::" + (t?.message ?: t?.toString())
}
{% raw %}{{/groovy}}{% endraw %}
]]></content>
<syntax>xwiki/2.1</syntax>
</page>
register: _put_authdiag
changed_when: false
- name: "XWIKI | Run AuthDiag"
uri:
url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/bin/view/XWiki/AuthDiag?xpage=plain"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200]
return_content: yes
register: _authdiag_run
changed_when: false
- name: "DEBUG | AuthDiag output"
debug:
msg: "{{ _authdiag_run.content | regex_replace('<[^>]+>', '') | trim }}"
# Optional sauber machen:
- name: "XWIKI | DELETE AuthDiag page"
uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'AuthDiag'] | url_join }}"
method: DELETE
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [204,200,202,404]
changed_when: false

View File

@@ -8,11 +8,9 @@
- "127.0.0.1:{{ XWIKI_HOST_PORT }}:{{ container_port }}"
environment:
JAVA_OPTS: >-
{% if xwiki_oidc_enabled_switch| bool %}
{% if xwiki_oidc_enabled_switch | bool %}
-Dxwiki.authentication.authservice=oidc
-Dxwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl
{% elif xwiki_ldap_enabled_switch | bool %}
-Dxwiki.authentication.authclass=org.xwiki.contrib.ldap.XWikiLDAPAuthServiceImpl
-Dxwiki.authentication.authservice=ldap
-Dxwiki.authentication.ldap=1
-Dxwiki.authentication.ldap.trylocal={{ (XWIKI_LDAP_TRYLOCAL | bool) | ternary(1, 0) }}
@@ -27,7 +25,6 @@
-Dxwiki.authentication.ldap.update_user=1
{% else %}
-Dxwiki.authentication.authservice=standard
-Dxwiki.authentication.authclass=com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl
{% endif %}
volumes:
- "{{ XWIKI_HOST_PROPERTIES_PATH }}:/usr/local/tomcat/webapps/ROOT/WEB-INF/xwiki.properties"

View File

@@ -1,117 +0,0 @@
import os
import sys
import unittest
from importlib import import_module
# Compute repo root (…/tests/unit/roles/web-app-desktop/lookup_plugins/docker_cards_grouped.py -> repo root)
_THIS_DIR = os.path.dirname(__file__)
_REPO_ROOT = os.path.abspath(os.path.join(_THIS_DIR, "../../../../.."))
# Add the lookup_plugins directory to sys.path so we can import the plugin as a plain module
_LOOKUP_DIR = os.path.join(_REPO_ROOT, "roles", "web-app-desktop", "lookup_plugins")
if _LOOKUP_DIR not in sys.path:
sys.path.insert(0, _LOOKUP_DIR)
# Import the plugin module
plugin = import_module("docker_cards_grouped")
LookupModule = plugin.LookupModule
try:
from ansible.errors import AnsibleError
except Exception: # Fallback for environments without full Ansible
class AnsibleError(Exception):
pass
class TestDockerCardsGroupedLookup(unittest.TestCase):
def setUp(self):
self.lookup = LookupModule()
# Menu categories with mixed-case names to verify case-insensitive sort
self.menu_categories = {
"B-Group": {"tags": ["b", "beta"]},
"a-Group": {"tags": ["a", "alpha"]},
"Zeta": {"tags": ["z"]},
}
# Cards with tags; one should end up uncategorized
self.cards = [
{"title": "Alpha Tool", "tags": ["a"]},
{"title": "Beta Widget", "tags": ["beta"]},
{"title": "Zed App", "tags": ["z"]},
{"title": "Unmatched Thing", "tags": ["x"]},
]
def _run(self, cards=None, menu_categories=None):
result = self.lookup.run(
[cards or self.cards, menu_categories or self.menu_categories]
)
# Plugin returns a single-element list containing the result dict
self.assertIsInstance(result, list)
self.assertEqual(len(result), 1)
self.assertIsInstance(result[0], dict)
return result[0]
def test_categorization_and_uncategorized(self):
data = self._run()
self.assertIn("categorized", data)
self.assertIn("uncategorized", data)
categorized = data["categorized"]
uncategorized = data["uncategorized"]
# Each matching card is placed into the proper category
self.assertIn("a-Group", categorized)
self.assertIn("B-Group", categorized)
self.assertIn("Zeta", categorized)
titles_in_a = [c["title"] for c in categorized["a-Group"]]
titles_in_b = [c["title"] for c in categorized["B-Group"]]
titles_in_z = [c["title"] for c in categorized["Zeta"]]
self.assertEqual(titles_in_a, ["Alpha Tool"])
self.assertEqual(titles_in_b, ["Beta Widget"])
self.assertEqual(titles_in_z, ["Zed App"])
# Unmatched card should be in 'uncategorized'
self.assertEqual(len(uncategorized), 1)
self.assertEqual(uncategorized[0]["title"], "Unmatched Thing")
def test_categories_sorted_alphabetically_case_insensitive(self):
data = self._run()
categorized = data["categorized"]
# Verify order is alphabetical by key, case-insensitive
keys = list(categorized.keys())
self.assertEqual(keys, ["a-Group", "B-Group", "Zeta"])
def test_multiple_tags_match_first_category_encountered(self):
# A card that matches multiple categories should be placed
# into the first matching category based on menu_categories iteration order.
# Here "Dual Match" has both 'a' and 'b' tags; since "a-Group" is alphabetically
# before "B-Group" only after sorting happens at RETURN time, we need to ensure the
# assignment is based on menu_categories order (insertion order).
menu_categories = {
"B-Group": {"tags": ["b"]},
"a-Group": {"tags": ["a"]},
}
cards = [{"title": "Dual Match", "tags": ["a", "b"]}]
# The plugin iterates menu_categories in insertion order and breaks on first match,
# so this card should end up in "B-Group".
data = self._run(cards=cards, menu_categories=menu_categories)
categorized = data["categorized"]
self.assertIn("B-Group", categorized)
self.assertEqual([c["title"] for c in categorized["B-Group"]], ["Dual Match"])
self.assertNotIn("a-Group", categorized) # no card added there
def test_missing_arguments_raises(self):
with self.assertRaises(AnsibleError):
self.lookup.run([]) # no args
with self.assertRaises(AnsibleError):
self.lookup.run([[]]) # only one arg
if __name__ == "__main__":
unittest.main()