Commit Graph
17 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 2d993141d2 fix(mattermost): let the OAuth token exchange reach a self-hosted issuer
Mattermost 11.11.0 routes its OAuth token and userinfo requests through the
same outbound filter as webhooks and link previews. Upstream commit 232d968f435e
"Filter OAuth endpoint requests that hit reserved IPs" changed two lines in
server/channels/app/oauth.go:

    1102 -> 1117  a.HTTPService().MakeClient(true)  ->  MakeClient(false)
    1144 -> 1159  a.HTTPService().MakeClient(true)  ->  MakeClient(false)

MakeTransport(trustURLs=true) returns NewTransport(insecure, nil, nil), so
before the bump neither a host nor an IP filter applied to that path at all.
Keycloak resolves into a reserved range in this topology, so every sign-in now
ends at /error?message=Token+request+failed:

    AuthorizeOAuthUser: Token request failed., Post
    "https://auth.<domain>/realms/<realm>/protocol/openid-connect/token":
    address forbidden ... IP 172.30.0.10 is in a reserved range and not in
    AllowedUntrustedInternalConnections

Name the issuer host, which is the smallest grant Mattermost offers. A verbatim
hostname short-circuits the dial before DNS and before the IP check
(shared/httpservice/client.go:136), so no CIDR is needed; a CIDR would open the
whole stack to webhook and link-preview SSRF instead. Both guarded legs are
listed because an inventory may override USER_INFO_URL to a different host, and
they are comma-joined because splitFields splits on whitespace and commas while
a space would need quoting in a dotenv value.

Not CI-only: IsOwnIP treats any address bound on a local interface as reserved,
so a single-host production deployment hits the same block. The cost is that
every MakeClient(false) consumer may now dial that one hostname on any port,
since Mattermost offers no port or path granularity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 11:56:47 +02:00
kevinveenbirkenbachandClaude Opus 5 e3f1fab9c2 feat(web-app-mattermost): keep the configuration in postgres, not on a shared volume
Without MM_CONFIG mattermost uses its file store at
/mattermost/config/config.json, which lives on a volume the swarm rewrite
places on the shared NFS export. The role is unpinned, so it runs one task
per node, and mattermost rewrites that file at startup while normalising
and migrating its config. Several replicas then write the same file over
NFS.

Point MM_CONFIG at the database instead. Read at the pinned 11.10.0 tag:
getConfigDSN falls back to env MM_CONFIG, server.go hands the result to
NewStoreFromDSN, and that selects NewDatabaseStore whenever IsDatabaseDSN
matches a postgres:// prefix. The file store is then never constructed.
Nothing in this role reads or patches config.json; the only reference is
the volume mount.

This externalises the state rather than capping the role at one replica,
so mattermost stays horizontally scalable.

Resolve the DSN once in vars/main.yml from the database lookup's url_full,
which already assembles scheme, credentials, host, port and name, and
reference that constant from both MM_SQLSETTINGS_DATASOURCE and MM_CONFIG.
The template previously spelled the DSN out across six lookups and
hardcoded the postgres:// scheme that url_full derives. Appending options
to url_full follows the pattern web-app-openproject already uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:43:29 +02:00
kevinveenbirkenbachandClaude Opus 5 3f900c7600 feat(mattermost): enable the Agents MCP server through the database
Mattermost keeps the plugin's MCP server switch in the database rather
than in a config file, so the role reads the plugin state, flips the
server on, and reads back the personal access token through SQL, with
reading and writing statements kept in separate directories.

Take over the token the server issued instead of generating one: the
value only exists once Mattermost has minted it, so it is persisted
through sys-token-store rather than declared as a vault credential.

Calls execute as the account the token belongs to, so the surface is
bounded by that account's permissions rather than by an administrator
role.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 22:19:29 +02:00
kevinveenbirkenbachandClaude Opus 4.8 15e7647d2f refactor(env): central TZ+LANG base included by every role env.j2
New roles/sys-svc-container/templates/env.j2 sets TZ={{ HOST_TIMEZONE }} and LANG=C.UTF-8. Every role env.j2 includes it at the top; it renders via stack_host_template, the same engine that resolves the includes in compose.yml.j2, so it works in both compose and swarm. TZ/LANG are removed from the individual role env.j2 so each value stays single-source.

New lint tests/lint/ansible/test_env_base_include.py verifies every role env.j2 includes the base and never re-sets a base-owned key (keys derived from the base file).

mattermost and gitea meta/volumes.yml drop the /etc/timezone host-path bind: swarm hard-rejects a task whose bind source is missing on the node (Debian-trixie nodes have no /etc/timezone). TZ now comes from the base env. /etc/localtime bind also dropped; the container timezone follows TZ.

