feat(matrix): deploy matrix-bridge build.1791062345 as a Synapse appservice (REN-587)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was canceled
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled

Adds the matrix-bridge Deployment, Service and ExternalSecret in the
matrix namespace, loads its appservice registration in Synapse, and opens
the bridge -> Paperclip API path (:3100) in both namespaces' policies.
Tokens and the board key come from OpenBao homelab/apps/matrix-bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
atlas-bot
2026-10-04 21:41:10 +00:00
co-authored by Claude Opus 5.5
parent c384a052a9
commit 9093b52e00
4 changed files with 208 additions and 4 deletions
+183
View File
@@ -0,0 +1,183 @@
# matrix-bridge (REN-569, REN-587): Matrix appservice between Element and the
# Paperclip agents. One room per agent with the owner; owner messages become
# comments on the agent's chat card, the agent's comments come back to the
# room. Source: gitea.renner.uno/konairius/matrix-bridge.
#
# No volume: the bridge keeps its state in the bot's account data on Synapse.
# No ingress: only Synapse talks to it (appservice transactions on :8080).
#
# OpenBao homelab/apps/matrix-bridge (seeded by the owner):
# as_token random; bridge -> Synapse, MATRIX_AS_TOKEN
# hs_token random; Synapse -> bridge, MATRIX_HS_TOKEN
# paperclip_api_key Paperclip board key "matrix-bridge (board)" (REN-582)
#
# The same Secret carries the appservice registration that Synapse loads
# from app_service_config_files (synapse.yaml), so both sides always get the
# same tokens. Synapse cannot start without it: seed OpenBao before merge.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: matrix-bridge
namespace: matrix
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: matrix-bridge
template:
engineVersion: v2
data:
as_token: "{{ .as_token }}"
hs_token: "{{ .hs_token }}"
paperclip_api_key: "{{ .paperclip_api_key }}"
# Agent users are @agent_<urlKey>:matrix.renner.uno; the bot is
# @paperclip:matrix.renner.uno.
registration.yaml: |
id: matrix-bridge
url: http://matrix-bridge:8080
as_token: "{{ .as_token }}"
hs_token: "{{ .hs_token }}"
sender_localpart: paperclip
rate_limited: false
namespaces:
users:
- exclusive: true
regex: "@agent_.*:matrix\\.renner\\.uno"
aliases: []
rooms: []
dataFrom:
- extract:
key: apps/matrix-bridge
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: matrix-bridge
namespace: matrix
annotations:
secret.reloader.stakater.com/reload: matrix-bridge
labels:
app.kubernetes.io/name: matrix-bridge
app.kubernetes.io/part-of: matrix
spec:
replicas: 1
# One poller only: two pods would bridge every comment twice.
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: matrix-bridge
template:
metadata:
labels:
app.kubernetes.io/name: matrix-bridge
app.kubernetes.io/part-of: matrix
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
# `node` in node:24-alpine.
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: matrix-bridge
image: gitea.renner.uno/konairius/matrix-bridge:build.1791062345
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: MATRIX_HOMESERVER_URL
value: http://synapse:8008
- name: MATRIX_SERVER_NAME
value: matrix.renner.uno
- name: MATRIX_OWNER
value: "@k.renner:matrix.renner.uno"
- name: MATRIX_AS_TOKEN
valueFrom:
secretKeyRef:
name: matrix-bridge
key: as_token
- name: MATRIX_HS_TOKEN
valueFrom:
secretKeyRef:
name: matrix-bridge
key: hs_token
- name: PAPERCLIP_API_URL
value: http://paperclip.paperclip.svc.cluster.local:3100
- name: PAPERCLIP_API_KEY
valueFrom:
secretKeyRef:
name: matrix-bridge
key: paperclip_api_key
- name: PAPERCLIP_COMPANY_ID
value: 72f7b75a-10b0-4084-ad5e-a0cdf31c7caf
- name: PAPERCLIP_PROJECT_ID
value: 67143c0b-e9a1-44c1-b012-0f8fa25625aa
ports:
- name: http
containerPort: 8080
resources:
requests:
cpu: 10m
memory: 64Mi
limits:
memory: 256Mi
# /health is 503 when the Paperclip comment poll fails, so it gates
# readiness only; a Paperclip outage must not restart the bridge.
readinessProbe:
httpGet:
path: /health
port: http
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
periodSeconds: 30
failureThreshold: 4
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: matrix-bridge
namespace: matrix
spec:
selector:
app.kubernetes.io/name: matrix-bridge
ports:
- name: http
port: 8080
targetPort: http
---
# The bridge calls the Paperclip API in-cluster. Synapse <-> bridge and DNS
# are covered by matrix-app-egress (part-of: matrix); the Paperclip side of
# this flow is in apps/paperclip/network-policy.yaml.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: matrix-bridge-paperclip-egress
namespace: matrix
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: matrix-bridge
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": paperclip
app.kubernetes.io/name: paperclip
toPorts:
- ports:
- port: "3100"
protocol: TCP
@@ -5,6 +5,7 @@ resources:
- synapse.yaml
- mas.yaml
- element.yaml
- bridge.yaml
- ingress.yaml
# mx-pg CNPG Postgres + offsite Barman backup. The media store and signing
# key are on synapse-data, backed up by Velero (matrix-daily).
+14 -4
View File
@@ -90,9 +90,10 @@ data:
push:
include_content: false
# Appservice registrations (bridges) go here, one file each, mounted
# from a Secret. Empty until REN-565 Part 2 is decided.
app_service_config_files: []
# Appservice registrations (bridges), one file each, mounted from a
# Secret. matrix-bridge: bridge.yaml (REN-587).
app_service_config_files:
- /appservices/matrix-bridge/registration.yaml
---
# Media store and the server signing key. Not reconstructable: lost media
# is gone, and a lost signing key invalidates the server's past signatures.
@@ -119,7 +120,7 @@ metadata:
namespace: matrix
annotations:
configmap.reloader.stakater.com/reload: synapse-config,mas-config
secret.reloader.stakater.com/reload: synapse-secrets
secret.reloader.stakater.com/reload: synapse-secrets,matrix-bridge
labels:
app.kubernetes.io/name: synapse
app.kubernetes.io/part-of: matrix
@@ -267,6 +268,9 @@ spec:
mountPath: /data
- name: tmp
mountPath: /tmp
- name: appservice-matrix-bridge
mountPath: /appservices/matrix-bridge
readOnly: true
volumes:
- name: config
configMap:
@@ -299,6 +303,12 @@ spec:
claimName: synapse-data
- name: tmp
emptyDir: {}
- name: appservice-matrix-bridge
secret:
secretName: matrix-bridge
items:
- key: registration.yaml
path: registration.yaml
---
apiVersion: v1
kind: Service
@@ -25,6 +25,16 @@ spec:
- ports:
- port: "3100"
protocol: TCP
# matrix-bridge (REN-587), board-key API calls; egress side in
# apps/matrix/bridge.yaml.
- fromEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": matrix
app.kubernetes.io/name: matrix-bridge
toPorts:
- ports:
- port: "3100"
protocol: TCP
---
# Covers the server and every agent it spawns (same pod).
apiVersion: cilium.io/v2