mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Every mautrix bridge generated its own registration with -g on start, with fresh random tokens. Synapse had to read that file from a shared directory, be restarted afterwards, and on swarm the services had to be removed first so the registrations could be wiped (93735fd533). The tokens changed on every redeploy and the deploy depended on a wait for files a container writes. Each bridge now has as_token and hs_token declared in meta/secrets.yml and a fixed appservice_port in its addon. The bridge config reads the tokens through lookup('config'), and registration.yaml.j2 renders the registration from that config in the upstream -g format, so both sides are known before anything starts and stay identical across redeploys. The files reach the containers as secrets: matrix_bridge_mounts offers a stack view (top-level definitions, content-hash named by compose_volumes), a synapse view (one registration per bridge under /registrations, mode 0444) and a bridge view (the config at /config/config.yaml). The bridges run as the synapse uid with an explicit --config and --no-update, keep their scratch /data on a 16m tmpfs so hlth-volumes finds no anonymous volume, and depend on a healthy synapse. Their healthcheck is /_matrix/mau/ready, which the bridge only reports after whoami with the as_token and an appservice ping carrying the hs_token, so a healthy bridge proves the token pair. The compose tasks lose the compose down, the swarm service rm block, the registration wait, the permission fixes and both synapse restarts; this reverts the workaround of93735fd533. Verified on the local compose stack: four bridges ready, synapse pings answered, registration and config sha256 unchanged across a redeploy, test-bridge-roster and 12 matrix Playwright specs green. A token rotation (new pair in both files, compose up --force-recreate as the handler runs it) came back healthy; a mismatched pair left the bridge unhealthy. The swarm-shaped stack file passes docker stack config with the 0444 secret modes, the 16 MiB tmpfs and content-hash secret names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
115 lines
4.0 KiB
Python
115 lines
4.0 KiB
Python
"""Lookup ``matrix_bridge_mounts``: the secrets that carry each enabled
|
|
bridge's config and registration.
|
|
|
|
Usage (compose templates):
|
|
|
|
lookup('compose_volumes', extra_secrets=lookup('matrix_bridge_mounts', 'stack'))
|
|
lookup('container_volumes', 'synapse', extra_secrets=lookup('matrix_bridge_mounts', 'synapse'))
|
|
lookup('container_volumes', service_name, extra_secrets=lookup('matrix_bridge_mounts', 'bridge', item.bridge_name))
|
|
|
|
Terms:
|
|
stack -- top-level secret definitions, named by compose_volumes
|
|
synapse -- one registration mount per bridge
|
|
bridge <name> -- the config mount of that bridge
|
|
|
|
Reads from the templating context:
|
|
MATRIX_BRIDGES -- enabled bridge configs (set_fact)
|
|
MATRIX_BRIDGE_SOURCE_DIR -- host directory holding <bridge>/*.yaml
|
|
MATRIX_BRIDGE_CONFIG_TARGET -- config path inside each bridge
|
|
MATRIX_REGISTRATION_FILE_FOLDER -- registration folder inside synapse
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
SECRET_MODE = 0o444
|
|
|
|
|
|
def _config_key(bridge: str) -> str:
|
|
return f"mautrix_{bridge}_config"
|
|
|
|
|
|
def _registration_key(bridge: str) -> str:
|
|
return f"mautrix_{bridge}_registration"
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
def run(
|
|
self,
|
|
terms: list[Any] | None,
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[Any]:
|
|
view = str(terms[0]) if terms else ""
|
|
if view not in ("stack", "synapse", "bridge"):
|
|
raise AnsibleError(
|
|
"matrix_bridge_mounts lookup expects 'stack', 'synapse' or "
|
|
f"'bridge <name>', got {terms!r}"
|
|
)
|
|
|
|
vars_ = variables or getattr(self._templar, "available_variables", {}) or {}
|
|
templar = getattr(self, "_templar", None)
|
|
|
|
def ctx(name: str) -> Any:
|
|
if name not in vars_:
|
|
raise AnsibleError(
|
|
f"matrix_bridge_mounts lookup: '{name}' is not set in the "
|
|
"templating context"
|
|
)
|
|
value = vars_[name]
|
|
if templar is not None:
|
|
value = templar.template(value)
|
|
return value
|
|
|
|
bridges = ctx("MATRIX_BRIDGES")
|
|
if not isinstance(bridges, list):
|
|
raise AnsibleError(
|
|
"matrix_bridge_mounts lookup: MATRIX_BRIDGES must be a list, "
|
|
f"got {type(bridges).__name__}"
|
|
)
|
|
names = [str(bridge["bridge_name"]) for bridge in bridges]
|
|
|
|
if view == "bridge":
|
|
if len(terms) != 2 or str(terms[1]) not in names:
|
|
raise AnsibleError(
|
|
f"matrix_bridge_mounts lookup: 'bridge' needs one enabled bridge name, got {terms[1:]!r}"
|
|
)
|
|
return [
|
|
[
|
|
{
|
|
"source": _config_key(str(terms[1])),
|
|
"target": str(ctx("MATRIX_BRIDGE_CONFIG_TARGET")),
|
|
"mode": SECRET_MODE,
|
|
}
|
|
]
|
|
]
|
|
|
|
if view == "synapse":
|
|
folder = str(ctx("MATRIX_REGISTRATION_FILE_FOLDER"))
|
|
return [
|
|
[
|
|
{
|
|
"source": _registration_key(name),
|
|
"target": f"{folder}mautrix-{name}/registration.yaml",
|
|
"mode": SECRET_MODE,
|
|
}
|
|
for name in names
|
|
]
|
|
]
|
|
|
|
source_dir = Path(str(ctx("MATRIX_BRIDGE_SOURCE_DIR")))
|
|
secrets: dict[str, dict[str, str]] = {}
|
|
for name in names:
|
|
secrets[_config_key(name)] = {
|
|
"file": str(source_dir / name / "config.yaml")
|
|
}
|
|
secrets[_registration_key(name)] = {
|
|
"file": str(source_dir / name / "registration.yaml")
|
|
}
|
|
return [secrets]
|