6 Commits

Author SHA1 Message Date
2f992983f4 xwiki: install/verify via REST Job API; add 'xwiki_job_id' filter; refactor extension probe; remove invalid /extensions/{id} verify; README wording
Context: fixed 404 on 'Verify OIDC extension is installed' by polling jobstatus and parsing job id via filter plugin.
Conversation: https://chatgpt.com/share/68c435b7-96c0-800f-b7d6-b3fe99b443e0
2025-09-12 17:01:37 +02:00
d7d8578b13 fix(xwiki): correct extension.repositories format to id:type:url
Changed repository definition from 'maven:xwiki-public ...' to 'xwiki-public:maven:...'
so that the XWiki Extension Manager can correctly register Maven repositories.
This resolves the 'Unsupported repository type [central]' error and allows OIDC extension installation.

Details: https://chatgpt.com/share/68c42c4f-fda4-800f-a003-c16bcc9bd2a3
2025-09-12 16:21:23 +02:00
f106d5ec36 web-app-xwiki: admin bootstrap & REST/extension install fixes
• Guard admin tasks via XWIKI_SSO_ENABLED
• Create admin using XWikiUsers object API
• Wait for REST without DW redirect
• Install OIDC/LDAP via /rest/jobs (+verify)
• Mount xwiki.cfg/properties under Tomcat WEB-INF
• Build REST URLs with url_join; enable DW auto bootstrap + repos

https://chatgpt.com/share/68c42502-a5cc-800f-b05a-a1dbe48f014d
2025-09-12 15:50:30 +02:00
53b3a3a7b1 Deactivated LDAP by default 2025-09-12 14:13:13 +02:00
f576b42579 XWiki: two-phase bootstrap + extension install before enabling auth; add XOR validation
- Add 02_validation.yml to prevent OIDC+LDAP enabled simultaneously
- Introduce _flush_config.yml with switches (OIDC/LDAP/superadmin)
- Bootstrap with native+superadmin → create admin → install extensions (superadmin) → enable final auth
- Refactor REST vars (XWIKI_REST_BASE, XWIKI_REST_XWIKI, XWIKI_REST_EXTENSION_INSTALL)
- Update templates to use switch vars; gate OIDC block in properties
- Idempotent REST readiness waits

Conversation: https://chatgpt.com/share/68c40c1e-2b3c-800f-b59f-8d37baa9ebb2
2025-09-12 14:04:02 +02:00
b0f10aa0d0 Removed unnecessary just up 2025-09-12 13:21:29 +02:00
16 changed files with 325 additions and 120 deletions

View File

@@ -9,7 +9,6 @@
listen:
- docker compose up
- docker compose restart
- docker compose just up
when: MODE_ASSERT | bool
- name: docker compose pull
@@ -41,7 +40,6 @@
listen:
- docker compose up
- docker compose restart
- docker compose just up
- name: Build docker compose
shell: |
@@ -77,7 +75,6 @@
DOCKER_CLIENT_TIMEOUT: 600
listen:
- docker compose up
- docker compose just up # @todo replace later just up by up when code is refactored, build atm is also listening to up
- name: docker compose restart
command:

View File

@@ -16,4 +16,4 @@
content: "{{ compose_slurp.content | b64decode | compose_mods(docker_repository_path, docker_compose.files.env) }}"
dest: "{{ docker_compose_file_final }}"
notify:
- docker compose just up
- docker compose up

View File

@@ -6,7 +6,7 @@ Empower your organization with **XWiki**, an open-source enterprise wiki and kno
## Overview
This role deploys XWiki using Docker, automating the installation, configuration, and management of your XWiki server. It integrates with an MariaDB database and an Nginx reverse proxy. The role supports advanced features such as global CSS injection, Matomo analytics, OIDC authentication, and centralized logout, making it a powerful and customizable solution within the Infinito.Nexus ecosystem.
This role deploys XWiki using Docker, automating the installation, configuration, and management of your XWiki server. It integrates with an relational database and a reverse proxy. The role supports advanced features such as global CSS injection, Matomo analytics, OIDC authentication, and centralized logout, making it a powerful and customizable solution within the Infinito.Nexus ecosystem.
## Features

