matrix: refactor synapse into library

This is in prepration for bringing up a Matrix server for hsp.sh.

Verified to cause no diff on prod.

Change-Id: Ied2de210692e3ddfdb1d3f37b12893b214c34b0b
diff --git a/app/matrix/lib/matrix.libsonnet b/app/matrix/lib/matrix.libsonnet
new file mode 100644
index 0000000..300cf31
--- /dev/null
+++ b/app/matrix/lib/matrix.libsonnet
@@ -0,0 +1,279 @@
+# Matrix server (synapse).
+# This needs a secret provisioned, create with:
+#    ns=matrix
+#    kubectl -n $ns create secret generic synapse --from-literal=postgres_password=$(pwgen 24 1) --from-literal=macaroon_secret_key=$(pwgen 32 1) --from-literal=registration_shared_secret=$(pwgen 32 1)
+#    kubectl -n $ns create secret generic oauth2-cas-proxy --from-literal=oauth2_secret=...
+#
+# 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 -
+# this is done via specialy named secrets (appservice-X-registration, for X key
+# in the appservices object).
+#
+# For appservice-irc instances, you can use this oneliner magic to get the
+# registration YAML from logs.
+#    kubectl -n matrix create secret generic appservice-irc-freenode-registration --from-file=registration.yaml=<(kubectl logs -n matrix $(kubectl get pods -n matrix --selector=job-name=appservice-irc-freenode-bootstrap --output=jsonpath='{.items[*].metadata.name}') | tail -n +4 | sed -r 's/(.*aliases:.*)/      group_id: "+freenode:hackerspace.pl"\n\1/')
+#
+# For appservice-telegram instances, you can use this oneliner magic:
+#    kubectl -n matrix create secret generic appservice-telegram-prod-registration --from-file=registration.yaml=<(kubectl -n matrix logs job/appservice-telegram-prod-bootstrap | grep -A 100 SNIPSNIP | grep -v SNIPSNIP)
+
+local kube = import "../../../kube/kube.libsonnet";
+local postgres = import "../../../kube/postgres.libsonnet";
+
+{
+    local app = self,
+    local cfg = app.cfg,
+    cfg:: {
+        namespace: error "cfg.namespace must be set",
+        # 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",
+        storageClassName: "waw-hdd-redundant-3",
+
+        synapseImage: "matrixdotorg/synapse:v1.19.2",
+        riotImage: "vectorim/riot-web:v1.7.7",
+        casProxyImage: "registry.k0.hswaw.net/q3k/oauth2-cas-proxy:0.1.4",
+        appserviceIRCImage: "matrixdotorg/matrix-appservice-irc:release-0.17.1",
+        # That's v0.8.2 - we just don't trust that host to not re-tag images.
+        appserviceTelegramImage: "dock.mau.dev/tulir/mautrix-telegram@sha256:9e68eaa80c9e4a75d9a09ec92dc4898b12d48390e01efa4de40ce882a6f7e330"
+    },
+
+    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 {
+        cfg+: {
+            namespace: cfg.namespace,
+            appName: "synapse",
+            database: "synapse",
+            username: "synapse",
+            prefix: "waw3-",
+            password: { secretKeyRef: { name: "synapse", key: "postgres_password" } },
+            storageClassName: cfg.storageClassName,
+            storageSize: "100Gi",
+        },
+    },
+
+    dataVolume: kube.PersistentVolumeClaim("synapse-data-waw3") {
+        metadata+: app.metadata("synapse-data"),
+        spec+: {
+            storageClassName: cfg.storageClassName,
+            accessModes: [ "ReadWriteOnce" ],
+            resources: {
+                requests: {
+                    storage: "50Gi",
+                },
+            },
+        },
+    },
+
+    synapseConfig: kube.ConfigMap("synapse") {
+        metadata+: app.metadata("synapse"),
+        data: {
+            "homeserver.yaml": importstr "synapse/homeserver.yaml",
+            "log.config": importstr "synapse/log.config",
+        },
+    },
+
+    casDeployment: 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.casProxyImage,
+                            ports_: {
+                                http: { containerPort: 5000 },
+                            },
+                            env_: {
+                                BASE_URL: "https://%s" % [cfg.webDomain],
+                                SERVICE_URL: "https://%s" % [cfg.webDomain],
+                                OAUTH2_CLIENT: "matrix",
+                                OAUTH2_SECRET: { secretKeyRef: { name: "oauth2-cas-proxy", key: "oauth2_secret" } },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+    },
+
+    casSvc: kube.Service("oauth2-cas-proxy") {
+        metadata+: app.metadata("oauth2-cas-proxy"),
+        target_pod:: app.casDeployment.spec.template,
+    },
+
+    synapseDeployment: kube.Deployment("synapse") {
+        metadata+: app.metadata("synapse"),
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        data: kube.PersistentVolumeClaimVolume(app.dataVolume),
+                        config_template: kube.ConfigMapVolume(app.synapseConfig),
+                    } + {
+                        [k]: { secret: { secretName: "appservice-%s-registration" % [k] } }
+                        for k in std.objectFields(app.appservices)
+                    },
+                    containers_: {
+                        web: kube.Container("synapse") {
+                            image: cfg.synapseImage,
+                            command: ["/bin/sh", "-c", "/start.py migrate_config && exec /start.py"],
+                            ports_: {
+                                http: { containerPort: 8008 },
+                                metrics: { containerPort: 9092 },
+                            },
+                            env_: {
+                                SYNAPSE_CONFIG_DIR: "/config",
+                                SYNAPSE_CONFIG_PATH: "/config/homeserver.yaml",
+
+                                # These values are not used in a template, but
+                                # are required by /start.py migrate_config
+                                SYNAPSE_SERVER_NAME: cfg.serverName,
+                                SYNAPSE_REPORT_STATS: "no",
+
+                                SYNAPSE_MACAROON_SECRET_KEY: { secretKeyRef: { name: "synapse", key: "macaroon_secret_key" } },
+                                SYNAPSE_REGISTRATION_SHARED_SECRET: { secretKeyRef: { name: "synapse", key: "registration_shared_secret" } },
+                                POSTGRES_PASSWORD: { secretKeyRef: { name: "synapse", key: "postgres_password" } },
+                            },
+                            volumeMounts_: {
+                                data: { mountPath: "/data" },
+                                config_template: {
+                                    mountPath: "/conf/homeserver.yaml",
+                                    subPath: "homeserver.yaml",
+                                },
+                            } + {
+                                [k]: { mountPath: "/appservices/%s" % [k] }
+                                for k in std.objectFields(app.appservices)
+                            },
+                        },
+                    },
+                },
+            },
+        },
+    },
+
+    synapseSvc: kube.Service("synapse") {
+        metadata+: app.metadata("synapse"),
+        target_pod:: app.synapseDeployment.spec.template,
+    },
+
+    riotConfig: kube.ConfigMap("riot-web-config") {
+        metadata+: app.metadata("riot-web-config"),
+        data: {
+            "config.json": std.manifestJsonEx({
+                "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
+                }
+            }, ""),
+        },
+    },
+
+    riotDeployment: kube.Deployment("riot-web") {
+        metadata+: app.metadata("riot-web"),
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        config: kube.ConfigMapVolume(app.riotConfig),
+                    },
+                    containers_: {
+                        web: kube.Container("riot-web") {
+                            image: cfg.riotImage,
+                            ports_: {
+                                http: { containerPort: 80 },
+                            },
+                            volumeMounts_: {
+                                config: {
+                                    mountPath: "/app/config.json",
+                                    subPath: "config.json",
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+    },
+
+    riotSvc: kube.Service("riot-web") {
+        metadata+: app.metadata("riot-web"),
+        target_pod:: app.riotDeployment.spec.template,
+    },
+
+    // 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
+    // available - so change things carefully!
+    // If bootstrapping a new appservice, just keep it out of this dictionary
+    // until it spits you a registration YAML and you feed that to a secret.
+    appservices: {},
+
+    ingress: kube.Ingress("matrix") {
+        metadata+: app.metadata("matrix") {
+            annotations+: {
+                "kubernetes.io/tls-acme": "true",
+                "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+                "nginx.ingress.kubernetes.io/proxy-body-size": "0",
+            },
+        },
+        spec+: {
+            tls: [
+                {
+                    hosts: [cfg.webDomain],
+                    secretName: "synapse-tls",
+                },
+            ],
+            rules: [
+                {
+                    host: cfg.webDomain,
+                    http: {
+                        paths: [
+                            { path: "/", backend: app.riotSvc.name_port },
+                            { path: "/_matrix", backend: app.synapseSvc.name_port },
+                            { path: "/_cas", backend: app.casSvc.name_port },
+                        ]
+                    },
+                }
+            ],
+        },
+    },
+
+}