Non-directive comments were removed from the touched env.j2 (banners, restatements, reference URLs, narration) so the changed files pass the comment-lint; only tool directives remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 22:13:57 +02:00
kevinveenbirkenbachandClaude Opus 4.8 8023e4b8b2 fix(web-app-mattermost): repair nextcloud OAuth coupling probe and gate user-access-tokens on nextcloud presence
The nextcloud->mattermost OAuth provisioning failed because the partner-running probe used 'container exec <mm> true' (rc 127 — no true binary in the mattermost image), which gated provisioning off and left the OAuth app unprovisioned (spec failed on oauth_instance_url=null). Replace it with 'container inspect -f {{.State.Running}}' gated on stdout-trimmed == true, provision the OAuth app via a controller-run script using a short-lived admin PAT, and rewrite the spec as a behavioral connect-redirect check.

Mattermost personal access tokens are now enabled only when the consumer web-app-nextcloud is deployed (services.mattermost.user_access_token_enabled renders from 'web-app-nextcloud' in group_names) instead of being hardcoded on.

Verified: probe live in the DiD stack (inspect running -> rc0, missing -> graceful skip); the user-access-token chain renders True/False via a real Templar (nextcloud present -> True, absent -> False); service-flag integration tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:41:19 +02:00
kevinveenbirkenbachandClaude Opus 4.8 153bfb1898 feat(objstore): consume the object store across 14 web-app roles
Switch penpot, nextcloud, gitea, mattermost, openwebui, funkwhale, baserow, openproject, pixelfed, mastodon, bookwyrm, listmonk, peertube and opentalk to the engine-agnostic objstore lookup: the minio service entries are pinned off, a seaweedfs service (gated on the provider being in group_names, public for the media apps) replaces them, and the matrix variants pin the new flag. OpenTalk keeps object storage mandatory through an embedded fallback and provisions its bucket via weed shell.

Add objstore lookup SPOT keys consumed by these roles: endpoint (host:port), public_domain, public_bucket (public S3 host plus bucket) and public_url (scheme plus public_bucket, scheme derived from the provider TLS state). This removes the repeated lookup('config', lookup('objstore', ...), 'server.domains.canonical.api') / host:port concatenations from the consumer templates. Listmonk settings render their JSON values via the to_json filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ed04dad5d5 refactor(meta): land req-008/009/010 role-meta layout/networks/ports/run_after migration
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).