View File

@@ -21,7 +21,7 @@ features:
central_database: true
logout: true
oidc: true
ldap: true
ldap: false # Just OIDC or LDAP can be enabled
server:
csp:
whitelist: {}

View File

@@ -0,0 +1,82 @@
# filter_plugins/xwiki_filters.py
from __future__ import annotations
import re
from typing import Any, Dict, Optional, Iterable
try:
# Ansible provides this; don't hard-depend at import time for unit tests
from ansible.errors import AnsibleFilterError
except Exception: # pragma: no cover
class AnsibleFilterError(Exception):
pass
_JOB_LOC_RE = re.compile(r"/rest/jobstatus/([^?\s#]+)")
def _join_elements(elems: Iterable[Any]) -> str:
return "/".join(str(x) for x in elems)
def xwiki_job_id(response: Any, default: Optional[str] = None, strict: bool = False) -> Optional[str]:
"""
Extract a XWiki job ID from a typical Ansible `uri` response.
Supports:
- JSON mapping: {"id": {"elements": ["install", "extensions", "123"]}}
- JSON mapping: {"id": "install/extensions/123"}
- Fallback from Location header or URL containing "/rest/jobstatus/<id>"
Args:
response: The registered result from the `uri` task (dict-like).
default: Value to return when no ID can be found (if strict=False).
strict: If True, raise AnsibleFilterError when no ID is found.
Returns:
The job ID string, or `default`/None.
Raises:
AnsibleFilterError: if `strict=True` and no job ID can be determined.
"""
if not isinstance(response, dict):
if strict:
raise AnsibleFilterError("xwiki_job_id: response must be a dict-like Ansible result.")
return default
# 1) Try JSON body
j = response.get("json")
if isinstance(j, dict):
job_id = j.get("id")
if isinstance(job_id, dict):
elems = job_id.get("elements")
if isinstance(elems, list) and elems:
return _join_elements(elems)
if isinstance(job_id, str) and job_id.strip():
return job_id.strip()
# 2) Fallback: Location header (Ansible `uri` exposes it as `location`)
loc = response.get("location")
if isinstance(loc, str) and loc:
m = _JOB_LOC_RE.search(loc)
if m:
return m.group(1)
# 3) As a last resort, try the final `url` (in case server redirected and Ansible captured it)
url = response.get("url")
if isinstance(url, str) and url:
m = _JOB_LOC_RE.search(url)
if m:
return m.group(1)
# Not found
if strict:
raise AnsibleFilterError("xwiki_job_id: could not extract job ID from response.")
return default
class FilterModule(object):
"""Custom filters for XWiki helpers."""
def filters(self):
return {
"xwiki_job_id": xwiki_job_id,
}

View File

