app/matrix: split matrix-ng into submodules, use kube.Namespace.Contain
matrix-ng split into multiple submodules causes some changes in keys
that might've been used for homeserver/riot configuration customization.
Migration to kube.Namespace.Contain has also caused change in Deployment
selectors (immutable fields), thus needing manual removal of these
first.
This is, as always, documented in lib/matrix-ng.libsonnet header.
Change-Id: I39a745ee27e3c55ec748818b9cf9b4e8ba1d2df5
diff --git a/app/matrix/lib/cas.libsonnet b/app/matrix/lib/cas.libsonnet
new file mode 100644
index 0000000..1a33aaa
--- /dev/null
+++ b/app/matrix/lib/cas.libsonnet
@@ -0,0 +1,48 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+ local app = self,
+ local cfg = app.cfg,
+ cfg:: {
+ image: error "cfg.image must be set",
+
+ # webDomain is the domain name at which matrix instance/cas proxy is served
+ webDomain: error "cfg.webDomain must be set",
+
+ oauth2: error "cfg.oauth2 must be set",
+ },
+
+ ns:: error "ns needs to be a kube.Namespace object",
+
+ deployment: app.ns.Contain(kube.Deployment("oauth2-cas-proxy")) {
+ spec+: {
+ replicas: 1,
+ template+: {
+ spec+: {
+ containers_: {
+ proxy: kube.Container("oauth2-cas-proxy") {
+ image: cfg.image,
+ ports_: {
+ http: { containerPort: 5000 },
+ },
+ env_: {
+ BASE_URL: "https://%s" % [cfg.webDomain],
+ SERVICE_URL: "https://%s" % [cfg.webDomain],
+ OAUTH2_CLIENT: cfg.oauth2.clientID,
+ OAUTH2_SECRET: cfg.oauth2.clientSecret,
+ OAUTH2_SCOPE: cfg.oauth2.scope,
+ OAUTH2_AUTHORIZE: cfg.oauth2.authorizeURL,
+ OAUTH2_TOKEN: cfg.oauth2.tokenURL,
+ OAUTH2_USERINFO: cfg.oauth2.userinfoURL,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+
+ svc: app.ns.Contain(kube.Service("oauth2-cas-proxy")) {
+ target_pod:: app.deployment.spec.template,
+ },
+}
diff --git a/app/matrix/lib/matrix-ng.libsonnet b/app/matrix/lib/matrix-ng.libsonnet
index 08c8153..976b8b5 100644
--- a/app/matrix/lib/matrix-ng.libsonnet
+++ b/app/matrix/lib/matrix-ng.libsonnet
@@ -15,6 +15,14 @@
# kubectl -n $ns edit secret synapse
# # ...add homeserver_signing_key, redis_password and worker_replication_secret keys
#
+# Additionally some resources need to be explicitly removed due to
+# label/annotations changes:
+# kubectl -n $ns delete deployment riot-web oauth2-cas-proxy wellknown synapse
+#
+# Some service configuration customization fields have been renamed:
+# .riotConfig → .riot.config
+# .synapseConfig → .synapse.config
+#
# Sequencing appservices is fun. The appservice needs to run first (for
# instance, via a bootstrap job), and on startup it will spit out a
# registration file. This registration file then needs to be fed to synapse -
@@ -32,6 +40,11 @@
local postgres = import "../../../kube/postgres.libsonnet";
local redis = import "../../../kube/redis.libsonnet";
+local riot = import "./riot.libsonnet";
+local cas = import "./cas.libsonnet";
+local wellKnown = import "./wellknown.libsonnet";
+local synapse = import "./synapse.libsonnet";
+
{
local app = self,
local cfg = app.cfg,
@@ -104,15 +117,6 @@
wellKnown: false,
},
- metadata(component):: {
- namespace: cfg.namespace,
- labels: {
- "app.kubernetes.io/name": "matrix",
- "app.kubernetes.io/managed-by": "kubecfg",
- "app.kubernetes.io/component": component,
- },
- },
-
namespace: kube.Namespace(cfg.namespace),
postgres3: postgres {
@@ -138,405 +142,47 @@
},
},
- dataVolume: kube.PersistentVolumeClaim("synapse-data-waw3") {
- metadata+: app.metadata("synapse-data"),
- spec+: {
- storageClassName: cfg.storageClassName,
- accessModes: [ "ReadWriteOnce" ],
- resources: {
- requests: {
- storage: "50Gi",
- },
- },
+ riot: riot {
+ ns: app.namespace,
+ cfg+: {
+ webDomain: cfg.webDomain,
+ serverName: cfg.serverName,
+ image: cfg.images.riot,
},
},
- // homeserver.yaml that will be used to run synapse (in synapseConfigMap).
- // This is based off of //app/matrix/lib/synapse/homeserver.yaml with some fields overriden per
- // deployment.
- synapseConfig:: (std.native("parseYaml"))(importstr "synapse/homeserver-ng.yaml")[0] {
- server_name: cfg.serverName,
- public_baseurl: "https://%s" % [cfg.webDomain],
- signing_key_path: "/secrets/homeserver_signing_key",
- app_service_config_files: [
- "/appservices/%s/registration.yaml" % [k]
- for k in std.objectFields(app.appservices)
- ],
- } + (if cfg.cas.enable then {
- cas_config: {
- enabled: true,
- server_url: "https://%s/_cas" % [cfg.webDomain],
- service_url: "https://%s" % [cfg.webDomain],
- },
- } else {}),
-
- synapseConfigMap: kube.ConfigMap("synapse") {
- metadata+: app.metadata("synapse"),
- data: {
- "homeserver.yaml": std.manifestYamlDoc(app.synapseConfig),
- "log.config": importstr "synapse/log.config",
- },
- },
-
- // homeserver-secrets.yaml contains all the templated secret variables from
- // base homeserver.yaml passed as yaml-encoded environment variable.
- // $(ENVVAR)-encoded variables are resolved by Kubernetes on pod startup
- synapseSecretsConfig:: (std.native("parseYaml"))(importstr "synapse/homeserver-secrets.yaml")[0] {
- } + (if cfg.oidc.enable then {
- oidc_config: cfg.oidc.config {
- enabled: true,
- client_secret: "$(OIDC_CLIENT_SECRET)",
- },
- } else {}),
-
cas: if cfg.cas.enable && cfg.oidc.enable then error "cfg.cas.enable and cfg.oidc.enable options are exclusive"
- else if cfg.cas.enable then {
- deployment: kube.Deployment("oauth2-cas-proxy") {
- metadata+: app.metadata("oauth2-cas-proxy"),
- spec+: {
- replicas: 1,
- template+: {
- spec+: {
- containers_: {
- proxy: kube.Container("oauth2-cas-proxy") {
- image: cfg.images.casProxy,
- ports_: {
- http: { containerPort: 5000 },
- },
- env_: {
- BASE_URL: "https://%s" % [cfg.webDomain],
- SERVICE_URL: "https://%s" % [cfg.webDomain],
- OAUTH2_CLIENT: cfg.cas.oauth2.clientID,
- OAUTH2_SECRET: cfg.cas.oauth2.clientSecret,
- OAUTH2_SCOPE: cfg.cas.oauth2.scope,
- OAUTH2_AUTHORIZE: cfg.cas.oauth2.authorizeURL,
- OAUTH2_TOKEN: cfg.cas.oauth2.tokenURL,
- OAUTH2_USERINFO: cfg.cas.oauth2.userinfoURL,
- },
- },
- },
- },
- },
- },
- },
-
- svc: kube.Service("oauth2-cas-proxy") {
- metadata+: app.metadata("oauth2-cas-proxy"),
- target_pod:: app.cas.deployment.spec.template,
- },
- },
-
- # Synapse process Deployment/StatefulSet base resource.
- SynapseWorker(name, workerType, builder):: builder(name) {
- local worker = self,
- cfg:: {
- # Configuration customization. Can contain environment substitution
- # syntax, as used in worker_name value.
- localConfig: {
- worker_app: workerType,
- worker_name: "$(POD_NAME)",
-
- # The replication listener on the main synapse process.
- worker_replication_host: "synapse-replication-master",
- worker_replication_http_port: 9093,
- },
-
- # Mount app.dataVolume in /data
- mountData: false,
- },
-
- metadata+: app.metadata(name),
- spec+: {
- replicas: 1,
- template+: {
- spec+: {
- volumes_: {
- config: kube.ConfigMapVolume(app.synapseConfigMap),
- secrets: { secret: { secretName: "synapse" } },
- } + {
- [k]: { secret: { secretName: "appservice-%s-registration" % [k] } }
- for k in std.objectFields(app.appservices)
- } + if worker.cfg.mountData then {
- data: kube.PersistentVolumeClaimVolume(app.dataVolume),
- } else {},
- containers_: {
- web: kube.Container("synapse") {
- image: cfg.images.synapse,
- command: [
- "/bin/sh", "-c", |||
- set -e
- echo "${X_SECRETS_CONFIG}" > /tmp/secrets.yaml
- echo "${X_LOCAL_CONFIG}" > /tmp/local.yaml
- exec python -m ${SYNAPSE_WORKER} --config-path /conf/homeserver.yaml --config-path /tmp/secrets.yaml --config-path /tmp/local.yaml
- |||
- ],
- ports_: {
- http: { containerPort: 8008 },
- metrics: { containerPort: 9092 },
- replication: { containerPort: 9093 },
- },
- env_: {
- SYNAPSE_WORKER: workerType,
-
- SYNAPSE_MACAROON_SECRET_KEY: { secretKeyRef: { name: "synapse", key: "macaroon_secret_key" } },
- SYNAPSE_REGISTRATION_SHARED_SECRET: { secretKeyRef: { name: "synapse", key: "registration_shared_secret" } },
- WORKER_REPLICATION_SECRET: { secretKeyRef: { name: "synapse", key: "worker_replication_secret" } },
- POSTGRES_PASSWORD: { secretKeyRef: { name: "synapse", key: "postgres_password" } },
- REDIS_PASSWORD: { secretKeyRef: { name: "synapse", key: "redis_password" } },
- POD_NAME: { fieldRef: { fieldPath: "metadata.name" } },
- OIDC_CLIENT_SECRET: if cfg.oidc.enable then cfg.oidc.config.client_secret else "",
-
- X_SECRETS_CONFIG: std.manifestYamlDoc(app.synapseSecretsConfig),
- X_LOCAL_CONFIG: std.manifestYamlDoc(worker.cfg.localConfig),
- },
- volumeMounts_: {
- config: { mountPath: "/conf", },
- secrets: { mountPath: "/secrets" },
- } + {
- [k]: { mountPath: "/appservices/%s" % [k] }
- for k in std.objectFields(app.appservices)
- } + if worker.cfg.mountData then {
- data: { mountPath: "/data" },
- } else {},
- },
- },
- securityContext: {
- runAsUser: 991,
- runAsGroup: 991,
- fsGroup: 991,
- },
- },
- },
- },
- },
-
- # Synapse main process
- synapseDeployment: app.SynapseWorker("synapse", "synapse.app.homeserver", kube.Deployment) {
+ else if cfg.cas.enable then cas {
+ ns: app.namespace,
cfg+: {
- # Main process doesn't need any configuration customization
- localConfig: {}
- },
- },
- synapseSvc: kube.Service("synapse") {
- metadata+: app.metadata("synapse"),
- target_pod:: app.synapseDeployment.spec.template,
- },
- synapseReplicationSvc: kube.Service("synapse-replication-master") {
- metadata+: app.metadata("synapse-replication-master"),
- target_pod:: app.synapseDeployment.spec.template,
- spec+: {
- ports: [
- { port: 9093, name: 'replication', targetPort: 9093 },
- ],
+ image: cfg.images.casProxy,
+ webDomain: cfg.webDomain,
+ oauth2: cfg.cas.oauth2,
},
},
- # Synapse generic worker deployment
- synapseGenericWorker: app.SynapseWorker("synapse-generic", "synapse.app.generic_worker", kube.StatefulSet) {
+ wellKnown: if cfg.wellKnown then wellKnown {
+ ns: app.namespace,
cfg+: {
- localConfig+: {
- worker_listeners: [{
- type: "http",
- port: 8008,
- x_forwarded: true,
- bind_addresses: ["::"],
- resources: [{ names: ["client", "federation"]}],
- }],
- },
- },
- },
- synapseGenericSvc: kube.Service("synapse-generic") {
- metadata+: app.metadata("synapse-generic"),
- target_pod:: app.synapseGenericWorker.spec.template,
- },
-
- # Following paths can be handled by generic workers.
- # See: https://github.com/matrix-org/synapse/blob/master/docs/workers.md
- synapseGenericWorkerPaths:: [
- "/_matrix/client/(v2_alpha|r0)/sync",
- "/_matrix/client/(api/v1|v2_alpha|r0)/events",
- "/_matrix/client/(api/v1|r0)/initialSync",
- "/_matrix/client/(api/v1|r0)/rooms/[^/]+/initialSync",
- "/_matrix/client/(api/v1|r0|unstable)/publicRooms",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/joined_members",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/context/.*",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/members",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/state",
- "/_matrix/client/(api/v1|r0|unstable)/account/3pid",
- "/_matrix/client/(api/v1|r0|unstable)/keys/query",
- "/_matrix/client/(api/v1|r0|unstable)/keys/changes",
- "/_matrix/client/versions",
- "/_matrix/client/(api/v1|r0|unstable)/voip/turnServer",
- "/_matrix/client/(api/v1|r0|unstable)/joined_groups",
- "/_matrix/client/(api/v1|r0|unstable)/publicised_groups",
- "/_matrix/client/(api/v1|r0|unstable)/publicised_groups/",
- # Blocked by https://github.com/matrix-org/synapse/issues/8966
- # "/_matrix/client/(api/v1|r0|unstable)/login",
- # "/_matrix/client/(r0|unstable)/register",
- # "/_matrix/client/(r0|unstable)/auth/.*/fallback/web",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/send",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/state/",
- "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/(join|invite|leave|ban|unban|kick)",
- "/_matrix/client/(api/v1|r0|unstable)/join/",
- "/_matrix/client/(api/v1|r0|unstable)/profile/",
- "/_matrix/federation/v1/event/",
- "/_matrix/federation/v1/state/",
- "/_matrix/federation/v1/state_ids/",
- "/_matrix/federation/v1/backfill/",
- "/_matrix/federation/v1/get_missing_events/",
- "/_matrix/federation/v1/publicRooms",
- "/_matrix/federation/v1/query/",
- "/_matrix/federation/v1/make_join/",
- "/_matrix/federation/v1/make_leave/",
- "/_matrix/federation/v1/send_join/",
- "/_matrix/federation/v2/send_join/",
- "/_matrix/federation/v1/send_leave/",
- "/_matrix/federation/v2/send_leave/",
- "/_matrix/federation/v1/invite/",
- "/_matrix/federation/v2/invite/",
- "/_matrix/federation/v1/query_auth/",
- "/_matrix/federation/v1/event_auth/",
- "/_matrix/federation/v1/exchange_third_party_invite/",
- "/_matrix/federation/v1/user/devices/",
- "/_matrix/federation/v1/get_groups_publicised",
- "/_matrix/key/v2/query",
- "/_matrix/federation/v1/send/",
- ],
-
- # Synapse media worker. This handles access to uploads and media stored in app.dataVolume
- synapseMediaWorker: app.SynapseWorker("synapse-media", "synapse.app.media_repository", kube.StatefulSet) {
- cfg+: {
- mountData: true,
- localConfig+: {
- worker_listeners: [{
- type: "http",
- port: 8008,
- x_forwarded: true,
- bind_addresses: ["::"],
- resources: [{ names: ["media"]}],
- }],
- },
- },
- },
- synapseMediaSvc: kube.Service("synapse-media") {
- metadata+: app.metadata("synapse-media"),
- target_pod:: app.synapseMediaWorker.spec.template,
- },
-
- riotConfig:: {
- "default_hs_url": "https://%s" % [cfg.webDomain],
- "disable_custom_urls": false,
- "disable_guests": false,
- "disable_login_language_selector": false,
- "disable_3pid_login": true,
- "brand": "Riot",
- "integrations_ui_url": "https://scalar.vector.im/",
- "integrations_rest_url": "https://scalar.vector.im/api",
- "integrations_jitsi_widget_url": "https://scalar.vector.im/api/widgets/jitsi.html",
-
- "bug_report_endpoint_url": "https://riot.im/bugreports/submit",
- "features": {
- "feature_groups": "labs",
- "feature_pinning": "labs",
- "feature_reactions": "labs"
- },
- "default_federate": true,
- "default_theme": "light",
- "roomDirectory": {
- "servers": [
- cfg.serverName,
- ]
- },
- "welcomeUserId": "@riot-bot:matrix.org",
- "enable_presence_by_hs_url": {
- "https://matrix.org": false
- }
- },
-
- riotConfigMap: kube.ConfigMap("riot-web-config") {
- metadata+: app.metadata("riot-web-config"),
- data: {
- "config.json": std.manifestJsonEx(app.riotConfig, ""),
- // Standard nginx.conf, made to work when running as unprivileged user.
- "nginx.conf": importstr "riot-nginx.conf",
- },
- },
-
- riotDeployment: kube.Deployment("riot-web") {
- metadata+: app.metadata("riot-web"),
- spec+: {
- replicas: 1,
- template+: {
- spec+: {
- volumes_: {
- config: kube.ConfigMapVolume(app.riotConfigMap),
- },
- containers_: {
- web: kube.Container("riot-web") {
- image: cfg.images.riot,
- ports_: {
- http: { containerPort: 8080 },
- },
- volumeMounts: [
- {
- name: "config",
- mountPath: "/app/config.json",
- subPath: "config.json",
- },
- {
- name: "config",
- mountPath: "/etc/nginx/nginx.conf",
- subPath: "nginx.conf",
- },
- ],
- },
- },
- securityContext: {
- // nginx:nginx
- runAsUser: 101,
- runAsGroup: 101,
- },
- },
- },
- },
- },
-
- riotSvc: kube.Service("riot-web") {
- metadata+: app.metadata("riot-web"),
- target_pod:: app.riotDeployment.spec.template,
- },
-
- wellKnown: if cfg.wellKnown then {
- deployment: kube.Deployment("wellknown") {
- metadata+: app.metadata("wellknown"),
- spec+: {
- replicas: 1,
- template+: {
- spec+: {
- containers_: {
- web: kube.Container("wellknown") {
- image: cfg.images.wellKnown,
- ports_: {
- http: { containerPort: 8080 },
- },
- command: ["/app/matrix/wellknown"],
- args: ["-hspki_disable", "-domain", cfg.webDomain],
- },
- },
- securityContext: {
- runAsUser: 101,
- runAsGroup: 101,
- },
- },
- },
- },
- },
- svc: kube.Service("wellknown") {
- metadata+: app.metadata("wellknown"),
- target_pod:: app.wellKnown.deployment.spec.template,
+ image: cfg.images.wellKnown,
+ webDomain: cfg.webDomain,
},
} else {},
+ synapse: synapse {
+ ns: app.namespace,
+ postgres: app.postgres3,
+ redis: app.redis,
+ appservices: app.appservices,
+ cfg+: app.cfg {
+ image: app.cfg.images.synapse,
+
+ macaroonSecretKey: { secretKeyRef: { name: "synapse", key: "macaroon_secret_key" } },
+ registrationSharedSecret: { secretKeyRef: { name: "synapse", key: "registration_shared_secret" } },
+ workerReplicationSecret: { secretKeyRef: { name: "synapse", key: "worker_replication_secret" } },
+ },
+ },
+
// Any appservice you add here will require an appservice-X-registration
// secret containing a registration.yaml file. Adding something to this
// dictionary will cause Synapse to not start until that secret is
@@ -545,8 +191,8 @@
// until it spits you a registration YAML and you feed that to a secret.
appservices: {},
- ingress: kube.Ingress("matrix") {
- metadata+: app.metadata("matrix") {
+ ingress: app.namespace.Contain(kube.Ingress("matrix")) {
+ metadata+: {
annotations+: {
"kubernetes.io/tls-acme": "true",
"certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
@@ -566,15 +212,15 @@
host: cfg.webDomain,
http: {
paths: [
- { path: path, backend: app.synapseGenericSvc.name_port }
- for path in app.synapseGenericWorkerPaths
+ { path: path, backend: app.synapse.genericWorker.svc.name_port }
+ for path in app.synapse.genericWorker.paths
] + [
- { path: "/", backend: app.riotSvc.name_port },
- { path: "/_matrix/media/", backend: app.synapseMediaSvc.name_port },
- { path: "/_matrix/", backend: app.synapseSvc.name_port },
+ { path: "/", backend: app.riot.svc.name_port },
+ { path: "/_matrix/media/", backend: app.synapse.mediaWorker.svc.name_port },
+ { path: "/_matrix/", backend: app.synapse.main.svc.name_port },
# Used by OpenID Connect login flow
- { path: "/_synapse/", backend: app.synapseSvc.name_port },
+ { path: "/_synapse/", backend: app.synapse.main.svc.name_port },
] + (if cfg.cas.enable then [
{ path: "/_cas", backend: app.cas.svc.name_port },
] else []) + (if cfg.wellKnown then [
diff --git a/app/matrix/lib/riot.libsonnet b/app/matrix/lib/riot.libsonnet
new file mode 100644
index 0000000..fc2f2e7
--- /dev/null
+++ b/app/matrix/lib/riot.libsonnet
@@ -0,0 +1,95 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+ local app = self,
+ local cfg = app.cfg,
+ cfg:: {
+ # webDomain is the domain name at which element will run
+ webDomain: error "cfg.webDomain must be set",
+ # serverName is the server part of the MXID this homeserver will cover
+ serverName: error "cfg.serverName must be set",
+ image: error "cfg.image must be set",
+ },
+
+ ns:: error "ns needs to be a kube.Namespace object",
+
+ config:: {
+ "default_hs_url": "https://%s" % [cfg.webDomain],
+ "disable_custom_urls": false,
+ "disable_guests": false,
+ "disable_login_language_selector": false,
+ "disable_3pid_login": true,
+ "brand": "Riot",
+ "integrations_ui_url": "https://scalar.vector.im/",
+ "integrations_rest_url": "https://scalar.vector.im/api",
+ "integrations_jitsi_widget_url": "https://scalar.vector.im/api/widgets/jitsi.html",
+
+ "bug_report_endpoint_url": "https://riot.im/bugreports/submit",
+ "features": {
+ "feature_groups": "labs",
+ "feature_pinning": "labs",
+ "feature_reactions": "labs"
+ },
+ "default_federate": true,
+ "default_theme": "light",
+ "roomDirectory": {
+ "servers": [
+ cfg.serverName,
+ ]
+ },
+ "welcomeUserId": "@riot-bot:matrix.org",
+ "enable_presence_by_hs_url": {
+ "https://matrix.org": false
+ }
+ },
+
+ configMap: app.ns.Contain(kube.ConfigMap("riot-web-config")) {
+ data: {
+ "config.json": std.manifestJsonEx(app.config, ""),
+ // Standard nginx.conf, made to work when running as unprivileged user.
+ "nginx.conf": importstr "riot/nginx.conf",
+ },
+ },
+
+ deployment: app.ns.Contain(kube.Deployment("riot-web")) {
+ spec+: {
+ replicas: 1,
+ template+: {
+ spec+: {
+ volumes_: {
+ config: kube.ConfigMapVolume(app.configMap),
+ },
+ containers_: {
+ web: kube.Container("riot-web") {
+ image: cfg.image,
+ ports_: {
+ http: { containerPort: 8080 },
+ },
+ volumeMounts: [
+ {
+ name: "config",
+ mountPath: "/app/config.json",
+ subPath: "config.json",
+ },
+ {
+ name: "config",
+ mountPath: "/etc/nginx/nginx.conf",
+ subPath: "nginx.conf",
+ },
+ ],
+ },
+ },
+ securityContext: {
+ // nginx:nginx
+ runAsUser: 101,
+ runAsGroup: 101,
+ },
+ },
+ },
+ },
+ },
+
+ svc: app.ns.Contain(kube.Service("riot-web")) {
+ target_pod:: app.deployment.spec.template,
+ },
+}
diff --git a/app/matrix/lib/riot-nginx.conf b/app/matrix/lib/riot/nginx.conf
similarity index 100%
rename from app/matrix/lib/riot-nginx.conf
rename to app/matrix/lib/riot/nginx.conf
diff --git a/app/matrix/lib/synapse.libsonnet b/app/matrix/lib/synapse.libsonnet
new file mode 100644
index 0000000..ea7bff2
--- /dev/null
+++ b/app/matrix/lib/synapse.libsonnet
@@ -0,0 +1,276 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+ local app = self,
+ local cfg = app.cfg,
+ cfg:: {
+ image: error "cfg.image needs to be set",
+ storageClassName: error "cfg.storrageClassName needs to be set",
+
+ # webDomain is the domain name at which synapse instance will run
+ webDomain: error "cfg.webDomain must be set",
+ # serverName is the server part of the MXID this homeserver will cover
+ serverName: error "cfg.serverName must be set",
+
+ cas: { enable: false },
+ oidc: { enable: false },
+
+ macaroonSecretKey: error "cfg.macaroonSecretKey needs to be set",
+ registrationSharedSecret: error "cfg.registationSharedSecret needs to be set",
+ workerReplicationSecret: error "cfg.workerReplicationSecret needs to be set",
+ },
+
+ ns:: error "ns needs to be provided",
+ postgres:: error "postgres needs to be provided",
+ redis:: error "redis needs to be provided",
+
+ // See matrix-ng.libsonnet for description
+ appservices:: error "appservices need to be provided",
+
+ dataVolume: app.ns.Contain(kube.PersistentVolumeClaim("synapse-data-waw3")) {
+ spec+: {
+ storageClassName: cfg.storageClassName,
+ accessModes: [ "ReadWriteOnce" ],
+ resources: {
+ requests: {
+ storage: "50Gi",
+ },
+ },
+ },
+ },
+
+ // homeserver.yaml that will be used to run synapse (in configMap).
+ // This is based off of //app/matrix/lib/synapse/homeserver.yaml with some fields overriden per
+ // deployment.
+ config:: (std.native("parseYaml"))(importstr "synapse/homeserver-ng.yaml")[0] {
+ server_name: cfg.serverName,
+ public_baseurl: "https://%s" % [cfg.webDomain],
+ signing_key_path: "/secrets/homeserver_signing_key",
+ app_service_config_files: [
+ "/appservices/%s/registration.yaml" % [k]
+ for k in std.objectFields(app.appservices)
+ ],
+ } + (if cfg.cas.enable then {
+ cas_config: {
+ enabled: true,
+ server_url: "https://%s/_cas" % [cfg.webDomain],
+ service_url: "https://%s" % [cfg.webDomain],
+ },
+ } else {}),
+
+ configMap: app.ns.Contain(kube.ConfigMap("synapse")) {
+ data: {
+ "homeserver.yaml": std.manifestYamlDoc(app.config),
+ "log.config": importstr "synapse/log.config",
+ },
+ },
+
+ // homeserver-secrets.yaml contains all the templated secret variables from
+ // base homeserver.yaml passed as yaml-encoded environment variable.
+ // $(ENVVAR)-encoded variables are resolved by Kubernetes on pod startup
+ secretsConfig:: (std.native("parseYaml"))(importstr "synapse/homeserver-secrets.yaml")[0] {
+ } + (if cfg.oidc.enable then {
+ oidc_config: cfg.oidc.config {
+ enabled: true,
+ client_secret: "$(OIDC_CLIENT_SECRET)",
+ },
+ } else {}),
+
+ # Synapse process Deployment/StatefulSet base resource.
+ SynapseWorker(name, workerType, builder):: app.ns.Contain(builder(name)) {
+ local worker = self,
+ cfg:: {
+ # Configuration customization. Can contain environment substitution
+ # syntax, as used in worker_name value.
+ localConfig: {
+ worker_app: workerType,
+ worker_name: "$(POD_NAME)",
+
+ # The replication listener on the main synapse process.
+ worker_replication_host: "synapse-replication-master",
+ worker_replication_http_port: 9093,
+ },
+
+ # Mount app.dataVolume in /data
+ mountData: false,
+ },
+
+ spec+: {
+ replicas: 1,
+ template+: {
+ spec+: {
+ volumes_: {
+ config: kube.ConfigMapVolume(app.configMap),
+ secrets: { secret: { secretName: "synapse" } },
+ } + {
+ [k]: { secret: { secretName: "appservice-%s-registration" % [k] } }
+ for k in std.objectFields(app.appservices)
+ } + if worker.cfg.mountData then {
+ data: kube.PersistentVolumeClaimVolume(app.dataVolume),
+ } else {},
+ containers_: {
+ web: kube.Container("synapse") {
+ image: cfg.image,
+ command: [
+ "/bin/sh", "-c", |||
+ set -e
+ echo "${X_SECRETS_CONFIG}" > /tmp/secrets.yaml
+ echo "${X_LOCAL_CONFIG}" > /tmp/local.yaml
+ exec python -m ${SYNAPSE_WORKER} --config-path /conf/homeserver.yaml --config-path /tmp/secrets.yaml --config-path /tmp/local.yaml
+ |||
+ ],
+ ports_: {
+ http: { containerPort: 8008 },
+ metrics: { containerPort: 9092 },
+ replication: { containerPort: 9093 },
+ },
+ env_: {
+ SYNAPSE_WORKER: workerType,
+
+ SYNAPSE_MACAROON_SECRET_KEY: cfg.macaroonSecretKey,
+ SYNAPSE_REGISTRATION_SHARED_SECRET: cfg.registrationSharedSecret,
+ WORKER_REPLICATION_SECRET: cfg.workerReplicationSecret,
+ POSTGRES_PASSWORD: app.postgres.cfg.password,
+ REDIS_PASSWORD: app.redis.cfg.password,
+ POD_NAME: { fieldRef: { fieldPath: "metadata.name" } },
+ OIDC_CLIENT_SECRET: if cfg.oidc.enable then cfg.oidc.config.client_secret else "",
+
+ X_SECRETS_CONFIG: std.manifestYamlDoc(app.secretsConfig),
+ X_LOCAL_CONFIG: std.manifestYamlDoc(worker.cfg.localConfig),
+ },
+ volumeMounts_: {
+ config: { mountPath: "/conf", },
+ secrets: { mountPath: "/secrets" },
+ } + {
+ [k]: { mountPath: "/appservices/%s" % [k] }
+ for k in std.objectFields(app.appservices)
+ } + if worker.cfg.mountData then {
+ data: { mountPath: "/data" },
+ } else {},
+ },
+ },
+ securityContext: {
+ runAsUser: 991,
+ runAsGroup: 991,
+ fsGroup: 991,
+ },
+ },
+ },
+ },
+ },
+
+ # Synapse main process
+ main: {
+ deployment: app.SynapseWorker("synapse", "synapse.app.homeserver", kube.Deployment) {
+ cfg+: {
+ # Main process doesn't need any configuration customization
+ localConfig: {}
+ },
+ },
+ svc: app.ns.Contain(kube.Service("synapse")) {
+ target_pod:: app.main.deployment.spec.template,
+ },
+ replicationSvc: app.ns.Contain(kube.Service("synapse-replication-master")) {
+ target_pod:: app.main.deployment.spec.template,
+ spec+: {
+ ports: [
+ { port: 9093, name: 'replication', targetPort: 9093 },
+ ],
+ },
+ },
+ },
+
+ genericWorker: {
+ # Synapse generic worker deployment
+ deployment: app.SynapseWorker("synapse-generic", "synapse.app.generic_worker", kube.StatefulSet) {
+ cfg+: {
+ localConfig+: {
+ worker_listeners: [{
+ type: "http",
+ port: 8008,
+ x_forwarded: true,
+ bind_addresses: ["::"],
+ resources: [{ names: ["client", "federation"]}],
+ }],
+ },
+ },
+ },
+ svc: app.ns.Contain(kube.Service("synapse-generic")) {
+ target_pod:: app.genericWorker.deployment.spec.template,
+ },
+
+ # Following paths can be handled by generic workers.
+ # See: https://github.com/matrix-org/synapse/blob/master/docs/workers.md
+ paths:: [
+ "/_matrix/client/(v2_alpha|r0)/sync",
+ "/_matrix/client/(api/v1|v2_alpha|r0)/events",
+ "/_matrix/client/(api/v1|r0)/initialSync",
+ "/_matrix/client/(api/v1|r0)/rooms/[^/]+/initialSync",
+ "/_matrix/client/(api/v1|r0|unstable)/publicRooms",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/joined_members",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/context/.*",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/members",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/state",
+ "/_matrix/client/(api/v1|r0|unstable)/account/3pid",
+ "/_matrix/client/(api/v1|r0|unstable)/keys/query",
+ "/_matrix/client/(api/v1|r0|unstable)/keys/changes",
+ "/_matrix/client/versions",
+ "/_matrix/client/(api/v1|r0|unstable)/voip/turnServer",
+ "/_matrix/client/(api/v1|r0|unstable)/joined_groups",
+ "/_matrix/client/(api/v1|r0|unstable)/publicised_groups",
+ "/_matrix/client/(api/v1|r0|unstable)/publicised_groups/",
+ # Blocked by https://github.com/matrix-org/synapse/issues/8966
+ # "/_matrix/client/(api/v1|r0|unstable)/login",
+ # "/_matrix/client/(r0|unstable)/register",
+ # "/_matrix/client/(r0|unstable)/auth/.*/fallback/web",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/send",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/state/",
+ "/_matrix/client/(api/v1|r0|unstable)/rooms/.*/(join|invite|leave|ban|unban|kick)",
+ "/_matrix/client/(api/v1|r0|unstable)/join/",
+ "/_matrix/client/(api/v1|r0|unstable)/profile/",
+ "/_matrix/federation/v1/event/",
+ "/_matrix/federation/v1/state/",
+ "/_matrix/federation/v1/state_ids/",
+ "/_matrix/federation/v1/backfill/",
+ "/_matrix/federation/v1/get_missing_events/",
+ "/_matrix/federation/v1/publicRooms",
+ "/_matrix/federation/v1/query/",
+ "/_matrix/federation/v1/make_join/",
+ "/_matrix/federation/v1/make_leave/",
+ "/_matrix/federation/v1/send_join/",
+ "/_matrix/federation/v2/send_join/",
+ "/_matrix/federation/v1/send_leave/",
+ "/_matrix/federation/v2/send_leave/",
+ "/_matrix/federation/v1/invite/",
+ "/_matrix/federation/v2/invite/",
+ "/_matrix/federation/v1/query_auth/",
+ "/_matrix/federation/v1/event_auth/",
+ "/_matrix/federation/v1/exchange_third_party_invite/",
+ "/_matrix/federation/v1/user/devices/",
+ "/_matrix/federation/v1/get_groups_publicised",
+ "/_matrix/key/v2/query",
+ "/_matrix/federation/v1/send/",
+ ],
+ },
+
+ # Synapse media worker. This handles access to uploads and media stored in app.dataVolume
+ mediaWorker: {
+ deployment: app.SynapseWorker("synapse-media", "synapse.app.media_repository", kube.StatefulSet) {
+ cfg+: {
+ mountData: true,
+ localConfig+: {
+ worker_listeners: [{
+ type: "http",
+ port: 8008,
+ x_forwarded: true,
+ bind_addresses: ["::"],
+ resources: [{ names: ["media"]}],
+ }],
+ },
+ },
+ },
+ svc: app.ns.Contain(kube.Service("synapse-media")) {
+ target_pod:: app.mediaWorker.deployment.spec.template,
+ },
+ },
+}
diff --git a/app/matrix/lib/wellknown.libsonnet b/app/matrix/lib/wellknown.libsonnet
new file mode 100644
index 0000000..cdc5ceb
--- /dev/null
+++ b/app/matrix/lib/wellknown.libsonnet
@@ -0,0 +1,41 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+ local app = self,
+ local cfg = app.cfg,
+ cfg:: {
+ image: error "cfg.image must be set",
+
+ # webDomain is the domain name of matrix homeserver to be served
+ webDomain: error "cfg.webDomain must be set",
+ },
+
+ ns:: error "ns needs to be a kube.Namespace object",
+
+ deployment: app.ns.Contain(kube.Deployment("wellknown")) {
+ spec+: {
+ replicas: 1,
+ template+: {
+ spec+: {
+ containers_: {
+ web: kube.Container("wellknown") {
+ image: cfg.image,
+ ports_: {
+ http: { containerPort: 8080 },
+ },
+ command: ["/app/matrix/wellknown"],
+ args: ["-hspki_disable", "-domain", cfg.webDomain],
+ },
+ },
+ securityContext: {
+ runAsUser: 101,
+ runAsGroup: 101,
+ },
+ },
+ },
+ },
+ },
+ svc: app.ns.Contain(kube.Service("wellknown")) {
+ target_pod:: app.deployment.spec.template,
+ },
+}