req-008 — role meta layout
  * Move every role's metadata under meta/<topic>.yml with file-root
    convention (no wrapping key matching the basename):
      - schema/main.yml + config/main.yml.credentials → meta/schema.yml
      - users/main.yml                                → meta/users.yml
      - config/main.yml.server                        → meta/server.yml
      - config/main.yml.rbac                          → meta/rbac.yml
      - config/main.yml.compose.services + every other top-level key
        of config/main.yml inlined under <entity>.<key>
                                                      → meta/services.yml
      - config/main.yml.compose.volumes               → meta/volumes.yml
  * Drop the legacy schema/, users/, config/ directories from every role.
  * meta/schema.yml: support nested credential keys, optional `default:`
    field (Jinja literal, not rendered/validated/regenerated), implicit
    `algorithm: plain`.
  * Materialised paths drop the `compose.` prefix:
      compose.services.<e>.<…> → services.<e>.<…>
      compose.volumes.<k>      → volumes.<k>
      <former-top-level>.<…>   → services.<entity_name>.<former-top-level>.<…>
  * Update every Python consumer (utils/cache, utils/manager, utils/applications,
    plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
    and task file to the new paths.
  * No legacy fallback. Repository-wide grep for `compose.services.` /
    `compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
    is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
  * Add tests/lint/repository/test_role_meta_layout.py guarding against the
    old paths returning, and an explicit lint guard that meta/variants.yml
    files do not carry a `compose:` wrapper.

req-009 — per-role networks/ports
  * Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
    dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
    dns_resolver}.
  * Delete group_vars/all/09_ports.yml entirely; ports now live on the
    service entity that exposes them at
    roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
  * Rename + int-cast every former top-level `services.<entity>.port`
    field to `services.<entity>.ports.inter` (single int).
  * Always-category-keyed maps for ports.local / ports.public; relay
    spans use {start, end} integers.
  * Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
    of truth for the per-<scope>.<category> band ranges, consumed by both
    the new CLI helpers and the lint test.
  * New CLI helpers (cli meta ports suggest, cli meta networks suggest)
    walk the live role tree gap-first then increment-fallback, with
    deterministic output and non-zero exit on capacity overflow.
  * cli create role consumes both helpers when scaffolding a new role.
  * Lint extensions: bare-int port shape rejection, host-bound port
    collision check (single ints + relay span integers, inter excluded),
    band-membership check with documented allow-list for the legacy
    web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.

req-010 — run_after / lifecycle migration
  * Move galaxy_info.run_after and galaxy_info.lifecycle out of
    meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
    lifecycle}; <primary_entity> is the value returned by
    get_entity_name(role_name).
  * For multi-entity roles whose primary entity has no compose service
    (web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
    gen roles), create a dedicated metadata-only top-level entry under
    <primary_entity>.
  * Introduce utils/roles/meta_lookup.py with get_role_run_after /
    get_role_lifecycle that resolve <primary_entity> once and read from
    meta/services.yml; degrade gracefully when meta/services.yml is
    absent OR the field is absent; raise MetaServicesShapeError when the
    file exists but is malformed.
  * Update every consumer (utils/service_registry, utils/invokable,
    cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
    lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
    cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
    init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
    use the helper.
  * Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
    values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
    run_after/lifecycle never on a non-primary entity; empty run_after
    lists rejected.

Also folded into this change set
  * services_disabler now writes `services.<svc>.{enabled,shared}` into
    host_vars instead of the old `compose.services.<svc>.{...}` path; the
    discover/find pair walks meta/services.yml and only treats entries
    carrying enabled/shared/provides as provider declarations (so
    metadata-only entries from req-010 do not create spurious provider
    rows).
  * service_registry skips `run_after` deps whose target was filtered
    out via SERVICES_DISABLED instead of failing the whole load.
  * cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
    consume the new applications.<role>.services.<svc>.image shape
    (mirror.md updated to match).
  * utils/domains/list.build_applications_from_roles now reads
    meta/server.yml directly (it had been silently returning {} since
    config/main.yml stopped existing).
  * utils/roles/resource_validation.filter_roles_by_min_storage reads
    meta/services.yml.<entity>.min_storage instead of the legacy
    compose.services path.
  * web-app-bluesky pre-clones the social-app build context BEFORE
    sys-svc-compose so the build context is on disk by the time
    sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
  * 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
    web-app-wordpress, web-app-keycloak) lose their `compose: services:`
    wrapper so deep-merge over meta/services.yml lands on the new
    file-root shape.

Validation
  * Unit tests:        1571 passed.
  * Lint tests:        50 passed.
  * Integration tests: 161 passed (1 skipped).
  * Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
    set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
    discourse,mattermost}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:58:26 +02:00
kevinveenbirkenbach b2914b7cd1 Merge branch 'main' into fix/users-applications.yml 2026-04-23 18:14:17 +02:00
Alejandro Roman 58aa904129 feat(prometheus-comm): fix last comments and fix new issue in matrix 2026-04-23 00:31:24 +04:00
Alejandro Roman e3a1797ba9 feat(prometheus-comm): fix previous issues + add comms correctly and avoid deffered logic for this PR - WIP - some tests still fails 2026-04-23 00:31:21 +04:00
Alejandro Roman 3f82fb1ac8 feat(prometheus-comm): make sure to move logic into separate temapltes and fix issues in code 2026-04-23 00:31:14 +04:00
kevinveenbirkenbachandClaude Opus 4.7 2dbb2aaa1a feat(email): introduce lookup('email') plugin, migrate all consumers
Moves the SYSTEM_EMAIL_* resolution logic out of group_vars/all/03_email.yml
into a dedicated Ansible lookup plugin, so every role template reads outgoing
mail settings via lookup('email') (global) or lookup('email', application_id)
(per-app). Per-app compose.services.email is merged on top of the resolved
defaults; SYSTEM_EMAIL_* variables remain usable as an inventory escape hatch.

Also splits the email docs: administrator-facing configuration stays under
docs/administration/configuration/, plugin contract and extension rules move
to docs/contributing/design/services/email.md. services.md is relocated to
services/base.md so the services/ subtree can host per-service design pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 22:47:40 +02:00
kevinveenbirkenbachandClaude Opus 4.6 b70a783423 refactor(lookups): migrate to runtime applications/users lookups
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 23:44:19 +02:00
Alejandro RomanandClaude Sonnet 4.6 ec47c688fd Add Playwright E2E tests for Mattermost (#538)
- New playwright.spec.js: two scenarios — SSO login via GitLab OAuth2
  endpoint (bypasses missing EnableSignInWithGitLab client config key in
  v11.5.1), channel view verification, API logout (bypasses nginx /logout
  intercept); and biber→administrator direct message flow using isolated
  browser contexts
- New playwright.env.j2: injects OIDC, Mattermost URL and user credentials
- tasks/01_setup.yml: create/add/convert administrator and biber users to
  team and gitlab SSO auth via LDAP uidNumber
- env.j2: add MM_EXPERIMENTALSETTINGS_DISABLELANDINGPAGE=true to prevent
  /landing SPA redirect in fresh browser contexts
- README.md: fix H1 so dashboard card renders "Explore Mattermost" link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 12:49:13 +04:00
Alejandro Roman 98d150062c Change author, varaibles more readable and add a fix for DNS 2026-03-17 10:50:26 +01:00
Alejandro Roman bb77f7210b feat(mattermost): Refactor the logic to follow strucutrre of prohject + fix all the issues on deployment with Ansible roles 2026-03-17 10:50:25 +01:00
Alejandro Roman 61b4b2207b feat(mattermost): created the roles and config for mattermost 2026-03-17 10:50:25 +01:00