@@ -1,70 +1,31 @@
- name: Validate XWiki variables
include_tasks: 02_validation.yml
- name: "load docker, db and proxy for {{ application_id }}"
include_role:
name: sys-stk-full-stateful
vars:
docker_compose_flush_handlers: false
- name: "Render xwiki.cfg"
template:
src: "xwiki.cfg.j2"
dest: "{{ XWIKI_HOST_CONF_PATH }}"
notify: docker compose up
- name: Deploy Bootstrap Config
include_tasks: _flush_config.yml
vars:
xwiki_oidc_enabled_switch: false
xwiki_ldap_enabled_switch: false
xwiki_superadmin_enabled_switch: true
- name: "Render xwiki.properties"
template:
src: "xwiki.properties.j2"
dest: "{{ XWIKI_HOST_PROPERTIES_PATH }}"
notify: docker compose up
- name: Load setup procedures for admin
include_tasks: 03_administrator.yml
when: not (XWIKI_SSO_ENABLED | bool)
- name: "flush docker compose for '{{ application_id }}'"
meta: flush_handlers
- name: Load setup procedures for extensions
include_tasks: 04_extensions.yml
- name: "Wait until XWiki REST is ready"
uri:
url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/"
status_code: [200, 401, 302]
return_content: no
register: xwiki_rest_up
retries: 60
delay: 5
until: xwiki_rest_up is succeeded
- include_tasks: 02_bootstrap_admin.yml
- name: "Check if OIDC extension installed"
uri:
url: "{{ XWIKI_REST_GENERAL }}/extensions/{{ XWIKI_EXT_OIDC_ID | urlencode }}"
method: GET
user: "{{ XWIKI_ADMIN_USER }}"
password: "{{ XWIKI_ADMIN_PASS }}"
force_basic_auth: yes
status_code: [200, 404, 302]
register: xwiki_oidc_ext
when: XWIKI_OIDC_ENABLED | bool
- name: "Check if LDAP extension installed"
uri:
url: "{{ XWIKI_REST_GENERAL }}/extensions/{{ XWIKI_EXT_LDAP_ID | urlencode }}"
method: GET
user: "{{ XWIKI_ADMIN_USER }}"
password: "{{ XWIKI_ADMIN_PASS }}"
force_basic_auth: yes
status_code: [200, 404, 302]
register: xwiki_ldap_ext
when: XWIKI_LDAP_ENABLED | bool
- name: "Install LDAP and/or OIDC extensions"
uri:
url: "{{ XWIKI_REST_BASE }}"
method: PUT
user: "{{ XWIKI_ADMIN_USER }}"
password: "{{ XWIKI_ADMIN_PASS }}"
force_basic_auth: yes
headers: { Content-Type: "text/xml" }
body: "{{ lookup('template', 'installjobrequest.xml.j2') }}"
status_code: 200
when:
- (XWIKI_OIDC_ENABLED | bool and xwiki_oidc_ext.status == 404) or
(XWIKI_LDAP_ENABLED | bool and (xwiki_ldap_ext is not skipped) and xwiki_ldap_ext.status == 404)
- name: Deploy Final Config
include_tasks: _flush_config.yml
vars:
xwiki_oidc_enabled_switch: "{{ XWIKI_OIDC_ENABLED | bool }}"
xwiki_ldap_enabled_switch: "{{ XWIKI_LDAP_ENABLED | bool }}"
xwiki_superadmin_enabled_switch: false
- include_tasks: utils/run_once.yml

View File

@@ -1,43 +0,0 @@
---
# Wait until REST endpoint is available (01_core usually ensures this, but add safety)
- name: "XWIKI | Wait until REST answers"
uri:
url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/"
status_code: [200, 401]
register: _rest_ping
retries: 60
delay: 5
until: _rest_ping is succeeded
# Check if the target admin already exists
# 404 => missing, 302 => DW redirect (treat as missing for bootstrap)
- name: "XWIKI | Check if target admin user exists"
uri:
url: "{{ XWIKI_REST_GENERAL }}/users/{{ XWIKI_ADMIN_USER | urlencode }}"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200, 404, 302]
register: _admin_exists
# Create admin user if not existing (or DW still redirecting)
- name: "XWIKI | Create admin user via REST"
uri:
url: "{{ XWIKI_REST_GENERAL }}/users"
method: POST
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: 201
headers:
Content-Type: "application/xml"
body: |
<user>
<firstName>{{ users.administrator.firstname | default('Admin') }}</firstName>
<lastName>{{ users.administrator.lastname | default('User') }}</lastName>
<email>{{ users.administrator.email }}</email>
<username>{{ XWIKI_ADMIN_USER }}</username>
<password>{{ XWIKI_ADMIN_PASS }}</password>
</user>
when: _admin_exists.status in [404, 302]

View File

