mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
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:
1 parent
e78596d76d
commit
bed4b3c84f
13 files changed
+666
-1325
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
@@ -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",
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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())
|
||||
+310
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user