refactor(docs): derive the role integration pages instead of freezing them

cli/build/docs/integration_matrix was 1189 lines a human ran through
'make integration-matrix', and its output sat frozen as a 125-line grid in
requirement 027. Its ENTITIES and EDGES lists were hand-maintained, so the
grid could only drift: it covered 84 entities while the tree holds 98 roles,
named two roles that no longer exist, and omitted sixteen current ones -
api, checkmk, docs, hermes, homeassistant, jellyfin, meta, mirror, openbao,
openclaw, penpot, pihole, s1, seaweedfs, seaweedfs-console and semaphore.

infinito_docs/generators/integrations.py replaces it and joins the nine
sibling generators in that package, so the overview, services and addons
pages are written into generated/integrations/ on every docs build rather
than when someone remembers the target. index.rst takes the overview and the
new docs/architecture/integrations.rst references the other two.
Requirement 027 becomes prose. The make target and its i8inma alias are
removed as a pair, which keeps the alias lints satisfied.

Coverage is not strictly larger. The removed EDGES list carried a curated
upstream project URL per integration, 96 of them, and the new edges have no
such field; only 48 of 159 addon declarations hold update.upstream_id or
config.repository, so about a third is re-derivable and the rest existed only
in that list. Its fourth field is no real loss - 57 entries read 'check', so
it was a near-constant rather than an axis. In exchange the old module had
zero tests, not one file under tests/ referenced it, and the generator ships
310 lines including generate().

Residuals: the HTML build runs without -W, but i18n.py:57 runs the gettext
builder with -W and check=True, and that build has never seen
integrations.rst - no catalog mentions generated/integrations. And
docs/requirements/036:72 still carries a checked criterion asserting a table
in 027 that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-10-02 18:22:04 +02:00
1 parent e78596d76d
commit bed4b3c84f
13 files changed
+666 -1325

No files matched your search