@@ -0,0 +1,9 @@
- name: "ASSERT | Only one auth backend (OIDC or LDAP) may be enabled"
assert:
that:
- not ((XWIKI_OIDC_ENABLED | bool) and (XWIKI_LDAP_ENABLED | bool))
fail_msg: >-
Invalid auth configuration: both OIDC and LDAP are enabled
(features.oidc={{ XWIKI_OIDC_ENABLED }}, features.ldap={{ XWIKI_LDAP_ENABLED }}).
Enable only one, or disable both to use native/superadmin login.
success_msg: "Auth config OK: OIDC={{ XWIKI_OIDC_ENABLED }}, LDAP={{ XWIKI_LDAP_ENABLED }}."

View File

@@ -0,0 +1,73 @@
# 1) Create page XWiki.<userid> (PUT is idempotent)
- name: "XWIKI | Ensure user page exists: XWiki.{{ XWIKI_ADMIN_USER }}"
uri:
url: "{{ XWIKI_REST_BASE }}wikis/xwiki/spaces/XWiki/pages/{{ XWIKI_ADMIN_USER | urlencode }}"
method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [201, 202, 204, 200]
headers:
Content-Type: "application/xml"
Accept: "application/xml"
body: |
<page xmlns="http://www.xwiki.org">
<title>{{ XWIKI_ADMIN_USER }}</title>
</page>
register: _user_page
# 2) Add XWiki.XWikiUsers object (only if it does not already exist)
- name: "XWIKI | Check if XWikiUsers object exists"
uri:
url: "{{ XWIKI_REST_BASE }}wikis/xwiki/spaces/XWiki/pages/{{ XWIKI_ADMIN_USER | urlencode }}/objects?classname=XWiki.XWikiUsers"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200, 404]
register: _users_obj_list
- name: "XWIKI | Add XWiki.XWikiUsers object"
uri:
url: "{{ XWIKI_REST_BASE }}wikis/xwiki/spaces/XWiki/pages/{{ XWIKI_ADMIN_USER | urlencode }}/objects"
method: POST
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [201, 200]
headers:
Content-Type: "application/xml"
Accept: "application/xml"
body: |
<object xmlns="http://www.xwiki.org">
<className>XWiki.XWikiUsers</className>
<properties>
<property name="first_name">{{ users.administrator.firstname | default('Admin') }}</property>
<property name="last_name">{{ users.administrator.lastname | default('User') }}</property>
<property name="email">{{ users.administrator.email }}</property>
<property name="active">1</property>
</properties>
</object>
when: _users_obj_list.status == 404 or ('<object' not in (_users_obj_list.content | default('')))
register: _user_obj_created
# 3) (Optional) Assign admin rights by adding the user to XWikiAdminGroup
- name: "XWIKI | Ensure user is in XWikiAdminGroup"
uri:
url: "{{ XWIKI_REST_BASE }}wikis/xwiki/spaces/XWiki/pages/XWikiAdminGroup/objects"
method: POST
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [201, 200]
headers:
Content-Type: "application/xml"
Accept: "application/xml"
body: |
<object xmlns="http://www.xwiki.org">
<className>XWiki.XWikiGroups</className>
<properties>
<property name="member">XWiki.{{ XWIKI_ADMIN_USER }}</property>
</properties>
</object>
when: XWIKI_LDAP_ENABLED | bool == false and XWIKI_OIDC_ENABLED | bool == false

View File

