matrix: add Telegram bridge appservice.

Configuring this one is a bit different from appservice-irc. Notably,
there's no way to give it a registration.yaml to overlay on top of a
config, se we end up using an init container with yq to do that for us.

Also, I had to manually copy the regsitration.yaml in synapse, from
/appservices/telegram-prod/registration.yaml to
/data/appservices/telegram-prod.jsonnet, in order to make it work with
the synapse docker start magic. :/

Otherwise, this is deployed and seems to be working.

Change-Id: Id747a0e310221855556c1d280439376f0c4e5ed6
diff --git a/app/matrix/appservice-telegram.libsonnet b/app/matrix/appservice-telegram.libsonnet
new file mode 100644
index 0000000..b174225
--- /dev/null
+++ b/app/matrix/appservice-telegram.libsonnet
@@ -0,0 +1,157 @@
+local kube = import "../../kube/kube.libsonnet";
+
+{
+    AppServiceTelegram(name):: {
+        local bridge = self,
+        local cfg = bridge.cfg,
+        cfg:: {
+            metadata: {},
+            image: error "image must be set",
+            storageClassName: error "storageClassName must be set",
+
+            // Data that will be serialized into the appservice's config.yaml.
+            // This is taken straight from a YAML that was generated by
+            // dock.mau.dev/tulir/mautrix-telegram:v0.8.2. We override here
+            // fields that we know are strictly necessary to be configured when
+            // instantiating this template.
+            config: (std.native("parseYaml")(importstr "appservice-telegram.yaml")[0]) + {
+                homeserver+: {
+                    address: error "homeserver.address must be set",
+                    domain: error "homeserver.domain must be set",
+                },
+                appservice+: {
+                    address: bridge.svc.http_url,
+                    // We disable this. I have no idea what it does, but it
+                    // wants a secret. ~q3k
+                    provisioning+: {
+                        enabled: false,
+                        shared_secret: if self.enabled then error "appservice.provisioning.shared_secret must be set" else "hackme",
+                    },
+                    id: error "appservice.id must be set",
+                    as_token: "This value is generated when generating the registration",
+                    hs_token: "This value is generated when generating the registration",
+                },
+                telegram+: {
+                    api_id: error "telegram.api_id must be set",
+                    api_hash: error "telegram.api_hash must be set",
+                    bot_token: error "telegram.bot_token must be set",
+                },
+                bridge+: {
+                    permissions: {
+                        '*': "relaybot",
+                    },
+                },
+            },
+        },
+
+        config: kube.Secret("appservice-telegram-%s" % [name]) {
+            metadata+: cfg.metadata,
+            data: {
+                "config.yaml": std.base64(std.manifestYamlDoc(cfg.config)),
+            },
+        },
+
+        dataVolume: kube.PersistentVolumeClaim("appservice-telegram-%s" % [name]) {
+            metadata+: cfg.metadata,
+            spec+: {
+                storageClassName: cfg.storageClassName,
+                accessModes: [ "ReadWriteOnce" ],
+                resources: {
+                    requests: {
+                        storage: "10Gi",
+                    },
+                },
+            },
+        },
+
+        bootstrapJob: kube.Job("appservice-telegram-%s-bootstrap" % [name]) {
+            metadata+: cfg.metadata {
+                labels: {
+                    "job-name": "appservice-telegram-%s-bootstrap" % [name],
+                },
+            },
+            spec+: {
+                template+: {
+                    spec+: {
+                        volumes_: {
+                            config: kube.SecretVolume(bridge.config),
+                        },
+                        containers_: {
+                            bootstrap: kube.Container("appservice-telegram-%s-bootstrap" % [name]) {
+                                image: cfg.image,
+                                command: [
+                                    "sh", "-c",
+                                    "python3 -m mautrix_telegram -g -c /config/config.yaml -r /tmp/registration.yaml && echo SNIPSNIP && cat /tmp/registration.yaml",
+                                ],
+                                volumeMounts_: {
+                                    config: { mountPath: "/config" },
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+
+        deployment: kube.Deployment("appservice-telegram-%s" % [name]) {
+            metadata+: cfg.metadata,
+            spec+: {
+                replicas: 1,
+                template+: {
+                    spec+: {
+                        volumes_: {
+                            config: kube.SecretVolume(bridge.config),
+                            data: kube.PersistentVolumeClaimVolume(bridge.dataVolume),
+                            registration: { secret: { secretName: "appservice-telegram-%s-registration" % [name] } },
+                        },
+                        initContainers: [
+                            // This container takes the stateless config from the Secret, and
+                            // updates it with the registration secrets from the registration token.
+                            kube.Container("generate-config") {
+                                volumeMounts_: {
+                                    config: { mountPath: "/config", },
+                                    registration: { mountPath: "/registration", },
+                                    data: { mountPath: "/data" },
+                                },
+                                // Ow, the edge! We need yq.
+                                // See: https://github.com/mikefarah/yq/issues/190#issuecomment-667519015
+                                image: "alpine@sha256:156f59dc1cbe233827642e09ed06e259ef6fa1ca9b2e29d52ae14d5e7b79d7f0",
+                                command: [
+                                    "sh", "-c", |||
+                                        set -e -x
+                                        apk add --no-cache yq
+                                        cp /config/config.yaml /data/config.yaml
+                                        yq w -i /data/config.yaml appservice.as_token $(yq r /registration/registration.yaml as_token)
+                                        yq w -i /data/config.yaml appservice.hs_token $(yq r /registration/registration.yaml hs_token)
+                                    |||
+                                ],
+                            },
+                        ],
+                        containers_: {
+                            appserviceIrc: kube.Container("appservice-telegram-%s" % [name]) {
+                                image: cfg.image,
+                                command: [
+                                    "sh", "-c", |||
+                                        alembic -x config=/data/config.yaml upgrade head
+                                        python3 -m mautrix_telegram -n -c /data/config.yaml
+                                    |||
+                                ],
+                                ports_: {
+                                    http: { containerPort: 29317 },
+                                },
+                                volumeMounts_: {
+                                    data: { mountPath: "/data" },
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+
+        svc: kube.Service("appservice-telegram-%s" % [name]) {
+            metadata+: cfg.metadata,
+            target_pod:: bridge.deployment.spec.template,
+        },
+    },
+}