-5
View File
@@ -451,11 +451,6 @@ install-system-python:
install-venv: install-system-python
@bash scripts/install/venv.sh
.PHONY: integration-matrix
# Regenerate the role-by-role integration matrix from the roles and the curated edge map.
integration-matrix:
@"$${PYTHON}" -m cli.build.docs.integration_matrix
.PHONY: kernel-loop-load
# Load the kernel loop driver the swarm backup DR drill needs.
# Note: run this on the host; container environments have no modprobe.
-1
View File
@@ -47,7 +47,6 @@ alias i8fi='m fix-chmod'
alias i8fido='m fix-dockerignore'
alias i8he='m help'
alias i8inex='m i18n-extract'
alias i8inma='m integration-matrix'
alias i8inpu='m i18n-prune'
alias i8inre='m i18n-retry'
alias i8inta='m i18n-translate'
@@ -1,12 +0,0 @@
"""Compatibility wrapper.
The implementation lives in ``cli/build/docs/integration_matrix/__main__.py``;
this package re-exports its public API so ``from
cli.build.docs.integration_matrix import ...`` works.
"""
from __future__ import annotations
from pathlib import Path
PROJECT_ROOT: Path = Path(__file__).resolve().parents[4]
@@ -1,652 +0,0 @@
#!/usr/bin/env python3
"""Generate the role×role integration matrix.
Source of truth for the integration-matrix markdown emitted alongside this
script. Regenerate it from the repository root and commit the regenerated file.
Axes are the entity names of every web-app-* and web-svc-* role.
A cell [row][col] marks whether the ROW role ships an addon/plugin that wires
in the COLUMN role:
- check -> a real upstream plugin exists (cell links to its page)
- coin -> integration exists but is gated behind a commercial/paid tier
- cross -> no known plugin
- dash -> diagonal (same role)
"""
# nocheck: file-size curated EDGES data table plus matrix renderers; the data and the renderer read together, splitting them only adds indirection.
import re
from utils.cache.files import read_text
from utils.roles.mapping import ROLE_FILE_META_SERVICES
from . import PROJECT_ROOT
ROLES_DIR = PROJECT_ROOT / "roles"
REPORT = PROJECT_ROOT / "docs" / "requirements" / "027-integration-matrix.md"
FRAMEWORK = {
"sso": "keycloak",
"matomo": "matomo",
"prometheus": "prometheus",
"email": "mailu",
"dashboard": "dashboard",
"css": "css",
"logout": "logout",
"cdn": "cdn",
"coturn": "coturn",
"collabora": "collabora",
"onlyoffice": "onlyoffice",
"libretranslate": "libretranslate",
"litellm": "litellm",
}
WEB_APP = [
"akaunting",
"baserow",
"bigbluebutton",
"bluesky",
"bookwyrm",
"bridgy-fed",
"chess",
"confluence",
"dashboard",
"decidim",
"discourse",
"erpnext",
"espocrm",
"fediwall",
"fider",
"flowise",
"friendica",
"funkwhale",
"fusiondirectory",
"gitea",
"gitlab",
"hugo",
"jenkins",
"jira",
"jitsi",
"joomla",
"keycloak",
"kix",
"lam",
"listmonk",
"litellm",
"littlejs",
"magento",
"mailu",
"mastodon",
"matomo",
"matrix",
"mattermost",
"mediawiki",
"mig",
"minio",
"mini-qr",
"mobilizon",
"moodle",
"n8n",
"navigator",
"nextcloud",
"odoo",
"opencloud",
"openproject",
"opentalk",
"openwebui",
"peertube",
"pgadmin",
"phpldapadmin",
"phpmyadmin",
"pixelfed",
"postmarks",
"pretix",
"prometheus",
"roulette-wheel",
"shopware",
"snipe-it",
"socialhome",
"sphinx",
"suitecrm",
"taiga",
"wordpress",
"xwiki",
"yourls",
"zammad",
]
WEB_SVC = [
"asset",
"cdn",
"collabora",
"coturn",
"css",
"file",
"html",
"legal",
"libretranslate",
"logout",
"onlyoffice",
"simpleicons",
"xmpp",
]
ENTITIES = WEB_APP + WEB_SVC
EDGES = [
(
"nextcloud",
"openproject",
"https://github.com/nextcloud/integration_openproject",
"check",
),
("nextcloud", "gitlab", "https://github.com/nextcloud/integration_gitlab", "check"),
(
"nextcloud",
"discourse",
"https://github.com/nextcloud/integration_discourse",
"check",
),
(
"nextcloud",
"mattermost",
"https://github.com/nextcloud/integration_mattermost",
"check",
),
("nextcloud", "matrix", "https://github.com/nextcloud/integration_matrix", "check"),
("nextcloud", "jira", "https://github.com/nextcloud/integration_jira", "check"),
("nextcloud", "zammad", "https://github.com/nextcloud/integration_zammad", "check"),
(
"nextcloud",
"openwebui",
"https://github.com/nextcloud/integration_openai",
"check",
),
(
"nextcloud",
"flowise",
"https://github.com/nextcloud/integration_openai",
"check",
),
(
"nextcloud",
"mastodon",
"https://github.com/nextcloud/integration_mastodon",
"check",
),
(
"nextcloud",
"peertube",
"https://github.com/nextcloud/integration_peertube",
"check",
),
("nextcloud", "moodle", "https://github.com/nextcloud/integration_moodle", "check"),
(
"nextcloud",
"suitecrm",
"https://github.com/eneiluj/integration_suitecrm",
"check",
),
("nextcloud", "bigbluebutton", "https://apps.nextcloud.com/apps/bbb", "check"),
("nextcloud", "xwiki", "https://apps.nextcloud.com/apps/xwiki", "check"),
("nextcloud", "collabora", "https://github.com/nextcloud/richdocuments", "check"),
(
"nextcloud",
"onlyoffice",
"https://github.com/ONLYOFFICE/onlyoffice-nextcloud",
"check",
),
("nextcloud", "keycloak", "https://github.com/nextcloud/user_oidc", "check"),
("nextcloud", "matomo", "https://apps.nextcloud.com/apps/matomo", "check"),
(
"mattermost",
"gitlab",
"https://github.com/mattermost/mattermost-plugin-gitlab",
"check",
),
(
"mattermost",
"jira",
"https://github.com/mattermost/mattermost-plugin-jira",
"check",
),
(
"mattermost",
"jenkins",
"https://github.com/mattermost/mattermost-plugin-jenkins",
"check",
),
(
"mattermost",
"jitsi",
"https://github.com/mattermost-community/mattermost-plugin-jitsi",
"check",
),
(
"mattermost",
"openwebui",
"https://github.com/mattermost/mattermost-plugin-agents",
"check",
),
(
"mattermost",
"prometheus",
"https://github.com/cpanato/mattermost-plugin-alertmanager",
"check",
),
(
"mattermost",
"keycloak",
"https://docs.mattermost.com/administration-guide/onboard/sso-saml.html",
"coin",
),
(
"gitlab",
"mattermost",
"https://docs.gitlab.com/user/project/integrations/mattermost_notifications/",
"check",
),
("gitlab", "jira", "https://docs.gitlab.com/integration/jira/", "check"),
("gitlab", "jenkins", "https://docs.gitlab.com/integration/jenkins/", "check"),
(
"gitlab",
"matrix",
"https://docs.gitlab.com/user/project/integrations/matrix/",
"check",
),
(
"gitlab",
"confluence",
"https://docs.gitlab.com/user/project/integrations/confluence/",
"check",
),
("gitlab", "keycloak", "https://docs.gitlab.com/integration/saml/", "check"),
(
"discourse",
"mattermost",
"https://github.com/discourse/discourse-chat-integration",
"check",
),
(
"discourse",
"matrix",
"https://github.com/discourse/discourse-chat-integration",
"check",
),
(
"discourse",
"prometheus",
"https://github.com/discourse/discourse-prometheus",
"check",
),
(
"discourse",
"bigbluebutton",
"https://github.com/discourse/discourse-bbb",
"check",
),
("discourse", "jitsi", "https://github.com/discourse/discourse-jitsi", "check"),
("discourse", "openwebui", "https://github.com/discourse/discourse-ai", "check"),
(
"discourse",
"matomo",
"https://github.com/discourse/discourse-matomo-analytics",
"check",
),
(
"discourse",
"keycloak",
"https://github.com/discourse/discourse-openid-connect",
"check",
),
(
"discourse",
"mastodon",
"https://github.com/discourse/discourse-activity-pub",
"check",
),
("matrix", "gitlab", "https://github.com/matrix-org/matrix-hookshot", "check"),
("matrix", "jira", "https://github.com/matrix-org/matrix-hookshot", "check"),
(
"matrix",
"keycloak",
"https://matrix-org.github.io/synapse/latest/openid.html",
"check",
),
(
"matrix",
"prometheus",
"https://matrix-org.github.io/synapse/latest/metrics-howto.html",
"check",
),
(
"matrix",
"jitsi",
"https://github.com/element-hq/element-web/blob/develop/docs/jitsi.md",
"check",
),
(
"moodle",
"bigbluebutton",
"https://moodle.org/plugins/mod_bigbluebuttonbn",
"check",
),
("moodle", "nextcloud", "https://moodle.org/plugins/repository_owncloud", "check"),
("moodle", "matrix", "https://docs.moodle.org/en/Matrix", "check"),
("moodle", "keycloak", "https://moodle.org/plugins/auth_oidc", "check"),
("moodle", "jitsi", "https://moodle.org/plugins/mod_jitsi", "check"),
("moodle", "matomo", "https://moodle.org/plugins/tool_webanalytics", "check"),
("moodle", "openwebui", "https://moodle.org/plugins/aiprovider_openwebui", "check"),
("jenkins", "gitlab", "https://plugins.jenkins.io/gitlab-plugin", "check"),
("jenkins", "gitea", "https://plugins.jenkins.io/gitea/", "check"),
("jenkins", "mattermost", "https://plugins.jenkins.io/mattermost/", "check"),
("jenkins", "prometheus", "https://plugins.jenkins.io/prometheus", "check"),
("jenkins", "keycloak", "https://plugins.jenkins.io/oic-auth/", "check"),
(
"openproject",
"nextcloud",
"https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/",
"check",
),
(
"openproject",
"gitlab",
"https://www.openproject.org/docs/system-admin-guide/integrations/gitlab-integration/",
"check",
),
(
"openproject",
"keycloak",
"https://www.openproject.org/docs/system-admin-guide/authentication/openid-providers/",
"coin",
),
("wordpress", "matomo", "https://wordpress.org/plugins/matomo/", "check"),
("wordpress", "mastodon", "https://wordpress.org/plugins/activitypub/", "check"),
("wordpress", "discourse", "https://wordpress.org/plugins/wp-discourse/", "check"),
(
"wordpress",
"keycloak",
"https://wordpress.org/plugins/daggerhart-openid-connect-generic/",
"check",
),
(
"wordpress",
"listmonk",
"https://wordpress.org/plugins/integration-for-listmonk-mailing-list-and-newsletter-manager/",
"check",
),
(
"wordpress",
"bigbluebutton",
"https://wordpress.org/plugins/video-conferencing-with-bbb/",
"check",
),
(
"wordpress",
"peertube",
"https://wordpress.org/plugins/video-manager-for-peertube/",
"check",
),
(
"mediawiki",
"keycloak",
"https://www.mediawiki.org/wiki/Extension:OpenID_Connect",
"check",
),
("mediawiki", "matomo", "https://www.mediawiki.org/wiki/Extension:Matomo", "check"),
(
"mediawiki",
"discourse",
"https://www.mediawiki.org/wiki/Extension:DiscourseSsoConsumer",
"check",
),
(
"mediawiki",
"peertube",
"https://www.mediawiki.org/wiki/Extension:PeerTubeEmbed",
"check",
),
(
"mediawiki",
"libretranslate",
"https://www.mediawiki.org/wiki/Extension:MachineTranslation",
"check",
),
("mediawiki", "minio", "https://www.mediawiki.org/wiki/Extension:AWS", "check"),
(
"mediawiki",
"openwebui",
"https://www.mediawiki.org/wiki/Extension:AIEditingAssistant",
"check",
),
(
"xwiki",
"keycloak",
"https://extensions.xwiki.org/xwiki/bin/view/Extension/OpenID%20Connect/",
"check",
),
(
"xwiki",
"matomo",
"https://extensions.xwiki.org/xwiki/bin/view/Extension/Piwiki%20Integration",
"check",
),
(
"joomla",
"keycloak",
"../../roles/web-app-joomla/files/joomla-oidc-plugin/",
"check",
),
(
"joomla",
"matomo",
"https://extensions.joomla.org/extension/itcs-matomo/",
"check",
),
("friendica", "matomo", "https://github.com/friendica/friendica-addons", "check"),
(
"pretix",
"keycloak",
"https://docs.pretix.eu/en/latest/admin/installation/index.html",
"check",
),
(
"peertube",
"keycloak",
"https://www.npmjs.com/package/peertube-plugin-auth-openid-connect",
"check",
),
(
"bigbluebutton",
"keycloak",
"https://docs.bigbluebutton.org/greenlight/v3/external-authentication/",
"check",
),
(
"zammad",
"keycloak",
"https://admin-docs.zammad.org/en/latest/settings/security/third-party/saml.html",
"check",
),
(
"zammad",
"mailu",
"https://admin-docs.zammad.org/en/latest/channels/email/index.html",
"check",
),
("espocrm", "keycloak", "https://docs.espocrm.com/administration/oidc/", "check"),
(
"espocrm",
"mailu",
"https://docs.espocrm.com/user-guide/imap-smtp-configuration/",
"check",
),
(
"suitecrm",
"keycloak",
"https://docs.suitecrm.com/8.x/admin/configuration/saml/8.7.0-saml-configuration/",
"check",
),
(
"suitecrm",
"mailu",
"https://github.com/SuiteCRM/SuiteCRM",
"check",
),
(
"odoo",
"keycloak",
"https://github.com/OCA/server-auth/tree/16.0/auth_oidc",
"check",
),
(
"odoo",
"mailu",
"https://www.odoo.com/documentation/latest/applications/general/email_communication.html",
"check",
),
(
"odoo",
"nextcloud",
"https://apps.odoo.com/apps/modules/19.0/nextcloud_odoo_integration",
"coin",
),
(
"erpnext",
"keycloak",
"https://docs.frappe.io/framework/user/en/guides/integration/openid_connect_and_frappe_social_login",
"check",
),
(
"erpnext",
"mailu",
"https://docs.frappe.io/erpnext/user/manual/en/setting-up-email-account",
"check",
),
(
"baserow",
"keycloak",
"https://baserow.io/user-docs/configure-openid-connect-for-oauth-2-sso",
"coin",
),
(
"taiga",
"keycloak",
"https://github.com/taigaio/taiga-contrib-oidc-auth",
"check",
),
]
CHECK = "✅"
WIRED = "☑️"
COIN = "🪙"
CROSS = "❌"
DASH = "—"
def scan_framework_edges():
"""Derive infinito-native integration edges from each role's meta/services.yml."""
edges = []
for prefix in ("web-app-", "web-svc-"):
for path in sorted(ROLES_DIR.glob(f"{prefix}*/{ROLE_FILE_META_SERVICES}")):
role = path.parent.parent.name
entity = role[len(prefix) :]
lines = read_text(str(path)).splitlines()
if not lines:
continue
seen = set()
for line in lines:
m = re.match(r"^([a-z0-9_]+):", line)
if not m:
continue
key = m.group(1)
target = FRAMEWORK.get(key)
if target and target != entity and target not in seen:
seen.add(target)
rel = f"../../roles/{role}/{ROLE_FILE_META_SERVICES}"
edges.append((entity, target, rel, "wired"))
return edges
def build():
by_pair = {(s, d): (url, kind) for (s, d, url, kind) in scan_framework_edges()}
by_pair.update({(s, d): (url, kind) for (s, d, url, kind) in EDGES})
corner = "↓ row wires → col"
def render_row(label, cells):
out = [label]
for j, cell in enumerate(cells):
out.append(cell)
if (j + 1) % 10 == 0 and (j + 1) < len(cells):
out.append(label)
return "| " + " | ".join(out) + " |"
header = render_row(corner, ENTITIES)
ncols = header.count("|") - 1
sep = "|" + "---|" * ncols
rows = [header, sep]
for i, src in enumerate(ENTITIES):
cells = []
for dst in ENTITIES:
if src == dst:
cells.append(DASH)
elif (src, dst) in by_pair:
url, kind = by_pair[(src, dst)]
sym = {"coin": COIN, "wired": WIRED}.get(kind, CHECK)
cells.append(f"[{sym}]({url})")
else:
cells.append(CROSS)
rows.append(render_row(f"**{src}**", cells))
if (i + 1) % 10 == 0 and (i + 1) < len(ENTITIES):
rows.append(header)
return "\n".join(rows)
HEADER = """# 027 - Integration Matrix
> Generated by `make integration-matrix`. Do not edit by hand, edit the generator's `EDGES` map and re-run.
Companion artifact for [026-unified-addon-syntax.md](026-unified-addon-syntax.md).
Axes are the **entity names** of every `web-app-*` and `web-svc-*` role.
A cell marks whether the **row** role ships an addon/plugin that wires in the **column** role.
## Legend
| Symbol | Meaning |
|---|---|
| {wired} | Already wired via an infinito-native service flag in the row role's `meta/services.yml` (group-gated). Links to that declaration. |
| {check} | A verified upstream app↔app plugin exists but is not yet declared as an addon. Links to the plugin page. This is the backlog. |
| {coin} | Integration exists but is gated behind a commercial / paid tier. |
| {cross} | No known integration to wire these two. |
| {dash} | Same role (diagonal). |
Notes:
- The matrix is **directional**: the row hosts the plugin/flag. Bidirectional pairs (e.g. `nextcloud`↔`openproject`) carry a symbol in both cells, each linking to that side.
- {wired} edges are derived automatically by scanning every role's `meta/services.yml` for integration service keys (`sso`→keycloak, `matomo`→matomo, `prometheus`→prometheus, `email`→mailu, `dashboard`, `css`, `logout`, `cdn`, `coturn`, `collabora`, `onlyoffice`, `libretranslate`, `litellm`). `ldap`/`redis`/`mariadb` map to `svc-db-*` roles that are off these axes and are not shown. The `litellm` column is the AI gateway surface of [036](036-native-ai-gateway-integration.md): the service key resolves to `svc-ai-litellm`, which is off-axis, so the edge is drawn to the `litellm` entity that fronts it.
- `→ keycloak` {wired} cells are the central `sso` service; a {check}/{coin} on `→ keycloak` instead means a role-local OIDC/SAML addon path beyond the central service.
- Native ActivityPub federation between fediverse roles (`mastodon`, `peertube`, `pixelfed`, `funkwhale`, `mobilizon`, `bookwyrm`, `socialhome`) needs **no plugin** and is therefore not a {check} edge unless an installable connector exists.
## Matrix
{matrix}
## Maintenance
- [ ] Regenerate this matrix after adding or removing any integration edge.
"""
def main():
fw = scan_framework_edges()
out = HEADER.format(
check=CHECK, wired=WIRED, coin=COIN, cross=CROSS, dash=DASH, matrix=build()
)
path = REPORT
path.write_text(out, encoding="utf-8")
print(
f"wrote {path} ({len(ENTITIES)}x{len(ENTITIES)} matrix, "
f"{len(fw)} wired + {len(EDGES)} upstream edges)"
)
if __name__ == "__main__":
main()
-525
View File
@@ -1,525 +0,0 @@
# nocheck: file-size: curated edge data table; one obvious home for the
# matrix's source-of-truth lists, splitting it only adds indirection.
"""Data tables for the role×role integration matrix generator.
Kept as a sibling importable module so the generator entrypoint stays small
and the curated edge data has one obvious home. The generator reads:
* ``WEB_APP`` / ``WEB_SVC``: the entity-name axes (role suffix of every
``web-app-*`` / ``web-svc-*`` role);
* ``FRAMEWORK``: top-level ``meta/services.yml`` integration keys mapped to
the in-axis target entity they wire the role into (the infinito-native,
group-gated service flags), distinct from the curated upstream edges;
* ``EDGES``: the curated, web-verified upstream app↔app plugin/integration
pages, each ``(source, target, url, kind)`` with ``kind`` in
``{"check", "coin"}``.
"""
from __future__ import annotations
FRAMEWORK: dict[str, str] = {
"sso": "keycloak",
"matomo": "matomo",
"prometheus": "prometheus",
"email": "mailu",
"dashboard": "dashboard",
"css": "css",
"logout": "logout",
"cdn": "cdn",
"coturn": "coturn",
"collabora": "collabora",
"onlyoffice": "onlyoffice",
"libretranslate": "libretranslate",
}
WEB_APP: list[str] = [
"akaunting",
"baserow",
"bigbluebutton",
"bluesky",
"bookwyrm",
"bridgy-fed",
"chess",
"confluence",
"dashboard",
"decidim",
"discourse",
"erpnext",
"espocrm",
"fediwall",
"fider",
"flowise",
"friendica",
"funkwhale",
"fusiondirectory",
"gitea",
"gitlab",
"hugo",
"jenkins",
"jira",
"jitsi",
"joomla",
"keycloak",
"kix",
"lam",
"listmonk",
"littlejs",
"magento",
"mailu",
"mastodon",
"matomo",
"matrix",
"mattermost",
"mediawiki",
"mig",
"minio",
"mini-qr",
"mobilizon",
"moodle",
"navigator",
"nextcloud",
"odoo",
"opencloud",
"openproject",
"opentalk",
"openwebui",
"peertube",
"pgadmin",
"phpldapadmin",
"phpmyadmin",
"pixelfed",
"postmarks",
"pretix",
"prometheus",
"roulette-wheel",
"shopware",
"snipe-it",
"socialhome",
"sphinx",
"suitecrm",
"taiga",
"wordpress",
"xwiki",
"yourls",
"zammad",
]
WEB_SVC: list[str] = [
"asset",
"cdn",
"collabora",
"coturn",
"css",
"file",
"html",
"legal",
"libretranslate",
"logout",
"onlyoffice",
"simpleicons",
"xmpp",
]
ENTITIES: list[str] = WEB_APP + WEB_SVC
EDGES: list[tuple[str, str, str, str]] = [
(
"nextcloud",
"openproject",
"https://github.com/nextcloud/integration_openproject",
"check",
),
("nextcloud", "gitlab", "https://github.com/nextcloud/integration_gitlab", "check"),
(
"nextcloud",
"discourse",
"https://github.com/nextcloud/integration_discourse",
"check",
),
(
"nextcloud",
"mattermost",
"https://github.com/nextcloud/integration_mattermost",
"check",
),
("nextcloud", "matrix", "https://github.com/nextcloud/integration_matrix", "check"),
("nextcloud", "jira", "https://github.com/nextcloud/integration_jira", "check"),
("nextcloud", "zammad", "https://github.com/nextcloud/integration_zammad", "check"),
(
"nextcloud",
"openwebui",
"https://github.com/nextcloud/integration_openai",
"check",
),
(
"nextcloud",
"flowise",
"https://github.com/nextcloud/integration_openai",
"check",
),
(
"nextcloud",
"mastodon",
"https://github.com/nextcloud/integration_mastodon",
"check",
),
(
"nextcloud",
"peertube",
"https://github.com/nextcloud/integration_peertube",
"check",
),
("nextcloud", "moodle", "https://github.com/nextcloud/integration_moodle", "check"),
(
"nextcloud",
"suitecrm",
"https://github.com/eneiluj/integration_suitecrm",
"check",
),
("nextcloud", "bigbluebutton", "https://apps.nextcloud.com/apps/bbb", "check"),
("nextcloud", "xwiki", "https://apps.nextcloud.com/apps/xwiki", "check"),
("nextcloud", "collabora", "https://github.com/nextcloud/richdocuments", "check"),
(
"nextcloud",
"onlyoffice",
"https://github.com/ONLYOFFICE/onlyoffice-nextcloud",
"check",
),
("nextcloud", "keycloak", "https://github.com/nextcloud/user_oidc", "check"),
("nextcloud", "matomo", "https://apps.nextcloud.com/apps/matomo", "check"),
(
"mattermost",
"gitlab",
"https://github.com/mattermost/mattermost-plugin-gitlab",
"check",
),
(
"mattermost",
"jira",
"https://github.com/mattermost/mattermost-plugin-jira",
"check",
),
(
"mattermost",
"jenkins",
"https://github.com/mattermost/mattermost-plugin-jenkins",
"check",
),
(
"mattermost",
"jitsi",
"https://github.com/mattermost-community/mattermost-plugin-jitsi",
"check",
),
(
"mattermost",
"openwebui",
"https://github.com/mattermost/mattermost-plugin-agents",
"check",
),
(
"mattermost",
"prometheus",
"https://github.com/cpanato/mattermost-plugin-alertmanager",
"check",
),
(
"mattermost",
"keycloak",
"https://docs.mattermost.com/administration-guide/onboard/sso-saml.html",
"coin",
),
(
"gitlab",
"mattermost",
"https://docs.gitlab.com/user/project/integrations/mattermost_notifications/",
"check",
),
("gitlab", "jira", "https://docs.gitlab.com/integration/jira/", "check"),
("gitlab", "jenkins", "https://docs.gitlab.com/integration/jenkins/", "check"),
(
"gitlab",
"matrix",
"https://docs.gitlab.com/user/project/integrations/matrix/",
"check",
),
(
"gitlab",
"confluence",
"https://docs.gitlab.com/user/project/integrations/confluence/",
"check",
),
("gitlab", "keycloak", "https://docs.gitlab.com/integration/saml/", "check"),
(
"discourse",
"mattermost",
"https://github.com/discourse/discourse-chat-integration",
"check",
),
(
"discourse",
"matrix",
"https://github.com/discourse/discourse-chat-integration",
"check",
),
(
"discourse",
"prometheus",
"https://github.com/discourse/discourse-prometheus",
"check",
),
(
"discourse",
"bigbluebutton",
"https://github.com/discourse/discourse-bbb",
"check",
),
("discourse", "jitsi", "https://github.com/discourse/discourse-jitsi", "check"),
("discourse", "openwebui", "https://github.com/discourse/discourse-ai", "check"),
(
"discourse",
"matomo",
"https://github.com/discourse/discourse-matomo-analytics",
"check",
),
(
"discourse",
"keycloak",
"https://github.com/discourse/discourse-openid-connect",
"check",
),
(
"discourse",
"mastodon",
"https://github.com/discourse/discourse-activity-pub",
"check",
),
("matrix", "gitlab", "https://github.com/matrix-org/matrix-hookshot", "check"),
("matrix", "jira", "https://github.com/matrix-org/matrix-hookshot", "check"),
(
"matrix",
"keycloak",
"https://matrix-org.github.io/synapse/latest/openid.html",
"check",
),
(
"matrix",
"prometheus",
"https://matrix-org.github.io/synapse/latest/metrics-howto.html",
"check",
),
(
"matrix",
"jitsi",
"https://github.com/element-hq/element-web/blob/develop/docs/jitsi.md",
"check",
),
(
"moodle",
"bigbluebutton",
"https://moodle.org/plugins/mod_bigbluebuttonbn",
"check",
),
("moodle", "nextcloud", "https://moodle.org/plugins/repository_owncloud", "check"),
("moodle", "matrix", "https://docs.moodle.org/en/Matrix", "check"),
("moodle", "keycloak", "https://moodle.org/plugins/auth_oidc", "check"),
("moodle", "jitsi", "https://moodle.org/plugins/mod_jitsi", "check"),
("moodle", "matomo", "https://moodle.org/plugins/tool_webanalytics", "check"),
("moodle", "openwebui", "https://moodle.org/plugins/aiprovider_openwebui", "check"),
("jenkins", "gitlab", "https://plugins.jenkins.io/gitlab-plugin", "check"),
("jenkins", "gitea", "https://plugins.jenkins.io/gitea/", "check"),
("jenkins", "mattermost", "https://plugins.jenkins.io/mattermost/", "check"),
("jenkins", "prometheus", "https://plugins.jenkins.io/prometheus", "check"),
("jenkins", "keycloak", "https://plugins.jenkins.io/oic-auth/", "check"),
(
"openproject",
"nextcloud",
"https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/",
"check",
),
(
"openproject",
"gitlab",
"https://www.openproject.org/docs/system-admin-guide/integrations/gitlab-integration/",
"check",
),
(
"openproject",
"keycloak",
"https://www.openproject.org/docs/system-admin-guide/authentication/openid-providers/",
"coin",
),
("wordpress", "matomo", "https://wordpress.org/plugins/matomo/", "check"),
("wordpress", "mastodon", "https://wordpress.org/plugins/activitypub/", "check"),
("wordpress", "discourse", "https://wordpress.org/plugins/wp-discourse/", "check"),
(
"wordpress",
"keycloak",
"https://wordpress.org/plugins/daggerhart-openid-connect-generic/",
"check",
),
(
"wordpress",
"listmonk",
"https://wordpress.org/plugins/integration-for-listmonk-mailing-list-and-newsletter-manager/",
"check",
),
(
"wordpress",
"bigbluebutton",
"https://wordpress.org/plugins/video-conferencing-with-bbb/",
"check",
),
(
"wordpress",
"peertube",
"https://wordpress.org/plugins/video-manager-for-peertube/",
"check",
),
(
"mediawiki",
"keycloak",
"https://www.mediawiki.org/wiki/Extension:OpenID_Connect",
"check",
),
("mediawiki", "matomo", "https://www.mediawiki.org/wiki/Extension:Matomo", "check"),
(
"mediawiki",
"discourse",
"https://www.mediawiki.org/wiki/Extension:DiscourseSsoConsumer",
"check",
),
(
"mediawiki",
"peertube",
"https://www.mediawiki.org/wiki/Extension:PeerTubeEmbed",
"check",
),
(
"mediawiki",
"libretranslate",
"https://www.mediawiki.org/wiki/Extension:MachineTranslation",
"check",
),
("mediawiki", "minio", "https://www.mediawiki.org/wiki/Extension:AWS", "check"),
(
"mediawiki",
"openwebui",
"https://www.mediawiki.org/wiki/Extension:AIEditingAssistant",
"check",
),
(
"xwiki",
"keycloak",
"https://extensions.xwiki.org/xwiki/bin/view/Extension/OpenID%20Connect/",
"check",
),
(
"xwiki",
"matomo",
"https://extensions.xwiki.org/xwiki/bin/view/Extension/Piwiki%20Integration",
"check",
),
(
"joomla",
"keycloak",
"../../roles/web-app-joomla/files/joomla-oidc-plugin/",
"check",
),
(
"joomla",
"matomo",
"https://extensions.joomla.org/extension/itcs-matomo/",
"check",
),
("friendica", "matomo", "https://github.com/friendica/friendica-addons", "check"),
(
"pretix",
"keycloak",
"https://docs.pretix.eu/en/latest/admin/installation/index.html",
"check",
),
(
"peertube",
"keycloak",
"https://www.npmjs.com/package/peertube-plugin-auth-openid-connect",
"check",
),
(
"bigbluebutton",
"keycloak",
"https://docs.bigbluebutton.org/greenlight/v3/external-authentication/",
"check",
),
(
"zammad",
"keycloak",
"https://admin-docs.zammad.org/en/latest/settings/security/third-party/saml.html",
"check",
),
(
"zammad",
"mailu",
"https://admin-docs.zammad.org/en/latest/channels/email/index.html",
"check",
),
("espocrm", "keycloak", "https://docs.espocrm.com/administration/oidc/", "check"),
(
"espocrm",
"mailu",
"https://docs.espocrm.com/user-guide/imap-smtp-configuration/",
"check",
),
(
"suitecrm",
"keycloak",
"https://docs.suitecrm.com/8.x/admin/configuration/saml/8.7.0-saml-configuration/",
"check",
),
(
"suitecrm",
"mailu",
"https://github.com/SuiteCRM/SuiteCRM",
"check",
),
(
"odoo",
"keycloak",
"https://github.com/OCA/server-auth/tree/16.0/auth_oidc",
"check",
),
(
"odoo",
"mailu",
"https://www.odoo.com/documentation/latest/applications/general/email_communication.html",
"check",
),
(
"odoo",
"nextcloud",
"https://apps.odoo.com/apps/modules/19.0/nextcloud_odoo_integration",
"coin",
),
(
"erpnext",
"keycloak",
"https://docs.frappe.io/framework/user/en/guides/integration/openid_connect_and_frappe_social_login",
"check",
),
(
"erpnext",
"mailu",
"https://docs.frappe.io/erpnext/user/manual/en/setting-up-email-account",
"check",
),
(
"baserow",
"keycloak",
"https://baserow.io/user-docs/configure-openid-connect-for-oauth-2-sso",
"coin",
),
(
"taiga",
"keycloak",
"https://github.com/taigaio/taiga-contrib-oidc-auth",
"check",
),
]
+14
View File
@@ -0,0 +1,14 @@
Role Integrations
=================
A role never names another role directly. It names a service key in its
``meta/services.yml``, and the key resolves to the role that provides it; an
add-on under ``meta/addons/`` reaches the same provider by naming that key in
its ``bridges:`` list. The two pages below list both sides for every
``web-app-*`` and ``web-svc-*`` role, regenerated from the roles on each build.
.. toctree::
:maxdepth: 1
/generated/integrations/services
/generated/integrations/addons
+11 -16
View File
@@ -19,7 +19,7 @@ The repository already deploys role-level extension units, but each role spells
The [per-role meta layout](../contributing/design/role/services/layout.md) already lists `addons`, `plugins`, and `modules` among the keys *inlined* under the primary service entity. This requirement promotes that concept to a first-class, per-file `meta/addons/<addon_id>.yml` topic (one file per addon, file root IS the addon spec), the same move requirement 011 made for `meta/info.yml` and requirement 008/009 made for other meta topics.
A recurring property of these extensions is that many of them exist **only to bridge into another role**: `ldapauth` and `user_ldap` bridge to `svc-db-openldap`; `sociallogin`, `oidc-authenticator`, `OpenIDConnect`, `plg_system_keycloak`, and the WordPress OIDC plugin bridge to the SSO provider (`web-app-keycloak`); `wp-discourse` bridges to `web-app-discourse`; XWiki's `matomo` plugin bridges to `web-app-matomo`. Today that bridge is implicit. This requirement makes it explicit: **an addon that bridges to another role MUST declare the bridged service, and that service MUST be present in the role's `meta/services.yml`.**
A recurring property of these extensions is that many of them exist **only to bridge into another role**: `ldapauth` and `user_ldap` bridge to `svc-db-openldap`; `sociallogin`, `oidc-authenticator`, `OpenIDConnect`, `plg_system_keycloak`, and the WordPress OIDC plugin bridge to the SSO provider (`web-app-keycloak`); `wp-discourse` bridges to `web-app-discourse`; XWiki's `ai-llm` extension bridges to `web-app-litellm`. Today that bridge is implicit. This requirement makes it explicit: **an addon that bridges to another role MUST declare the bridged service, and that service MUST be present in the role's `meta/services.yml`.**
## Confirmed Decisions
@@ -38,9 +38,9 @@ These choices are settled at requirement creation time and bound the implementat
11. **Every addon ships a Playwright test.** Each addon with a user-facing surface MUST add a matching Playwright spec under `roles/<role>/files/playwright/` that exercises that addon's behaviour (e.g. an `ldapauth`/`sociallogin` login path, a `wp-discourse` round-trip, an enabled Odoo module's UI entry point). Desktop roles (`desk-*`) drive no web surface a Playwright test could exercise and therefore NEVER ship addon specs — the lint forbids spec files under `desk-*` roles. For every web-facing role there is no per-addon exemption: an addon is considered migrated only once its Playwright spec is present and green. Cross-role integration specs (proving two roles are actually connected) run under a dedicated **integration-pair deploy variant** that co-deploys both partner roles, and are added incrementally as each ✅ pair is wired rather than all upfront.
12. **Addon state is a variant axis.** Optional addons (`required: false`) MUST express their enabled/disabled split through the role's [`meta/variants.yml`](../contributing/design/role/services/layout.md) so CI matrix runs exercise both states, mirroring requirement 025's MCP variant axis. Required addons (`required: true`) are not a variant axis because they are always installed.
13. **Network bridges are addons.** Appservice / network bridges (e.g. the Matrix `mautrix` bridges to WhatsApp, Telegram, Signal, Slack, Facebook, Instagram) are addons declared under `meta/addons/` with `mechanism: bridge`, one file per bridged network. Each bridge owns its credentials in [`meta/secrets.yml`](../contributing/design/role/services/layout.md) and is `required: false` and disabled by default. The role's former bridge declarations (e.g. `web-app-matrix`'s `vars/bridges.yml`) are absorbed into `meta/addons/`.
14. **The integration matrix is the cross-role addon backlog.** The full role×role capability map lives in the generated companion [027-integration-matrix.md](027-integration-matrix.md) (source of truth: [cli/build/docs/integration_matrix](../../cli/build/docs/integration_matrix/__main__.py)). ☑️ cells are integrations **already wired** through an infinito-native service flag in the row role's `meta/services.yml` (e.g. `sso`→keycloak, `matomo`, `prometheus`, `email`→mailu), derived by scanning the repo, no action required. ✅ cells are verified **upstream app↔app plugins not yet declared** as addons, the backlog this requirement commits to wire. 🪙 cells are the same as ✅ but gated behind a commercial tier (opt-in, operator-enabled). ❌ cells have no known integration. The matrix MUST be regenerated (not hand-edited) whenever a role or an integration is added; the `meta/addons/` migrations turn ✅ into ☑️ over time.
14. **The integration matrix is the cross-role addon backlog.** The role×role capability map is generated on every docs build by [infinito_docs/generators/integrations.py](../../roles/web-app-docs/files/python/infinito_docs/generators/integrations.py) and reached from [docs/architecture/integrations.rst](../architecture/integrations.rst); [027-integration-matrix.md](027-integration-matrix.md) states where an integration's truth lives and which ones are still open. **Wired** edges are integrations already active through an infinito-native service flag in the row role's `meta/services.yml` (e.g. `sso`→keycloak, `matomo`, `prometheus`, `email`→mailu), derived by scanning the repository, no action required. **Declared** edges are upstream app↔app plugins wired as addons. **Open** edges are verified upstream plugins that nothing declares yet, the backlog this requirement commits to wire. **Paid** edges are the same as open but gated behind a commercial tier (opt-in, operator-enabled). No committed copy of the map exists to drift, so there is nothing to regenerate by hand; the `meta/addons/` migrations move an edge from open to declared over time.
15. **Scope is the full backlog, executed in phases.** The definition of done is the FULL set: every ✅ cell in the matrix wired as an addon, and an exhaustive per-`web-app-*`-role upstream-plugin survey completed so the ✅ layer is exhaustive rather than hub-biased. Delivery is phased: the shared contract plus the first-slice roles land first (see [Implementation Strategy](#implementation-strategy)), then the remaining roles and ✅ wirings sweep incrementally, but no phase reduces scope. The requirement is not complete until the full ✅ set is wired and every `web-app-*` role surveyed.
16. **The integration matrix is a generated artifact with a sanctioned generator.** The generator lives under `cli/build/docs/integration_matrix/` and runs through `make integration-matrix`; `docs/` holds only the rendered page. It MUST satisfy the repo lints: its `EDGES` data moves into a sibling data module so the entrypoint stays under the 500-line limit, it uses the shared project-root helper instead of a local `__file__` upward walk, and it carries no self-path literal or `requirement NNN` token. The generated `027-integration-matrix.md` carries a `generated-artifact / not-a-requirement` head marker so the requirements-completeness/archival lint skips it. Dead upstream URLs in `EDGES` are fixed or dropped on regeneration.
16. **The integration matrix is a generated artifact with a sanctioned generator.** The generator is a [docs-build generator](../../roles/web-app-docs/files/python/infinito_docs/generators/integrations.py) that emits its pages on every docs build, so no copy is committed and none can drift. It MUST satisfy the repo lints: it stays under the 500-line limit, it uses the shared project-root helper instead of a local `__file__` upward walk, and it carries no self-path literal or `requirement NNN` token. It derives every edge from the repository, so a dead upstream URL cannot outlive the declaration it came from.
17. **Only bridging addons add a service block.** A `<target>:` service block is added to the row role ONLY for addons that declare a `bridges:` dependency (per Decision 3), i.e. the in-repo cross-role integrations; a plain addon with no cross-role dependency adds no service block. Each bridging addon's `<target>:` block carries the standard dynamic `enabled`/`shared` flags with matching true/false `meta/variants.yml` coverage, and (for web-facing pairs) a co-deploy Playwright spec. Across the in-repo ✅ set this is a bounded, mechanical expansion scoped to the bridging integrations, accepted as part of Decision 15's full scope.
18. **Numbering.** This document is `026-unified-addon-syntax.md`, forming the trio `025-mcp-role-integration` → `026-unified-addon-syntax` → `027-integration-matrix`. The pre-existing `025-mcp-role-integration` vs `025-web-app-matrix-ansible-flavor` number collision is out of this requirement's scope; a separate doc owns its resolution.
@@ -55,7 +55,7 @@ Each row is a migration target. The "Bridges" column is the cross-role dependenc
| [web-app-nextcloud](../../roles/web-app-nextcloud/) | `plugin` | `bbb, onlyoffice, richdocuments, sociallogin, spreed, user_ldap, whiteboard, xwiki` | `sociallogin` → `web-app-keycloak`, `user_ldap` → `svc-db-openldap` | `vars/plugins/` |
| [web-app-wordpress](../../roles/web-app-wordpress/) | `plugin`, `mu_plugin` | `daggerhart-openid-connect-generic, wp-discourse` (+ mu-plugins) | OIDC → `web-app-keycloak`, `wp-discourse` → `web-app-discourse` | `tasks/plugins/` |
| [web-app-mediawiki](../../roles/web-app-mediawiki/) | `extension` | `PluggableAuth, OpenIDConnect` | `OpenIDConnect` → `web-app-keycloak` | [vars/main.yml](../../roles/web-app-mediawiki/vars/main.yml) |
| [web-app-xwiki](../../roles/web-app-xwiki/) | `plugin` | `oidc-authenticator, ldap-authenticator, matomo` | `oidc` → `web-app-keycloak`, `ldap` → `svc-db-openldap`, `matomo` → `web-app-matomo` | [meta/services.yml](../../roles/web-app-xwiki/meta/services.yml) `xwiki.plugins` |
| [web-app-xwiki](../../roles/web-app-xwiki/) | `plugin` | `oidc-authenticator, ldap-authenticator, ai-llm` | `sso` → `web-app-keycloak`, `ldap` → `svc-db-openldap`, `litellm` → `web-app-litellm` | [meta/services.yml](../../roles/web-app-xwiki/meta/services.yml) `xwiki.plugins` |
| [web-app-joomla](../../roles/web-app-joomla/) | `plugin` | `plg_system_keycloak` | `sso` → `web-app-keycloak` | [tasks/08_oidc_plugin.yml](../../roles/web-app-joomla/tasks/08_oidc_plugin.yml) |
| [web-app-matrix](../../roles/web-app-matrix/) | `bridge` | `mautrix-whatsapp, mautrix-telegram, mautrix-signal, mautrix-slack, mautrix-meta` | external networks (no in-repo service); each owns a DB credential | `vars/bridges.yml` |
| [web-app-discourse](../../roles/web-app-discourse/) | `plugin` | `docker_manager, discourse-activity-pub, discourse-akismet, discourse-ldap-auth` | `discourse-ldap-auth` → `svc-db-openldap` | [meta/services.yml](../../roles/web-app-discourse/meta/services.yml) `discourse.plugins` |
@@ -79,15 +79,9 @@ A grep for `meta/addons/` before implementation MUST return nothing, and that ba
## Cross-Role Integration Matrix
The companion [027-integration-matrix.md](027-integration-matrix.md) is a generated role×role matrix over the entity names of every `web-app-*` and `web-svc-*` role. Each cell marks whether the **row** role wires in the **column** role:
The map of which role wires in which is derived from the roles themselves on every docs build, by [infinito_docs/generators/integrations.py](../../roles/web-app-docs/files/python/infinito_docs/generators/integrations.py), and reached from [docs/architecture/integrations.rst](../architecture/integrations.rst). Its two sources are the declarations this requirement governs: a **wired** edge comes from a top-level service key in the row role's `meta/services.yml`, and a **declared** edge from the `bridges:` entry of a `meta/addons/*.yml`. Edges are directional — the row hosts the flag or the plugin — so a bidirectional pair such as `nextcloud`↔`openproject` appears twice. It currently covers 98 roles with **811 wired edges plus 99 bridged addon edges**.
- ☑️ : already wired via an infinito-native service flag in the row role's `meta/services.yml` (cell links to that declaration);
- ✅ : a verified upstream app↔app plugin exists but is not yet declared as an addon (cell links to the plugin page), the backlog;
- 🪙 : same as ✅ but gated behind a commercial / paid tier;
- ❌ : no known integration;
- `—` : diagonal (same role).
The matrix is **directional** (the row hosts the plugin/flag); bidirectional pairs such as `nextcloud`↔`openproject` carry a symbol in both cells. It is generated by [cli/build/docs/integration_matrix](../../cli/build/docs/integration_matrix/__main__.py): ☑️ edges are scanned from every role's `meta/services.yml` (the `sso`/`matomo`/`prometheus`/`email`/`dashboard`/`css`/`logout`/`cdn`/`coturn`/`collabora`/`onlyoffice`/`libretranslate` keys), and ✅/🪙 edges come from a curated, web-verified `EDGES` map. It MUST be regenerated rather than hand-edited. At creation time it covers 82 roles with **523 infinito-native (☑️) wired edges plus 96 curated upstream (✅/🪙) edges**. The ☑️ layer is exhaustive (it is scanned from the repo); the ✅/🪙 layer is **not yet exhaustive** and grows as each role's upstream plugin ecosystem is researched in depth (e.g. a MediaWiki deep-dive added `DiscourseSsoConsumer`, `PeerTubeEmbed`, `MachineTranslation`→libretranslate, `Extension:AWS`→minio, and an LLM editor→openwebui). The ✅/🪙 set is the **backlog**: every ✅ pair is an addon this requirement commits to declare and wire (see Acceptance Criteria), and completing the per-role upstream survey is itself an acceptance criterion.
Both layers are exhaustive over what the repository declares, which is exactly why the backlog lives outside them: an **open** pair, where a verified upstream plugin exists but nothing declares it, has no generated representation at all. Moving a pair from open to declared is the per-role upstream survey, and that survey is itself an acceptance criterion of this requirement (a MediaWiki deep-dive, for instance, added `DiscourseSsoConsumer`, `PeerTubeEmbed`, `MachineTranslation`→libretranslate, `Extension:AWS`→minio and an LLM editor→openwebui). [027-integration-matrix.md](027-integration-matrix.md) states where each layer's truth lives and why no frozen copy of the map is kept.
## Target Schema
@@ -213,7 +207,8 @@ Rules:
- Credentials for proprietary / external (non-self-hosted) providers MUST be declared once in `group_vars/all/18_api.yml` under the `API` mapping (a key per provider, e.g. `cloudflare`, `telegram`, `github`, `google`, `jira`), sourced from the vault and defaulting to empty.
- All access to those values MUST go through the `lookup('api', '<provider>.<key>')` plugin ([plugins/lookup/api.py](../../plugins/lookup/api.py)), giving the proprietary-API logic a single point to change.
- An integration whose partner is self-hostable in-repo (e.g. `gitlab`) is wired as a local `bridges:` integration gated on the partner role being present. An integration whose target is purely proprietary / external (e.g. `github`, `google`, `jira`) carries NO `bridges:` and MUST be enabled only when its `lookup('api', '<provider>.client_id')` and `client_secret` are present.
- An integration whose partner is self-hostable in-repo (e.g. `gitlab`) is wired as a local `bridges:` integration gated on the partner role being present. An integration whose target is purely proprietary / external (e.g. `github`, `google`, Atlassian Cloud) carries NO `bridges:` and MUST be enabled only when its `lookup('api', '<provider>.client_id')` and `client_secret` are present.
- `jira` names two different things and the rule splits along that line. The in-repo role `web-app-jira` is a self-hostable partner, so `roles/web-app-gitlab/meta/addons/jira.yml`, `roles/web-app-matrix/meta/addons/hookshot-jira.yml` and `roles/web-app-mattermost/meta/addons/mattermost-plugin-jira.yml` declare `bridges: [jira]`; the role is `lifecycle: eol`, so those three stay dormant behind a literal `false` rather than being removed, which the `eol-dependency` rule enforces. Atlassian Cloud is the proprietary target, reached by `roles/web-app-nextcloud/meta/addons/integration_jira.yml` through `lookup('api_enabled', 'jira')` with no `bridges:` at all.
### Per-addon Playwright specs
@@ -265,7 +260,7 @@ Rules:
- [x] [web-app-nextcloud](../../roles/web-app-nextcloud/) declares each plugin in `meta/addons/`; `sociallogin` bridges `sso` and `user_ldap` bridges `ldap`, both present in `meta/services.yml`; each plugin's full runtime config from `vars/plugins/` is absorbed into its addon `config:` block and the superseded `vars/plugins/*.yml` files are deleted; the install/enable/configure tasks read the addon `config:` payload; deploy behaviour is unchanged.
- [ ] [web-app-wordpress](../../roles/web-app-wordpress/) declares its `plugin` and `mu_plugin` entries in `meta/addons/`; the OIDC plugin bridges `sso` and `wp-discourse` bridges the Discourse service.
- [ ] [web-app-mediawiki](../../roles/web-app-mediawiki/) declares `PluggableAuth` and `OpenIDConnect` in `meta/addons/` with `mechanism: extension`; `OpenIDConnect` bridges `sso`.
- [ ] [web-app-xwiki](../../roles/web-app-xwiki/) declares its three plugins in `meta/addons/`, preserving `version` pins; bridges `sso`, `ldap`, and `matomo` resolve to service blocks.
- [ ] [web-app-xwiki](../../roles/web-app-xwiki/) declares its three plugins in `meta/addons/`, preserving `version` pins; bridges `sso`, `ldap`, and `litellm` resolve to service blocks.
- [ ] [web-app-joomla](../../roles/web-app-joomla/) declares `plg_system_keycloak` in `meta/addons/` with `mechanism: plugin`, `source: built`, and `bridges: [sso]`.
- [ ] [web-app-matrix](../../roles/web-app-matrix/) declares each mautrix network bridge in `meta/addons/` with `mechanism: bridge`, `required: false`, `enabled: false` by default, and its DB credential referenced from `meta/secrets.yml`; `vars/bridges.yml` is absorbed and deleted.
- [ ] [web-app-discourse](../../roles/web-app-discourse/) declares its plugins in `meta/addons/` with `mechanism: plugin`; `discourse-ldap-auth` carries `bridges: [ldap]` resolving to the `ldap` service block, while `docker_manager`, `discourse-activity-pub`, and `discourse-akismet` carry no `bridges`.
@@ -278,7 +273,7 @@ Rules:
### Cross-role integration (matrix backlog)
- [ ] [027-integration-matrix.md](027-integration-matrix.md) is regenerated from [cli/build/docs/integration_matrix](../../cli/build/docs/integration_matrix/__main__.py) and committed; a check fails if the committed matrix differs from a fresh generation (no hand edits). The generator derives ☑️ edges by scanning every role's `meta/services.yml` integration keys and renders ✅/🪙 from the curated `EDGES` map.
- [ ] The matrix is emitted by [infinito_docs/generators/integrations.py](../../roles/web-app-docs/files/python/infinito_docs/generators/integrations.py) on every docs build, so no copy is committed and none can drift. The generator derives ☑️ edges by scanning every role's `meta/services.yml` keys and ✅ edges from every `meta/addons/*.yml` `bridges:` list.
- [ ] The matrix axes contain the entity name of **every** `web-app-*` and `web-svc-*` role; a role added later forces a matrix regeneration.
- [ ] ☑️ cells (infinito-native service flags already declared in `meta/services.yml`) are NOT re-declared as `meta/addons/` addons; they remain service integrations, and only ✅/🪙 cells become new addons.
- [ ] A per-role upstream-plugin survey is completed for **every** `web-app-*` role, not only the integration hubs, so the ✅/🪙 layer is exhaustive rather than hub-biased. Each survey records, per partner role, the real upstream plugin (or its documented absence) with a source URL; the MediaWiki survey (which surfaced `DiscourseSsoConsumer`, `PeerTubeEmbed`, `MachineTranslation`→libretranslate, `Extension:AWS`→minio, an LLM editor→openwebui) is the template for depth.
@@ -318,7 +313,7 @@ Rules:
- [x] Each addon spec guards on the addon being enabled (`skipUnlessAddonEnabled`) via the `ADDON_<ID>_ENABLED` flags rendered into `templates/playwright.env.j2`.
- [x] Per-addon task hooks live in `roles/<role>/tasks/addons/<id>.yml`; the former per-plugin task logic was moved there for a uniform schema.
- [x] Proprietary / external API credentials are declared once in `group_vars/all/18_api.yml` (`API` mapping) and accessed exclusively through the `lookup('api', '<provider>.<key>')` plugin; the existing cloudflare/telegram code routes through it.
- [x] Self-hostable integration partners (e.g. `gitlab`) are wired as local `bridges:` integrations; purely proprietary targets (`github`, `google`, `jira`) carry no `bridges:` and are enabled only when their `API` credentials are present.
- [x] Self-hostable integration partners (e.g. `gitlab`, and the EOL `jira`, whose three bridged addons stay dormant behind a literal `false`) are wired as local `bridges:` integrations; purely proprietary targets (`github`, `google`, Atlassian Cloud via `integration_jira`) carry no `bridges:` and are enabled only when their `API` credentials are present.
- [ ] Each cross-role integration spec drives the full OAuth connect-flow and is green when both the host and partner roles are deployed (deploy-gated; skips cleanly when the integration is not provisioned).
### Documentation
+53 -114
View File
@@ -1,125 +1,64 @@
# 027 - Integration Matrix
> Generated by `make integration-matrix`. Do not edit by hand, edit the generator's `EDGES` map and re-run.
Companion to [026-unified-addon-syntax.md](026-unified-addon-syntax.md): how a cross-role
integration is declared, where its truth lives, and which ones are still open.
Companion artifact for [026-unified-addon-syntax.md](026-unified-addon-syntax.md).
Axes are the **entity names** of every `web-app-*` and `web-svc-*` role.
A cell marks whether the **row** role ships an addon/plugin that wires in the **column** role.
This page replaces the generated role×role grid that used to live here. The grid listed every
pair of `web-app-*` and `web-svc-*` entity names, which grows with the square of the role
count, and it was only refreshed when somebody ran its generator by hand. Nothing compared it
against the repository, so it drifted silently: at the point of removal its hardcoded axes were
missing fourteen roles, carried one role that no longer existed, and therefore dropped 84 of the
574 integration edges the repository actually declares.
## Legend
## Where the truth lives
| Symbol | Meaning |
|---|---|
| ☑️ | Already wired via an infinito-native service flag in the row role's `meta/services.yml` (group-gated). Links to that declaration. |
| ✅ | A verified upstream app↔app plugin exists but is not yet declared as an addon. Links to the plugin page. This is the backlog. |
| 🪙 | Integration exists but is gated behind a commercial / paid tier. |
| ❌ | No known integration to wire these two. |
| — | Same role (diagonal). |
An integration has exactly two possible homes, and both are machine-readable.
Notes:
**A service flag in the row role's `meta/services.yml`.** The infinito-native integrations are
plain service keys — `sso` (keycloak), `matomo`, `prometheus`, `email` (mailu), `dashboard`,
`css`, `logout`, `cdn`, `coturn`, `collabora`, `onlyoffice`, `libretranslate` and `litellm`. Each
is group-gated, so the flag tells you both that the integration exists and when it is active.
`lookup('roles_with_service', '<key>')` answers "who consumes this" from the repository itself;
no second list needs to agree with it.
- The matrix is **directional**: the row hosts the plugin/flag. Bidirectional pairs (e.g. `nextcloud`↔`openproject`) carry a symbol in both cells, each linking to that side.
- ☑️ edges are derived automatically by scanning every role's `meta/services.yml` for integration service keys (`sso`→keycloak, `matomo`→matomo, `prometheus`→prometheus, `email`→mailu, `dashboard`, `css`, `logout`, `cdn`, `coturn`, `collabora`, `onlyoffice`, `libretranslate`, `litellm`). `ldap`/`redis`/`mariadb` map to `svc-db-*` roles that are off these axes and are not shown. The `litellm` column is the AI gateway surface of [036](036-native-ai-gateway-integration.md): the service key resolves to `svc-ai-litellm`, which is off-axis, so the edge is drawn to the `litellm` entity that fronts it.
- `→ keycloak` ☑️ cells are the central `sso` service; a ✅/🪙 on `→ keycloak` instead means a role-local OIDC/SAML addon path beyond the central service.
- Native ActivityPub federation between fediverse roles (`mastodon`, `peertube`, `pixelfed`, `funkwhale`, `mobilizon`, `bookwyrm`, `socialhome`) needs **no plugin** and is therefore not a ✅ edge unless an installable connector exists.
**An addon under the row role's `meta/addons/`.** Everything that needs an upstream plugin lives
there, with its `mechanism` and, for an in-repo partner, a `bridges:` entry resolving to the
service key it wires. The addon contract and its lints are requirement
[026](026-unified-addon-syntax.md).
## Matrix
A pair that appears in neither place has no integration. That is the whole state space, and it is
queryable — which is why a frozen copy of it was worth less than the query.
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| **akaunting** | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-akaunting/meta/services.yml) | ❌ | **akaunting** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **akaunting** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-akaunting/meta/services.yml) | ❌ | ❌ | ❌ | **akaunting** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-akaunting/meta/services.yml) | ❌ | [☑️](../../roles/web-app-akaunting/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **akaunting** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **akaunting** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-akaunting/meta/services.yml) | **akaunting** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **akaunting** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-akaunting/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **akaunting** | [☑️](../../roles/web-app-akaunting/meta/services.yml) | ❌ | ❌ | ❌ |
| **baserow** | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-baserow/meta/services.yml) | ❌ | **baserow** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **baserow** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [🪙](https://baserow.io/user-docs/configure-openid-connect-for-oauth-2-sso) | ❌ | ❌ | ❌ | **baserow** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-baserow/meta/services.yml) | ❌ | [☑️](../../roles/web-app-baserow/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **baserow** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **baserow** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-baserow/meta/services.yml) | **baserow** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **baserow** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-baserow/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **baserow** | [☑️](../../roles/web-app-baserow/meta/services.yml) | ❌ | ❌ | ❌ |
| **bigbluebutton** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | ❌ | **bigbluebutton** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bigbluebutton** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://docs.bigbluebutton.org/greenlight/v3/external-authentication/) | ❌ | ❌ | ❌ | **bigbluebutton** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | ❌ | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bigbluebutton** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bigbluebutton** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | **bigbluebutton** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bigbluebutton** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bigbluebutton** | [☑️](../../roles/web-app-bigbluebutton/meta/services.yml) | ❌ | ❌ | ❌ |
| **bluesky** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bluesky/meta/services.yml) | ❌ | **bluesky** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bluesky** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bluesky/meta/services.yml) | ❌ | ❌ | ❌ | **bluesky** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bluesky/meta/services.yml) | ❌ | [☑️](../../roles/web-app-bluesky/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bluesky** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bluesky** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bluesky/meta/services.yml) | **bluesky** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bluesky** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bluesky/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bluesky** | [☑️](../../roles/web-app-bluesky/meta/services.yml) | ❌ | ❌ | ❌ |
| **bookwyrm** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | ❌ | **bookwyrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bookwyrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | ❌ | ❌ | ❌ | **bookwyrm** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | ❌ | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bookwyrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bookwyrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | **bookwyrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bookwyrm** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bookwyrm** | [☑️](../../roles/web-app-bookwyrm/meta/services.yml) | ❌ | ❌ | ❌ |
| **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | ❌ | **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | ❌ | ❌ | ❌ | **bridgy-fed** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | ❌ | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **bridgy-fed** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **bridgy-fed** | [☑️](../../roles/web-app-bridgy-fed/meta/services.yml) | ❌ | ❌ | ❌ |
| **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | [☑️](../../roles/web-app-chess/meta/services.yml) | ❌ | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-chess/meta/services.yml) | ❌ | ❌ | ❌ | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-chess/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-chess/meta/services.yml) | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **chess** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-chess/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **chess** | [☑️](../../roles/web-app-chess/meta/services.yml) | ❌ | ❌ | ❌ |
| **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | [☑️](../../roles/web-app-confluence/meta/services.yml) | ❌ | **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-confluence/meta/services.yml) | ❌ | ❌ | ❌ | **confluence** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-confluence/meta/services.yml) | ❌ | [☑️](../../roles/web-app-confluence/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-confluence/meta/services.yml) | **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **confluence** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-confluence/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **confluence** | [☑️](../../roles/web-app-confluence/meta/services.yml) | ❌ | ❌ | ❌ |
| **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-dashboard/meta/services.yml) | ❌ | ❌ | ❌ | **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-dashboard/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-dashboard/meta/services.yml) | **dashboard** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **dashboard** | ❌ | ❌ | [☑️](../../roles/web-app-dashboard/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-dashboard/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **dashboard** | [☑️](../../roles/web-app-dashboard/meta/services.yml) | ❌ | ❌ | ❌ |
| **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-decidim/meta/services.yml) | — | **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-decidim/meta/services.yml) | ❌ | ❌ | ❌ | **decidim** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-decidim/meta/services.yml) | ❌ | [☑️](../../roles/web-app-decidim/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-decidim/meta/services.yml) | **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **decidim** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-decidim/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **decidim** | [☑️](../../roles/web-app-decidim/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **discourse** | ❌ | ❌ | [✅](https://github.com/discourse/discourse-bbb) | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-discourse/meta/services.yml) | ❌ | **discourse** | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **discourse** | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/discourse/discourse-jitsi) | ❌ | [✅](https://github.com/discourse/discourse-openid-connect) | ❌ | ❌ | ❌ | **discourse** | [☑️](../../roles/web-app-discourse/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-discourse/meta/services.yml) | [✅](https://github.com/discourse/discourse-activity-pub) | [✅](https://github.com/discourse/discourse-matomo-analytics) | [✅](https://github.com/discourse/discourse-chat-integration) | [✅](https://github.com/discourse/discourse-chat-integration) | ❌ | ❌ | **discourse** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **discourse** | ❌ | [✅](https://github.com/discourse/discourse-ai) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/discourse/discourse-prometheus) | **discourse** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **discourse** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-discourse/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **discourse** | [☑️](../../roles/web-app-discourse/meta/services.yml) | ❌ | ❌ | ❌ |
| **erpnext** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-erpnext/meta/services.yml) | ❌ | **erpnext** | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **erpnext** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://docs.frappe.io/framework/user/en/guides/integration/openid_connect_and_frappe_social_login) | ❌ | ❌ | ❌ | **erpnext** | ❌ | ❌ | ❌ | [✅](https://docs.frappe.io/erpnext/user/manual/en/setting-up-email-account) | ❌ | [☑️](../../roles/web-app-erpnext/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **erpnext** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **erpnext** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-erpnext/meta/services.yml) | **erpnext** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **erpnext** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-erpnext/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **erpnext** | [☑️](../../roles/web-app-erpnext/meta/services.yml) | ❌ | ❌ | ❌ |
| **espocrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-espocrm/meta/services.yml) | ❌ | **espocrm** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **espocrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://docs.espocrm.com/administration/oidc/) | ❌ | ❌ | ❌ | **espocrm** | ❌ | ❌ | ❌ | [✅](https://docs.espocrm.com/user-guide/imap-smtp-configuration/) | ❌ | [☑️](../../roles/web-app-espocrm/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **espocrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **espocrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-espocrm/meta/services.yml) | **espocrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **espocrm** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-espocrm/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **espocrm** | [☑️](../../roles/web-app-espocrm/meta/services.yml) | ❌ | ❌ | ❌ |
| **fediwall** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fediwall/meta/services.yml) | ❌ | **fediwall** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fediwall** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fediwall/meta/services.yml) | ❌ | ❌ | ❌ | **fediwall** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fediwall/meta/services.yml) | ❌ | [☑️](../../roles/web-app-fediwall/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **fediwall** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fediwall** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fediwall/meta/services.yml) | **fediwall** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fediwall** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fediwall/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **fediwall** | [☑️](../../roles/web-app-fediwall/meta/services.yml) | ❌ | ❌ | ❌ |
| **fider** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fider/meta/services.yml) | ❌ | **fider** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | **fider** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fider/meta/services.yml) | ❌ | ❌ | ❌ | **fider** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fider/meta/services.yml) | ❌ | [☑️](../../roles/web-app-fider/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **fider** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fider** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fider/meta/services.yml) | **fider** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fider** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fider/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **fider** | [☑️](../../roles/web-app-fider/meta/services.yml) | ❌ | ❌ | ❌ |
| **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | ❌ | ❌ | **flowise** | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-flowise/meta/services.yml) | **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **flowise** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **flowise** | [☑️](../../roles/web-app-flowise/meta/services.yml) | ❌ | ❌ | ❌ |
| **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-friendica/meta/services.yml) | ❌ | **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-friendica/meta/services.yml) | ❌ | ❌ | ❌ | **friendica** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-friendica/meta/services.yml) | ❌ | [✅](https://github.com/friendica/friendica-addons) | ❌ | ❌ | ❌ | ❌ | **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-friendica/meta/services.yml) | **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **friendica** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-friendica/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **friendica** | [☑️](../../roles/web-app-friendica/meta/services.yml) | ❌ | ❌ | ❌ |
| **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | ❌ | **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | ❌ | ❌ | ❌ | **funkwhale** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | ❌ | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **funkwhale** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **funkwhale** | [☑️](../../roles/web-app-funkwhale/meta/services.yml) | ❌ | ❌ | ❌ |
| **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | ❌ | **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | ❌ | ❌ | ❌ | **fusiondirectory** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | ❌ | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **fusiondirectory** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **fusiondirectory** | [☑️](../../roles/web-app-fusiondirectory/meta/services.yml) | ❌ | ❌ | ❌ |
| **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitea/meta/services.yml) | ❌ | **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitea/meta/services.yml) | ❌ | ❌ | ❌ | **gitea** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitea/meta/services.yml) | ❌ | [☑️](../../roles/web-app-gitea/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitea/meta/services.yml) | **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **gitea** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitea/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **gitea** | [☑️](../../roles/web-app-gitea/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **gitlab** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://docs.gitlab.com/user/project/integrations/confluence/) | [☑️](../../roles/web-app-gitlab/meta/services.yml) | ❌ | **gitlab** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **gitlab** | — | ❌ | [✅](https://docs.gitlab.com/integration/jenkins/) | [✅](https://docs.gitlab.com/integration/jira/) | ❌ | ❌ | [✅](https://docs.gitlab.com/integration/saml/) | ❌ | ❌ | ❌ | **gitlab** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitlab/meta/services.yml) | ❌ | [☑️](../../roles/web-app-gitlab/meta/services.yml) | [✅](https://docs.gitlab.com/user/project/integrations/matrix/) | [✅](https://docs.gitlab.com/user/project/integrations/mattermost_notifications/) | ❌ | ❌ | **gitlab** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **gitlab** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitlab/meta/services.yml) | **gitlab** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **gitlab** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-gitlab/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **gitlab** | [☑️](../../roles/web-app-gitlab/meta/services.yml) | ❌ | ❌ | ❌ |
| **hugo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-hugo/meta/services.yml) | ❌ | **hugo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **hugo** | ❌ | — | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-hugo/meta/services.yml) | ❌ | ❌ | ❌ | **hugo** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-hugo/meta/services.yml) | ❌ | [☑️](../../roles/web-app-hugo/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **hugo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **hugo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-hugo/meta/services.yml) | **hugo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **hugo** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-hugo/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **hugo** | [☑️](../../roles/web-app-hugo/meta/services.yml) | ❌ | ❌ | ❌ |
| **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jenkins/meta/services.yml) | ❌ | **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://plugins.jenkins.io/gitea/) | **jenkins** | [✅](https://plugins.jenkins.io/gitlab-plugin) | ❌ | — | ❌ | ❌ | ❌ | [✅](https://plugins.jenkins.io/oic-auth/) | ❌ | ❌ | ❌ | **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jenkins/meta/services.yml) | ❌ | [✅](https://plugins.jenkins.io/mattermost/) | ❌ | ❌ | **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://plugins.jenkins.io/prometheus) | **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jenkins** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jenkins/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **jenkins** | [☑️](../../roles/web-app-jenkins/meta/services.yml) | ❌ | ❌ | ❌ |
| **jira** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jira/meta/services.yml) | ❌ | **jira** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jira** | ❌ | ❌ | ❌ | — | ❌ | ❌ | [☑️](../../roles/web-app-jira/meta/services.yml) | ❌ | ❌ | ❌ | **jira** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jira/meta/services.yml) | ❌ | [☑️](../../roles/web-app-jira/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **jira** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jira** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jira/meta/services.yml) | **jira** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jira** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jira/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **jira** | [☑️](../../roles/web-app-jira/meta/services.yml) | ❌ | ❌ | ❌ |
| **jitsi** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jitsi/meta/services.yml) | ❌ | **jitsi** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jitsi** | ❌ | ❌ | ❌ | ❌ | — | ❌ | [☑️](../../roles/web-app-jitsi/meta/services.yml) | ❌ | ❌ | ❌ | **jitsi** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jitsi/meta/services.yml) | ❌ | [☑️](../../roles/web-app-jitsi/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **jitsi** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jitsi** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jitsi/meta/services.yml) | **jitsi** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **jitsi** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-jitsi/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **jitsi** | [☑️](../../roles/web-app-jitsi/meta/services.yml) | ❌ | ❌ | ❌ |
| **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-joomla/meta/services.yml) | ❌ | **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | — | [✅](../../roles/web-app-joomla/files/joomla-oidc-plugin/) | ❌ | ❌ | ❌ | **joomla** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-joomla/meta/services.yml) | ❌ | [✅](https://extensions.joomla.org/extension/itcs-matomo/) | ❌ | ❌ | ❌ | ❌ | **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-joomla/meta/services.yml) | **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **joomla** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-joomla/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **joomla** | [☑️](../../roles/web-app-joomla/meta/services.yml) | ❌ | ❌ | ❌ |
| **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-keycloak/meta/services.yml) | ❌ | **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | **keycloak** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-keycloak/meta/services.yml) | ❌ | [☑️](../../roles/web-app-keycloak/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-keycloak/meta/services.yml) | **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **keycloak** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-keycloak/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **keycloak** | [☑️](../../roles/web-app-keycloak/meta/services.yml) | ❌ | ❌ | ❌ |
| **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-kix/meta/services.yml) | ❌ | **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-kix/meta/services.yml) | — | ❌ | ❌ | **kix** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-kix/meta/services.yml) | ❌ | [☑️](../../roles/web-app-kix/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-kix/meta/services.yml) | **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **kix** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-kix/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **kix** | [☑️](../../roles/web-app-kix/meta/services.yml) | ❌ | ❌ | ❌ |
| **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-lam/meta/services.yml) | ❌ | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-lam/meta/services.yml) | ❌ | — | ❌ | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-lam/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-lam/meta/services.yml) | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **lam** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-lam/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **lam** | [☑️](../../roles/web-app-lam/meta/services.yml) | ❌ | ❌ | ❌ |
| **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-listmonk/meta/services.yml) | ❌ | **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-listmonk/meta/services.yml) | ❌ | ❌ | — | **listmonk** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-listmonk/meta/services.yml) | ❌ | [☑️](../../roles/web-app-listmonk/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-listmonk/meta/services.yml) | **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **listmonk** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-listmonk/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **listmonk** | [☑️](../../roles/web-app-listmonk/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-litellm/meta/services.yml) | ❌ | **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-litellm/meta/services.yml) | ❌ | ❌ | ❌ | **litellm** | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-litellm/meta/services.yml) | **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **litellm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **litellm** | [☑️](../../roles/web-app-litellm/meta/services.yml) | ❌ | ❌ | ❌ |
| **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-littlejs/meta/services.yml) | ❌ | **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-littlejs/meta/services.yml) | ❌ | ❌ | ❌ | **littlejs** | ❌ | — | ❌ | [☑️](../../roles/web-app-littlejs/meta/services.yml) | ❌ | [☑️](../../roles/web-app-littlejs/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-littlejs/meta/services.yml) | **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **littlejs** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-littlejs/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **littlejs** | [☑️](../../roles/web-app-littlejs/meta/services.yml) | ❌ | ❌ | ❌ |
| **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-magento/meta/services.yml) | ❌ | **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-magento/meta/services.yml) | ❌ | ❌ | ❌ | **magento** | ❌ | ❌ | — | [☑️](../../roles/web-app-magento/meta/services.yml) | ❌ | [☑️](../../roles/web-app-magento/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-magento/meta/services.yml) | **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **magento** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-magento/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **magento** | [☑️](../../roles/web-app-magento/meta/services.yml) | ❌ | ❌ | ❌ |
| **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mailu/meta/services.yml) | ❌ | **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mailu/meta/services.yml) | ❌ | ❌ | ❌ | **mailu** | ❌ | ❌ | ❌ | — | ❌ | [☑️](../../roles/web-app-mailu/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mailu/meta/services.yml) | **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mailu** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mailu/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mailu** | [☑️](../../roles/web-app-mailu/meta/services.yml) | ❌ | ❌ | ❌ |
| **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mastodon/meta/services.yml) | ❌ | **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mastodon/meta/services.yml) | ❌ | ❌ | ❌ | **mastodon** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mastodon/meta/services.yml) | — | [☑️](../../roles/web-app-mastodon/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mastodon/meta/services.yml) | **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mastodon** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mastodon/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mastodon** | [☑️](../../roles/web-app-mastodon/meta/services.yml) | ❌ | ❌ | ❌ |
| **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-matomo/meta/services.yml) | ❌ | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-matomo/meta/services.yml) | ❌ | ❌ | ❌ | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-matomo/meta/services.yml) | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **matomo** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-matomo/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **matomo** | [☑️](../../roles/web-app-matomo/meta/services.yml) | ❌ | ❌ | ❌ |
| **matrix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-matrix/meta/services.yml) | ❌ | **matrix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **matrix** | [✅](https://github.com/matrix-org/matrix-hookshot) | ❌ | ❌ | [✅](https://github.com/matrix-org/matrix-hookshot) | [✅](https://github.com/element-hq/element-web/blob/develop/docs/jitsi.md) | ❌ | [✅](https://matrix-org.github.io/synapse/latest/openid.html) | ❌ | ❌ | ❌ | **matrix** | [☑️](../../roles/web-app-matrix/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-matrix/meta/services.yml) | ❌ | [☑️](../../roles/web-app-matrix/meta/services.yml) | — | ❌ | ❌ | ❌ | **matrix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **matrix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://matrix-org.github.io/synapse/latest/metrics-howto.html) | **matrix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **matrix** | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-matrix/meta/services.yml) | [☑️](../../roles/web-app-matrix/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **matrix** | [☑️](../../roles/web-app-matrix/meta/services.yml) | ❌ | ❌ | ❌ |
| **mattermost** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mattermost/meta/services.yml) | ❌ | **mattermost** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mattermost** | [✅](https://github.com/mattermost/mattermost-plugin-gitlab) | ❌ | [✅](https://github.com/mattermost/mattermost-plugin-jenkins) | [✅](https://github.com/mattermost/mattermost-plugin-jira) | [✅](https://github.com/mattermost-community/mattermost-plugin-jitsi) | ❌ | [🪙](https://docs.mattermost.com/administration-guide/onboard/sso-saml.html) | ❌ | ❌ | ❌ | **mattermost** | [☑️](../../roles/web-app-mattermost/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-mattermost/meta/services.yml) | ❌ | [☑️](../../roles/web-app-mattermost/meta/services.yml) | ❌ | — | ❌ | ❌ | **mattermost** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mattermost** | ❌ | [✅](https://github.com/mattermost/mattermost-plugin-agents) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/cpanato/mattermost-plugin-alertmanager) | **mattermost** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mattermost** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mattermost/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mattermost** | [☑️](../../roles/web-app-mattermost/meta/services.yml) | ❌ | ❌ | ❌ |
| **mediawiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mediawiki/meta/services.yml) | ❌ | **mediawiki** | [✅](https://www.mediawiki.org/wiki/Extension:DiscourseSsoConsumer) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mediawiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://www.mediawiki.org/wiki/Extension:OpenID_Connect) | ❌ | ❌ | ❌ | **mediawiki** | [☑️](../../roles/web-app-mediawiki/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-mediawiki/meta/services.yml) | ❌ | [✅](https://www.mediawiki.org/wiki/Extension:Matomo) | ❌ | ❌ | — | ❌ | **mediawiki** | [✅](https://www.mediawiki.org/wiki/Extension:AWS) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mediawiki** | ❌ | [✅](https://www.mediawiki.org/wiki/Extension:AIEditingAssistant) | [✅](https://www.mediawiki.org/wiki/Extension:PeerTubeEmbed) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mediawiki/meta/services.yml) | **mediawiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mediawiki** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mediawiki/meta/services.yml) | ❌ | ❌ | ❌ | [✅](https://www.mediawiki.org/wiki/Extension:MachineTranslation) | **mediawiki** | [☑️](../../roles/web-app-mediawiki/meta/services.yml) | ❌ | ❌ | ❌ |
| **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mig/meta/services.yml) | ❌ | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mig/meta/services.yml) | ❌ | ❌ | ❌ | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mig/meta/services.yml) | ❌ | ❌ | ❌ | — | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mig/meta/services.yml) | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mig** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mig/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mig** | [☑️](../../roles/web-app-mig/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-minio/meta/services.yml) | ❌ | **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-minio/meta/services.yml) | ❌ | ❌ | ❌ | **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-minio/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **minio** | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-minio/meta/services.yml) | **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **minio** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-minio/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **minio** | [☑️](../../roles/web-app-minio/meta/services.yml) | ❌ | ❌ | ❌ |
| **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mini-qr/meta/services.yml) | ❌ | **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mini-qr/meta/services.yml) | ❌ | ❌ | ❌ | **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mini-qr/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mini-qr** | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mini-qr/meta/services.yml) | **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mini-qr** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mini-qr/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mini-qr** | [☑️](../../roles/web-app-mini-qr/meta/services.yml) | ❌ | ❌ | ❌ |
| **mobilizon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | ❌ | **mobilizon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mobilizon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | ❌ | ❌ | ❌ | **mobilizon** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | ❌ | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mobilizon** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mobilizon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | **mobilizon** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **mobilizon** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **mobilizon** | [☑️](../../roles/web-app-mobilizon/meta/services.yml) | ❌ | ❌ | ❌ |
| **moodle** | ❌ | ❌ | [✅](https://moodle.org/plugins/mod_bigbluebuttonbn) | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-moodle/meta/services.yml) | ❌ | **moodle** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **moodle** | ❌ | ❌ | ❌ | ❌ | [✅](https://moodle.org/plugins/mod_jitsi) | ❌ | [✅](https://moodle.org/plugins/auth_oidc) | ❌ | ❌ | ❌ | **moodle** | [☑️](../../roles/web-app-moodle/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-moodle/meta/services.yml) | ❌ | [✅](https://moodle.org/plugins/tool_webanalytics) | [✅](https://docs.moodle.org/en/Matrix) | ❌ | ❌ | ❌ | **moodle** | ❌ | ❌ | ❌ | — | ❌ | ❌ | [✅](https://moodle.org/plugins/repository_owncloud) | ❌ | ❌ | ❌ | **moodle** | ❌ | [✅](https://moodle.org/plugins/aiprovider_openwebui) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-moodle/meta/services.yml) | **moodle** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **moodle** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-moodle/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **moodle** | [☑️](../../roles/web-app-moodle/meta/services.yml) | ❌ | ❌ | ❌ |
| **n8n** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | **n8n** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **n8n** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | ❌ | ❌ | **n8n** | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **n8n** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | **n8n** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-n8n/meta/services.yml) | **n8n** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **n8n** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **n8n** | [☑️](../../roles/web-app-n8n/meta/services.yml) | ❌ | ❌ | ❌ |
| **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-navigator/meta/services.yml) | ❌ | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-navigator/meta/services.yml) | ❌ | ❌ | ❌ | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-navigator/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-navigator/meta/services.yml) | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **navigator** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-navigator/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **navigator** | [☑️](../../roles/web-app-navigator/meta/services.yml) | ❌ | ❌ | ❌ |
| **nextcloud** | ❌ | ❌ | [✅](https://apps.nextcloud.com/apps/bbb) | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | ❌ | **nextcloud** | [✅](https://github.com/nextcloud/integration_discourse) | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/nextcloud/integration_openai) | ❌ | ❌ | ❌ | ❌ | **nextcloud** | [✅](https://github.com/nextcloud/integration_gitlab) | ❌ | ❌ | [✅](https://github.com/nextcloud/integration_jira) | ❌ | ❌ | [✅](https://github.com/nextcloud/user_oidc) | ❌ | ❌ | ❌ | **nextcloud** | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | [✅](https://github.com/nextcloud/integration_mastodon) | [✅](https://apps.nextcloud.com/apps/matomo) | [✅](https://github.com/nextcloud/integration_matrix) | [✅](https://github.com/nextcloud/integration_mattermost) | ❌ | ❌ | **nextcloud** | ❌ | ❌ | ❌ | [✅](https://github.com/nextcloud/integration_moodle) | ❌ | ❌ | — | ❌ | ❌ | [✅](https://github.com/nextcloud/integration_openproject) | **nextcloud** | ❌ | [✅](https://github.com/nextcloud/integration_openai) | [✅](https://github.com/nextcloud/integration_peertube) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | **nextcloud** | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/eneiluj/integration_suitecrm) | ❌ | ❌ | [✅](https://apps.nextcloud.com/apps/xwiki) | ❌ | **nextcloud** | [✅](https://github.com/nextcloud/integration_zammad) | ❌ | ❌ | [✅](https://github.com/nextcloud/richdocuments) | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **nextcloud** | [☑️](../../roles/web-app-nextcloud/meta/services.yml) | [✅](https://github.com/ONLYOFFICE/onlyoffice-nextcloud) | ❌ | ❌ |
| **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-odoo/meta/services.yml) | ❌ | **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/OCA/server-auth/tree/16.0/auth_oidc) | ❌ | ❌ | ❌ | **odoo** | ❌ | ❌ | ❌ | [✅](https://www.odoo.com/documentation/latest/applications/general/email_communication.html) | ❌ | [☑️](../../roles/web-app-odoo/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [🪙](https://apps.odoo.com/apps/modules/19.0/nextcloud_odoo_integration) | — | ❌ | ❌ | **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-odoo/meta/services.yml) | **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **odoo** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-odoo/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **odoo** | [☑️](../../roles/web-app-odoo/meta/services.yml) | ❌ | ❌ | ❌ |
| **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opencloud/meta/services.yml) | ❌ | **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opencloud/meta/services.yml) | ❌ | ❌ | ❌ | **opencloud** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opencloud/meta/services.yml) | ❌ | [☑️](../../roles/web-app-opencloud/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opencloud/meta/services.yml) | **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **opencloud** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opencloud/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **opencloud** | [☑️](../../roles/web-app-opencloud/meta/services.yml) | ❌ | ❌ | ❌ |
| **openproject** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openproject/meta/services.yml) | ❌ | **openproject** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **openproject** | [✅](https://www.openproject.org/docs/system-admin-guide/integrations/gitlab-integration/) | ❌ | ❌ | ❌ | ❌ | ❌ | [🪙](https://www.openproject.org/docs/system-admin-guide/authentication/openid-providers/) | ❌ | ❌ | ❌ | **openproject** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openproject/meta/services.yml) | ❌ | [☑️](../../roles/web-app-openproject/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **openproject** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/) | ❌ | ❌ | — | **openproject** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openproject/meta/services.yml) | **openproject** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **openproject** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openproject/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **openproject** | [☑️](../../roles/web-app-openproject/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **opentalk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opentalk/meta/services.yml) | ❌ | **opentalk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **opentalk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opentalk/meta/services.yml) | ❌ | ❌ | ❌ | **opentalk** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opentalk/meta/services.yml) | ❌ | [☑️](../../roles/web-app-opentalk/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **opentalk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **opentalk** | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opentalk/meta/services.yml) | **opentalk** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **opentalk** | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-opentalk/meta/services.yml) | [☑️](../../roles/web-app-opentalk/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **opentalk** | [☑️](../../roles/web-app-opentalk/meta/services.yml) | ❌ | ❌ | ❌ |
| **openwebui** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | **openwebui** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **openwebui** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | ❌ | ❌ | **openwebui** | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **openwebui** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **openwebui** | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openwebui/meta/services.yml) | **openwebui** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **openwebui** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **openwebui** | [☑️](../../roles/web-app-openwebui/meta/services.yml) | ❌ | ❌ | ❌ |
| **peertube** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-peertube/meta/services.yml) | ❌ | **peertube** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **peertube** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://www.npmjs.com/package/peertube-plugin-auth-openid-connect) | ❌ | ❌ | ❌ | **peertube** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-peertube/meta/services.yml) | ❌ | [☑️](../../roles/web-app-peertube/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **peertube** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **peertube** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-peertube/meta/services.yml) | **peertube** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **peertube** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-peertube/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **peertube** | [☑️](../../roles/web-app-peertube/meta/services.yml) | ❌ | ❌ | ❌ |
| **pgadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | ❌ | **pgadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pgadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | ❌ | ❌ | ❌ | **pgadmin** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | ❌ | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **pgadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pgadmin** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | **pgadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pgadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **pgadmin** | [☑️](../../roles/web-app-pgadmin/meta/services.yml) | ❌ | ❌ | ❌ |
| **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpldapadmin/meta/services.yml) | ❌ | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpldapadmin/meta/services.yml) | ❌ | ❌ | ❌ | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpldapadmin/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpldapadmin/meta/services.yml) | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **phpldapadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpldapadmin/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **phpldapadmin** | [☑️](../../roles/web-app-phpldapadmin/meta/services.yml) | ❌ | ❌ | ❌ |
| **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | ❌ | **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | ❌ | ❌ | ❌ | **phpmyadmin** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | ❌ | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **phpmyadmin** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **phpmyadmin** | [☑️](../../roles/web-app-phpmyadmin/meta/services.yml) | ❌ | ❌ | ❌ |
| **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | ❌ | **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | ❌ | ❌ | ❌ | **pixelfed** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | ❌ | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pixelfed** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **pixelfed** | [☑️](../../roles/web-app-pixelfed/meta/services.yml) | ❌ | ❌ | ❌ |
| **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-postmarks/meta/services.yml) | ❌ | **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-postmarks/meta/services.yml) | ❌ | ❌ | ❌ | **postmarks** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-postmarks/meta/services.yml) | ❌ | [☑️](../../roles/web-app-postmarks/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | [☑️](../../roles/web-app-postmarks/meta/services.yml) | **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **postmarks** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-postmarks/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **postmarks** | [☑️](../../roles/web-app-postmarks/meta/services.yml) | ❌ | ❌ | ❌ |
| **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pretix/meta/services.yml) | ❌ | **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://docs.pretix.eu/en/latest/admin/installation/index.html) | ❌ | ❌ | ❌ | **pretix** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pretix/meta/services.yml) | ❌ | [☑️](../../roles/web-app-pretix/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | [☑️](../../roles/web-app-pretix/meta/services.yml) | **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **pretix** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-pretix/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **pretix** | [☑️](../../roles/web-app-pretix/meta/services.yml) | ❌ | ❌ | ❌ |
| **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-prometheus/meta/services.yml) | ❌ | **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-prometheus/meta/services.yml) | ❌ | ❌ | ❌ | **prometheus** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-prometheus/meta/services.yml) | ❌ | [☑️](../../roles/web-app-prometheus/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **prometheus** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-prometheus/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **prometheus** | [☑️](../../roles/web-app-prometheus/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **roulette-wheel** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | ❌ | **roulette-wheel** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **roulette-wheel** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | ❌ | ❌ | ❌ | **roulette-wheel** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | ❌ | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **roulette-wheel** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **roulette-wheel** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | **roulette-wheel** | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **roulette-wheel** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **roulette-wheel** | [☑️](../../roles/web-app-roulette-wheel/meta/services.yml) | ❌ | ❌ | ❌ |
| **shopware** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-shopware/meta/services.yml) | ❌ | **shopware** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **shopware** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-shopware/meta/services.yml) | ❌ | ❌ | ❌ | **shopware** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-shopware/meta/services.yml) | ❌ | [☑️](../../roles/web-app-shopware/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **shopware** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **shopware** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-shopware/meta/services.yml) | **shopware** | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **shopware** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-shopware/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **shopware** | [☑️](../../roles/web-app-shopware/meta/services.yml) | ❌ | ❌ | ❌ |
| **snipe-it** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | ❌ | **snipe-it** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **snipe-it** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | ❌ | ❌ | ❌ | **snipe-it** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | ❌ | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **snipe-it** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **snipe-it** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | **snipe-it** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **snipe-it** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **snipe-it** | [☑️](../../roles/web-app-snipe-it/meta/services.yml) | ❌ | ❌ | ❌ |
| **socialhome** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-socialhome/meta/services.yml) | ❌ | **socialhome** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **socialhome** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-socialhome/meta/services.yml) | ❌ | ❌ | ❌ | **socialhome** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **socialhome** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **socialhome** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-socialhome/meta/services.yml) | **socialhome** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **socialhome** | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-socialhome/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | ❌ | **socialhome** | [☑️](../../roles/web-app-socialhome/meta/services.yml) | ❌ | ❌ | ❌ |
| **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **sphinx** | ❌ | ❌ | ❌ | ❌ |
| **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-suitecrm/meta/services.yml) | ❌ | **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://docs.suitecrm.com/8.x/admin/configuration/saml/8.7.0-saml-configuration/) | ❌ | ❌ | ❌ | **suitecrm** | ❌ | ❌ | ❌ | [✅](https://github.com/SuiteCRM/SuiteCRM) | ❌ | [☑️](../../roles/web-app-suitecrm/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-suitecrm/meta/services.yml) | **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | **suitecrm** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-suitecrm/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **suitecrm** | [☑️](../../roles/web-app-suitecrm/meta/services.yml) | ❌ | ❌ | ❌ |
| **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-taiga/meta/services.yml) | ❌ | **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://github.com/taigaio/taiga-contrib-oidc-auth) | ❌ | ❌ | ❌ | **taiga** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-taiga/meta/services.yml) | ❌ | [☑️](../../roles/web-app-taiga/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-taiga/meta/services.yml) | **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | **taiga** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-taiga/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **taiga** | [☑️](../../roles/web-app-taiga/meta/services.yml) | ❌ | ❌ | ❌ |
| **wordpress** | ❌ | ❌ | [✅](https://wordpress.org/plugins/video-conferencing-with-bbb/) | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-wordpress/meta/services.yml) | ❌ | **wordpress** | [✅](https://wordpress.org/plugins/wp-discourse/) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **wordpress** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://wordpress.org/plugins/daggerhart-openid-connect-generic/) | ❌ | ❌ | [✅](https://wordpress.org/plugins/integration-for-listmonk-mailing-list-and-newsletter-manager/) | **wordpress** | [☑️](../../roles/web-app-wordpress/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-wordpress/meta/services.yml) | [✅](https://wordpress.org/plugins/activitypub/) | [✅](https://wordpress.org/plugins/matomo/) | ❌ | ❌ | ❌ | ❌ | **wordpress** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **wordpress** | ❌ | ❌ | [✅](https://wordpress.org/plugins/video-manager-for-peertube/) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-wordpress/meta/services.yml) | **wordpress** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | **wordpress** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-wordpress/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **wordpress** | [☑️](../../roles/web-app-wordpress/meta/services.yml) | ❌ | ❌ | ❌ |
| **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-xwiki/meta/services.yml) | ❌ | **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://extensions.xwiki.org/xwiki/bin/view/Extension/OpenID%20Connect/) | ❌ | ❌ | ❌ | **xwiki** | [☑️](../../roles/web-app-xwiki/meta/services.yml) | ❌ | ❌ | [☑️](../../roles/web-app-xwiki/meta/services.yml) | ❌ | [✅](https://extensions.xwiki.org/xwiki/bin/view/Extension/Piwiki%20Integration) | ❌ | ❌ | ❌ | ❌ | **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-xwiki/meta/services.yml) | **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | **xwiki** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-xwiki/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **xwiki** | [☑️](../../roles/web-app-xwiki/meta/services.yml) | ❌ | ❌ | ❌ |
| **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-yourls/meta/services.yml) | ❌ | **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-yourls/meta/services.yml) | ❌ | ❌ | ❌ | **yourls** | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-yourls/meta/services.yml) | ❌ | [☑️](../../roles/web-app-yourls/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-yourls/meta/services.yml) | **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | **yourls** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-yourls/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **yourls** | [☑️](../../roles/web-app-yourls/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://admin-docs.zammad.org/en/latest/settings/security/third-party/saml.html) | ❌ | ❌ | ❌ | **zammad** | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | [✅](https://admin-docs.zammad.org/en/latest/channels/email/index.html) | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **zammad** | — | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **zammad** | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | ❌ |
| **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-asset/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-asset/meta/services.yml) | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | — | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-asset/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ |
| **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ |
| **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ |
| **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-coturn/meta/services.yml) | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ |
| **css** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-css/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-css/meta/services.yml) | **css** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | [☑️](../../roles/web-svc-css/meta/services.yml) | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | **css** | ❌ | ❌ | ❌ | ❌ |
| **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-file/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-file/meta/services.yml) | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-file/meta/services.yml) | — | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ |
| **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-html/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-html/meta/services.yml) | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-html/meta/services.yml) | ❌ | — | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ |
| **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-legal/meta/services.yml) | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ |
| **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ | — | **libretranslate** | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | css | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-logout/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-logout/meta/services.yml) | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-logout/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **logout** | — | ❌ | ❌ | ❌ |
| **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-onlyoffice/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-onlyoffice/meta/services.yml) | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-onlyoffice/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | — | ❌ | ❌ |
| **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | — | ❌ |
| **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | — |
## Layers
## Maintenance
| Layer | Meaning | Where it is declared |
|---|---|---|
| Wired | Active through an infinito-native service flag | `meta/services.yml` of the row role |
| Declared | Upstream plugin wired as an addon | `meta/addons/` of the row role |
| Open | A verified upstream plugin exists, nothing declares it | nowhere yet — this is the backlog |
| Paid | Integration exists only behind a commercial tier | nowhere; opt-in by the operator |
| None | No known integration between the two roles | nowhere |
- [ ] Regenerate this matrix after adding or removing any integration edge.
Integrations are **directional**: the row role hosts the flag or the plugin. A bidirectional pair
such as `nextcloud` ↔ `openproject` is two declarations, one on each side.
Two classes need no declaration and are therefore never "open". Native ActivityPub federation
between the fediverse roles (`mastodon`, `peertube`, `pixelfed`, `funkwhale`, `mobilizon`,
`bookwyrm`, `socialhome`) works without a plugin. And `ldap`, `redis` and `mariadb` resolve to
`svc-db-*` roles, which are infrastructure rather than integration partners.
## Open work
The wired and declared layers are exhaustive by construction — they are read from the
repository. The open layer is not: it grows as each role's upstream plugin ecosystem is
surveyed. A MediaWiki survey, for example, surfaced `DiscourseSsoConsumer`, `PeerTubeEmbed`,
`MachineTranslation` (libretranslate), `Extension:AWS` (minio) and an LLM editor (openwebui) —
five integrations that no list had carried before someone looked.
## Acceptance Criteria
- [ ] Every `web-app-*` role has had its upstream plugin ecosystem surveyed once, and each
integration found is either declared under `meta/addons/` or recorded as deliberately
skipped with a reason.
- [ ] A reader can answer "does role A integrate role B" from the repository alone, without
consulting a generated page.
+10
View File
@@ -3,6 +3,16 @@ About
.. markdown-include:: ./README.md
Integrations
------------
Which roles this repository wires into which, derived from the roles themselves.
.. toctree::
:maxdepth: 1
generated/integrations/overview
Documentation
-------------
@@ -97,6 +97,13 @@ def generate_commands(src):
"--output-file",
generated / "cli_commands.rst",
),
_generator(
"integrations",
"--roles-dir",
src / "roles",
"--output-dir",
generated / "integrations",
),
_generator("readmes", "--generated-dir", generated),
_module(
"cli.build.docs.readme",
@@ -0,0 +1,260 @@
"""Render every integration the repository declares between two of its roles.
A role reaches an infinito-native provider through a top-level key in its
``meta/services.yml`` and an upstream provider through a ``bridges:`` entry in
one of its ``meta/addons/*.yml``. Both sides are read from the roles here, and
the axis comes from :func:`utils.roles.order.find_roles`, so a new role, a new
service key or a new add-on reaches the page on the next build.
A declared edge is not a live one. A target whose primary entity carries
``lifecycle: eol`` is marked, because the integration stays in the tree while
its flag is held at a literal ``false``; without the mark the page advertises a
partner no deployment can reach. The policy is
``docs/contributing/design/role/services/lifecycle.md``.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
from utils.cache.yaml import load_yaml
from utils.roles.entity.name import get_entity_name
from utils.roles.mapping import ROLE_DIR_META_ADDONS, ROLE_FILE_META_SERVICES
from utils.roles.meta_lookup import get_role_lifecycle
from utils.roles.order import find_roles
AXIS_PREFIXES = ["web-app-", "web-svc-"]
GROUP_MEMBER = re.compile(r"'([a-z0-9-]+)' in group_names")
EOL = "eol"
EOL_MARK = " (end of life)"
EOL_NOTE = """\
A target marked ``(end of life)`` is a role the project no longer maintains or
tests. The integration stays declared, but the partner's service flag is a
literal ``false`` no inventory turns on, so the edge is dormant.
"""
OVERVIEW_HEADER = (
"""\
Integrations
============
Every integration this repository declares between two of its own roles. A
role names its provider as a key in ``meta/services.yml``, and an add-on under
``meta/addons/`` names the same key in its ``bridges:`` list. Both sides are
read from the roles on each build, so this page cannot fall behind them.
"""
+ EOL_NOTE
)
SERVICES_HEADER = (
"""\
Native Integrations
===================
The provider each role wires itself into through ``meta/services.yml``, listed
under the service key that declares the link.
"""
+ EOL_NOTE
)
ADDONS_HEADER = (
"""\
Add-On Integrations
===================
The add-on each role installs to reach another role, listed with the service
key it bridges and the role that key resolves to.
"""
+ EOL_NOTE
)
def axis(roles_dir: Path) -> list[str]:
"""Return every role the matrix covers, in name order.
Args:
roles_dir: directory holding the role folders.
"""
return [path.name for path, _ in find_roles(roles_dir, AXIS_PREFIXES)]
def _gated_roles(entry) -> set[str]:
"""Return every role name a ``meta/services.yml`` entry gates itself on.
Args:
entry: the value of one top-level service key.
"""
return set(GROUP_MEMBER.findall(str(entry)))
def _services(role_path: Path) -> dict:
"""Return the ``meta/services.yml`` of a role, empty when it has none.
Args:
role_path: the role directory.
"""
return load_yaml(str(role_path / ROLE_FILE_META_SERVICES), default_if_missing={})
def service_targets(roles_dir: Path, roles: list[str]) -> dict[str, str]:
"""Return the role each service key reaches, keyed by service key.
A key resolves to the role whose group membership its entry gates itself
on, and otherwise to the role carrying it as an entity name. A key that
resolves to neither names no role on the axis and is absent from the
result.
Args:
roles_dir: directory holding the role folders.
roles: the role names on the axis.
"""
targets = {get_entity_name(role): role for role in roles}
on_axis = set(roles)
for role in roles:
for key, entry in _services(Path(roles_dir) / role).items():
gated = _gated_roles(entry) & on_axis
if len(gated) == 1:
targets[key] = gated.pop()
return targets
def eol_roles(roles_dir: Path, roles: list[str]) -> set[str]:
"""Return the roles on the axis whose primary entity is ``lifecycle: eol``.
Args:
roles_dir: directory holding the role folders.
roles: the role names on the axis.
"""
return {
role
for role in roles
if get_role_lifecycle(Path(roles_dir) / role, role_name=role) == EOL
}
def render_target(role: str, eol: set[str]) -> str:
"""Return one target role, marked when the partner is end of life.
Args:
role: the target role name.
eol: the role names :func:`eol_roles` reported.
"""
return f"``{role}``{EOL_MARK if role in eol else ''}"
def service_edges(role_path: Path, targets: dict[str, str]) -> list[tuple[str, str]]:
"""Return ``(service key, role)`` for every native integration of a role.
Args:
role_path: the role directory.
targets: the role each service key reaches.
"""
return sorted(
(key, targets[key])
for key in _services(role_path)
if key in targets and targets[key] != role_path.name
)
def addon_edges(role_path: Path, targets: dict[str, str]) -> list[tuple[str, str, str]]:
"""Return ``(add-on, service key, role)`` for every bridge a role declares.
Args:
role_path: the role directory.
targets: the role each service key reaches.
"""
return sorted(
(addon.stem, key, targets[key])
for addon in (role_path / ROLE_DIR_META_ADDONS).glob("*.yml")
for key in load_yaml(str(addon)).get("bridges") or []
if key in targets and targets[key] != role_path.name
)
def _block(role: str, items: list[str]) -> str:
"""Return one definition-list entry for a role.
Args:
role: the role name.
items: body lines, rendered as a bullet list.
"""
if not items:
return f"``{role}``\n None."
return "\n".join([f"``{role}``", *(f" * {item}" for item in items)])
def generate(roles_dir: Path, output_dir: Path) -> int:
"""Write the integration pages.
Args:
roles_dir: directory holding the role folders.
output_dir: directory the ``.rst`` pages are written to.
Returns:
How many roles the pages cover.
"""
roles = axis(roles_dir)
targets = service_targets(roles_dir, roles)
eol = eol_roles(roles_dir, roles)
overview, services, addons = [], [], []
for role in roles:
native = service_edges(Path(roles_dir) / role, targets)
bridged = addon_edges(Path(roles_dir) / role, targets)
reached = sorted(
{target for _, target in native} | {target for _, _, target in bridged}
)
overview.append(_block(role, [render_target(t, eol) for t in reached]))
services.append(
_block(
role,
[
f"``{key}`` -> {render_target(target, eol)}"
for key, target in native
],
)
)
addons.append(
_block(
role,
[
f"``{addon}``: ``{key}`` -> {render_target(target, eol)}"
for addon, key, target in bridged
],
)
)
output_dir.mkdir(parents=True, exist_ok=True)
for name, header, blocks in (
("overview", OVERVIEW_HEADER, overview),
("services", SERVICES_HEADER, services),
("addons", ADDONS_HEADER, addons),
):
(output_dir / f"{name}.rst").write_text(
header + "\n\n".join(blocks) + "\n", encoding="utf-8"
)
return len(roles)
def main(argv=None):
parser = argparse.ArgumentParser(
description="Generate the integration matrix from the roles."
)
parser.add_argument("--roles-dir", required=True, help="Directory of the roles.")
parser.add_argument("--output-dir", required=True, help="Directory for the pages.")
args = parser.parse_args(argv)
output = Path(args.output_dir)
count = generate(Path(args.roles_dir), output)
print(f"{count} role(s) written to {output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,310 @@
from __future__ import annotations
import importlib
import sys
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from utils.cache.files import read_text
from utils.roles.mapping import (
ROLE_DIR_META_ADDONS,
ROLE_FILE_META_MAIN,
ROLE_FILE_META_SERVICES,
)
from . import PROJECT_ROOT
_TOOLING = str(PROJECT_ROOT / "roles" / "web-app-docs" / "files" / "python")
if _TOOLING not in sys.path:
sys.path.insert(0, _TOOLING)
integrations = importlib.import_module("infinito_docs.generators.integrations")
SSO_GATE = "sso:\n enabled: \"{{ 'web-app-keycloak' in group_names }}\"\n"
JIRA_KEY = "jira:\n enabled: false\n"
JIRA_EOL = "jira:\n enabled: false\n lifecycle: eol\n"
def _role(roles_dir: Path, name: str, services: str = "", addons=None) -> Path:
"""Create one role directory below ``roles_dir``.
Args:
roles_dir: the directory the role is created in.
name: the role name.
services: body of ``meta/services.yml``; omitted when empty.
addons: ``{file stem: body}`` written under ``meta/addons/``.
"""
role = roles_dir / name
meta = role / ROLE_FILE_META_MAIN
meta.parent.mkdir(parents=True)
meta.write_text("galaxy_info: {}\n", encoding="utf-8")
if services:
(role / ROLE_FILE_META_SERVICES).write_text(services, encoding="utf-8")
for stem, body in (addons or {}).items():
addon = role / ROLE_DIR_META_ADDONS / f"{stem}.yml"
addon.parent.mkdir(parents=True, exist_ok=True)
addon.write_text(body, encoding="utf-8")
return role
class TestAxis(unittest.TestCase):
def test_every_web_role_present_is_on_the_axis(self) -> None:
"""A hardcoded axis is what dropped 14 roles and kept a deleted one."""
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha")
_role(roles_dir, "web-svc-beta")
self.assertEqual(
integrations.axis(roles_dir), ["web-app-alpha", "web-svc-beta"]
)
def test_a_role_outside_the_web_prefixes_is_not_on_the_axis(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha")
_role(roles_dir, "svc-db-postgres")
self.assertEqual(integrations.axis(roles_dir), ["web-app-alpha"])
def test_a_role_that_does_not_exist_cannot_appear(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha")
self.assertNotIn("web-app-gone", integrations.axis(roles_dir))
class TestServiceTargets(unittest.TestCase):
def test_a_key_resolves_to_the_role_its_gate_names(self) -> None:
"""``sso`` names no role; only its gate says the provider is Keycloak."""
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha", services=SSO_GATE)
_role(roles_dir, "web-app-keycloak")
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(targets["sso"], "web-app-keycloak")
def test_a_key_that_is_an_entity_name_resolves_to_that_role(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha", services="matomo:\n bond: 1\n")
_role(roles_dir, "web-app-matomo")
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(targets["matomo"], "web-app-matomo")
def test_a_key_naming_no_role_on_the_axis_resolves_to_nothing(self) -> None:
"""A sidecar must not read as an integration with another role."""
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(
roles_dir,
"web-app-alpha",
services="redis:\n enabled: \"{{ 'svc-db-redis' in group_names }}\"\n",
)
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertNotIn("redis", targets)
class TestEdges(unittest.TestCase):
def test_a_service_key_becomes_an_edge_to_its_provider(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
role = _role(roles_dir, "web-app-alpha", services=SSO_GATE)
_role(roles_dir, "web-app-keycloak")
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(
integrations.service_edges(role, targets),
[("sso", "web-app-keycloak")],
)
def test_a_roles_own_entry_is_not_an_edge_to_itself(self) -> None:
"""``alpha:`` in ``web-app-alpha`` is the role's own container."""
with TemporaryDirectory() as td:
roles_dir = Path(td)
role = _role(roles_dir, "web-app-alpha", services="alpha:\n bond: 1\n")
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(integrations.service_edges(role, targets), [])
def test_an_addon_bridge_becomes_an_edge(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
role = _role(
roles_dir,
"web-app-alpha",
services=SSO_GATE,
addons={"openid_connect": "bridges:\n - sso\n"},
)
_role(roles_dir, "web-app-keycloak")
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(
integrations.addon_edges(role, targets),
[("openid_connect", "sso", "web-app-keycloak")],
)
def test_an_addon_without_a_bridge_is_not_an_edge(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
role = _role(
roles_dir,
"web-app-alpha",
addons={"docker_manager": "mechanism: plugin\n"},
)
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(integrations.addon_edges(role, targets), [])
def test_a_role_without_any_integration_has_no_edges(self) -> None:
"""A role with neither file must not raise on the missing paths."""
with TemporaryDirectory() as td:
roles_dir = Path(td)
role = _role(roles_dir, "web-app-alpha")
targets = integrations.service_targets(
roles_dir, integrations.axis(roles_dir)
)
self.assertEqual(integrations.service_edges(role, targets), [])
self.assertEqual(integrations.addon_edges(role, targets), [])
class TestEolTargets(unittest.TestCase):
def test_a_role_whose_primary_is_eol_is_reported(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha", services=JIRA_KEY)
_role(roles_dir, "web-app-jira", services=JIRA_EOL)
self.assertEqual(
integrations.eol_roles(roles_dir, integrations.axis(roles_dir)),
{"web-app-jira"},
)
def test_a_role_without_a_lifecycle_is_not_reported(self) -> None:
with TemporaryDirectory() as td:
roles_dir = Path(td)
_role(roles_dir, "web-app-alpha", services=SSO_GATE)
_role(roles_dir, "web-app-keycloak")
self.assertEqual(
integrations.eol_roles(roles_dir, integrations.axis(roles_dir)),
set(),
)
def test_only_an_eol_target_carries_the_mark(self) -> None:
self.assertEqual(
integrations.render_target("web-app-jira", {"web-app-jira"}),
"``web-app-jira`` (end of life)",
)
self.assertEqual(
integrations.render_target("web-app-keycloak", {"web-app-jira"}),
"``web-app-keycloak``",
)
class TestRenderedEolPages(unittest.TestCase):
"""A declared edge to an EOL partner must not read as a live integration."""
def _render(self) -> dict[str, str]:
with TemporaryDirectory() as td:
roles_dir = Path(td) / "roles"
roles_dir.mkdir()
_role(
roles_dir,
"web-app-alpha",
services=JIRA_KEY + SSO_GATE,
addons={"jira": "bridges:\n - jira\n"},
)
_role(roles_dir, "web-app-jira", services=JIRA_EOL)
_role(roles_dir, "web-app-keycloak")
output = Path(td) / "out"
integrations.generate(roles_dir, output)
return {
name: read_text(str(output / f"{name}.rst"))
for name in ("overview", "services", "addons")
}
def test_the_overview_marks_the_eol_partner(self) -> None:
self.assertIn(" * ``web-app-jira`` (end of life)", self._render()["overview"])
def test_the_service_page_marks_the_eol_provider(self) -> None:
self.assertIn(
" * ``jira`` -> ``web-app-jira`` (end of life)",
self._render()["services"],
)
def test_the_addon_page_marks_the_eol_provider(self) -> None:
self.assertIn(
" * ``jira``: ``jira`` -> ``web-app-jira`` (end of life)",
self._render()["addons"],
)
def test_a_live_partner_on_the_same_page_stays_unmarked(self) -> None:
self.assertIn(
" * ``sso`` -> ``web-app-keycloak``\n", self._render()["services"]
)
def test_every_page_explains_the_mark(self) -> None:
for page in self._render().values():
self.assertIn("``(end of life)``", page)
class TestRenderedPages(unittest.TestCase):
def _render(self) -> dict[str, str]:
with TemporaryDirectory() as td:
roles_dir = Path(td) / "roles"
roles_dir.mkdir()
_role(
roles_dir,
"web-app-alpha",
services=SSO_GATE,
addons={"openid_connect": "bridges:\n - sso\n"},
)
_role(roles_dir, "web-app-keycloak")
_role(roles_dir, "web-svc-beta")
output = Path(td) / "out"
count = integrations.generate(roles_dir, output)
pages = {
name: read_text(str(output / f"{name}.rst"))
for name in ("overview", "services", "addons")
}
self.assertEqual(count, 3)
return pages
def test_every_role_is_named_on_every_page(self) -> None:
for page in self._render().values():
self.assertIn("``web-app-alpha``", page)
self.assertIn("``web-app-keycloak``", page)
self.assertIn("``web-svc-beta``", page)
def test_the_service_page_names_the_key_and_the_provider(self) -> None:
self.assertIn(
" * ``sso`` -> ``web-app-keycloak``", self._render()["services"]
)
def test_the_addon_page_names_the_addon_the_key_and_the_provider(self) -> None:
self.assertIn(
" * ``openid_connect``: ``sso`` -> ``web-app-keycloak``",
self._render()["addons"],
)
def test_the_overview_names_the_reached_role_once(self) -> None:
"""The key and the add-on reach the same role and must not list it twice."""
overview = self._render()["overview"]
block = overview.split("``web-app-alpha``\n")[1].split("\n\n")[0]
self.assertEqual(block, " * ``web-app-keycloak``")
def test_a_role_without_edges_keeps_a_definition_body(self) -> None:
"""An empty body would break the definition list it sits in."""
self.assertIn("``web-svc-beta``\n None.", self._render()["overview"])
if __name__ == "__main__":
unittest.main()
@@ -61,6 +61,7 @@ class TestProgress(unittest.TestCase):
"infinito_docs.generators.make_targets",
"infinito_docs.generators.aliases",
"infinito_docs.generators.cli_commands",
"infinito_docs.generators.integrations",
"infinito_docs.generators.readmes",
"cli.build.docs.readme",
"cli.build.docs.readme.overview",