@@ -0,0 +1,60 @@
- name: "Probe OIDC extension"
include_tasks: _probe_extension.yml
vars:
ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
result_var: "xwiki_oidc_ext"
- name: "Probe LDAP extension"
include_tasks: _probe_extension.yml
vars:
ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
result_var: "xwiki_ldap_ext"
- name: "Install LDAP and/or OIDC extensions"
uri:
url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false&media=json"
method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
headers:
Content-Type: "text/xml"
Accept: "application/json"
X-Requested-With: "XMLHttpRequest"
body: "{{ lookup('template', 'installjobrequest.xml.j2') }}"
status_code: [200, 202]
return_content: yes
when:
- (XWIKI_OIDC_ENABLED | bool and (xwiki_oidc_ext.status | default(404)) != 200)
or
(XWIKI_LDAP_ENABLED | bool and (xwiki_ldap_ext is not skipped) and (xwiki_ldap_ext.status | default(404)) != 200)
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
register: xwiki_install_job
- name: "Extract install job id"
set_fact:
xwiki_install_job_id: "{{ xwiki_install_job | xwiki_job_id(default='') }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Poll install job status until FINISHED without errors"
when: xwiki_install_job_id is defined and xwiki_install_job_id|length > 0
uri:
url: "{{ XWIKI_REST_BASE }}jobstatus/{{ xwiki_install_job_id }}?media=json"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
headers:
Accept: "application/json"
status_code: 200
return_content: yes
register: _install_status
changed_when: false
retries: 20
delay: 3
until:
- _install_status is succeeded
- _install_status.json.state == 'FINISHED'
- (_install_status.json.errors | default([]) | length) == 0

View File

@@ -0,0 +1,28 @@
- name: "Render xwiki.cfg"
template:
src: "xwiki.cfg.j2"
dest: "{{ XWIKI_HOST_CONF_PATH }}"
notify: docker compose up
- name: "Deploy xwiki.properties"
template:
src: "xwiki.properties.j2"
dest: "{{ XWIKI_HOST_PROPERTIES_PATH }}"
notify: docker compose up
- name: "flush docker compose for '{{ application_id }}'"
meta: flush_handlers
- name: "Wait until XWiki REST is ready (no DW redirect)"
uri:
url: "{{ XWIKI_REST_BASE }}"
status_code: [200, 401]
return_content: no
follow_redirects: none
register: xwiki_rest_up
changed_when: false
retries: 60
delay: 5
until:
- xwiki_rest_up is succeeded
- (xwiki_rest_up.redirected is not defined) or (not xwiki_rest_up.redirected)

View File

@@ -0,0 +1,22 @@
- name: "XWIKI | Probe extension {{ ext_id }}"
when: ext_enabled | bool
uri:
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ ext_id | urlencode }}"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
follow_redirects: none
return_content: no
headers:
Accept: "application/xml"
status_code: [200, 401, 404, 302]
register: _probe
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
changed_when: false
- name: "XWIKI | Save probe result for {{ ext_id }}"
when: ext_enabled | bool
set_fact:
"{{ result_var }}": "{{ _probe }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@@ -9,6 +9,8 @@
ports:
- "127.0.0.1:{{ XWIKI_HOST_PORT }}:{{ container_port }}"
volumes:
- "{{ XWIKI_HOST_CONF_PATH }}:/usr/local/tomcat/webapps/ROOT/WEB-INF/xwiki.cfg"
- "{{ XWIKI_HOST_PROPERTIES_PATH }}:/usr/local/tomcat/webapps/ROOT/WEB-INF/xwiki.properties"
- "{{ XWIKI_HOST_CONF_PATH }}:/usr/local/xwiki/xwiki.cfg"
- "{{ XWIKI_HOST_PROPERTIES_PATH }}:/usr/local/xwiki/xwiki.properties"
- 'data:/usr/local/xwiki'

View File

@@ -1,7 +1,7 @@
# ---- Authentication selection
{% if XWIKI_OIDC_ENABLED | bool %}
{% if xwiki_oidc_enabled_switch | bool %}
xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl
{% elif XWIKI_LDAP_ENABLED | bool %}
{% elif xwiki_ldap_enabled_switch | bool %}
xwiki.authentication.authclass=org.xwiki.contrib.ldap.XWikiLDAPAuthServiceImpl
xwiki.authentication.ldap=1
xwiki.authentication.ldap.trylocal={{ (XWIKI_LDAP_TRYLOCAL | bool) | ternary(1, 0) }}
@@ -15,9 +15,9 @@ xwiki.authentication.ldap.bind_pass={{ XWIKI_LDAP_BIND_PASS }}
xwiki.authentication.ldap.fields_mapping={{ XWIKI_LDAP_FIELDS_MAPPING }}
xwiki.authentication.ldap.update_user=1
{% else %}
# Fallback: Native XWiki Auth
# xwiki.authentication.authclass=com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl
xwiki.authentication.authclass=com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl
{% endif %}
{% if xwiki_superadmin_enabled_switch | bool %}
# ---- Superadmin must live in xwiki.cfg (not in xwiki.properties)
xwiki.superadminpassword={{ XWIKI_SUPERADMIN_PASSWORD }}
{% endif %}

View File

@@ -1,6 +1,6 @@
############################################
# OIDC
{% if XWIKI_OIDC_ENABLED | bool %}
{% if xwiki_oidc_enabled_switch | bool %}
oidc.provider={{ XWIKI_OIDC_PROVIDER }}
oidc.endpoint.authorization={{ XWIKI_OIDC_AUTHORIZATION }}
oidc.endpoint.token={{ XWIKI_OIDC_TOKEN }}
@@ -16,7 +16,19 @@ oidc.groups.mapping=XWiki.XWikiAdminGroup={{ XWIKI_OIDC_ADMIN_PROVIDER_GROUP }}
{% endif %}
############################################
# Distribution Wizard
# Disable automatic start so REST is reachable during automation
distribution.automaticStartOnMainWiki=false
distribution.automaticStartOnWiki=false
# Distribution Wizard (bootstrap)
{% if xwiki_superadmin_enabled_switch | bool %}
# Start DW automatisch und ohne Interaktion
distribution.automaticStartOnMainWiki=true
distribution.automaticStartOnWiki=true
distribution.job.interactive=false
# Standard-Flavor für Main Wiki (XWiki Standard)
distribution.defaultUI=org.xwiki.platform:xwiki-platform-distribution-flavor-mainwiki
{% endif %}
# Keep data in the Docker volume
environment.permanentDirectory=/usr/local/xwiki/data
# Make sure Extension Manager can fetch artifacts
extension.repositories=xwiki-public:maven:https://nexus.xwiki.org/nexus/content/groups/public/
extension.repositories=central:maven:https://repo1.maven.org/maven2/

View File

@@ -26,6 +26,7 @@ XWIKI_DATA_VOLUME: "{{ applications | get_app_conf(applicatio
# Feature toggles (must be set in config/main.yml -> features)
XWIKI_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap') }}"
XWIKI_OIDC_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc') }}"
XWIKI_SSO_ENABLED: "{{ (XWIKI_OIDC_ENABLED | bool) or (XWIKI_LDAP_ENABLED | bool) }}"
# Admin credentials (must be provided via inventory/vault)
XWIKI_ADMIN_USER: "{{ users.administrator.username }}"
@@ -37,8 +38,9 @@ XWIKI_SUPERADMIN_PASSWORD: "{{ applications | get_app_conf(applicatio
XWIKI_SUPERADMIN_USERNAME: "superadmin"
# REST endpoint (local inside container)
XWIKI_REST_BASE: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/jobs?jobType=install&async=false"
XWIKI_REST_GENERAL: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/xwiki/rest/wikis/xwiki"
XWIKI_REST_BASE: "{{ ['http://127.0.0.1:'~ XWIKI_HOST_PORT, '/rest/'] | url_join }}"
XWIKI_REST_EXTENSION_INSTALL: "{{ [XWIKI_REST_BASE, 'jobs'] | url_join }}"
XWIKI_REST_XWIKI: "{{ [XWIKI_REST_BASE, 'wikis/xwiki'] | url_join }}"
# Extension IDs + Versions (pin versions explicitly)
XWIKI_EXT_LDAP_ID: "org.xwiki.contrib.ldap:ldap-authenticator"