Installer: matrix with hydra maester to auto-register oauth2 client
diff --git a/apps/matrix/capture-config/Makefile b/apps/matrix/capture-config/Makefile
index 2cdc2d3..6e3ddec 100644
--- a/apps/matrix/capture-config/Makefile
+++ b/apps/matrix/capture-config/Makefile
@@ -5,7 +5,7 @@
 	go build -o capture-config *.go
 
 image: build
-	docker build --tag=giolekva/capture-config:latest .
+	docker build --tag=giolekva/capture-config:latest . --platform=linux/arm64
 
 push: image
 	docker push giolekva/capture-config:latest
diff --git a/apps/matrix/capture-config/go.mod b/apps/matrix/capture-config/go.mod
index c23ffeb..a7ae433 100644
--- a/apps/matrix/capture-config/go.mod
+++ b/apps/matrix/capture-config/go.mod
@@ -3,6 +3,7 @@
 go 1.16
 
 require (
+	github.com/miracl/conflate v1.2.1
 	k8s.io/api v0.22.2
 	k8s.io/apimachinery v0.22.2
 	k8s.io/client-go v0.22.2
diff --git a/apps/matrix/capture-config/go.sum b/apps/matrix/capture-config/go.sum
index 037ee0b..ed374ec 100644
--- a/apps/matrix/capture-config/go.sum
+++ b/apps/matrix/capture-config/go.sum
@@ -28,6 +28,7 @@
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -53,6 +54,8 @@
 github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -137,6 +140,8 @@
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/miracl/conflate v1.2.1 h1:QlB+Hjh8vnPIjimCK2VKEvtLVxVGIVxNQ4K95JRpi90=
+github.com/miracl/conflate v1.2.1/go.mod h1:F85f+vrE7SwfRoL31EpLZFa1sub0SDxzcwxDBxFvy7k=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -174,6 +179,13 @@
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -230,6 +242,7 @@
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191125084936-ffdde1057850/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -405,6 +418,7 @@
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
diff --git a/apps/matrix/capture-config/main.go b/apps/matrix/capture-config/main.go
index b031fbb..08d7be2 100644
--- a/apps/matrix/capture-config/main.go
+++ b/apps/matrix/capture-config/main.go
@@ -10,9 +10,13 @@
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
+
+	"github.com/miracl/conflate"
 )
 
 var configFile = flag.String("config", "", "Path to the homeserver.yaml config file.")
+var configToMerge = flag.String("config-to-merge", "", "Name of the configmap to merge with generated one.")
+var toMergeFilename = flag.String("to-merge-filename", "", "Name of the file from config to merge.")
 var namespace = flag.String("namespace", "", "Namespace name.")
 var configMapName = flag.String("config-map-name", "", "Name of the ConfigMap to create.")
 
@@ -28,11 +32,7 @@
 	return cs
 }
 
-func createConfigFromFile() *v1.ConfigMap {
-	f, err := ioutil.ReadFile(*configFile)
-	if err != nil {
-		panic(err)
-	}
+func createConfig(data []byte) *v1.ConfigMap {
 	return &v1.ConfigMap{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "ConfigMap",
@@ -42,17 +42,35 @@
 			Name: *configMapName,
 		},
 		Data: map[string]string{
-			path.Base(*configFile): string(f),
+			path.Base(*configFile): string(data),
 		},
 	}
 }
 
 func main() {
 	flag.Parse()
-	config := createConfigFromFile()
 	client := createClient().CoreV1().ConfigMaps(*namespace)
-	_, err := client.Create(context.TODO(), config, metav1.CreateOptions{})
+	conf := conflate.New()
+	generated, err := ioutil.ReadFile(*configFile)
 	if err != nil {
 		panic(err)
 	}
+	if err := conf.AddData(generated); err != nil {
+		panic(err)
+	}
+	toMerge, err := client.Get(context.TODO(), *configToMerge, metav1.GetOptions{})
+	if err != nil {
+		panic(err)
+	}
+	if err := conf.AddData([]byte(toMerge.Data[*toMergeFilename])); err != nil {
+		panic(err)
+	}
+	merged, err := conf.MarshalYAML()
+	if err != nil {
+		panic(err)
+	}
+	config := createConfig(merged)
+	if _, err := client.Create(context.TODO(), config, metav1.CreateOptions{}); err != nil {
+		panic(err)
+	}
 }
diff --git a/charts/auth/templates/ui.yaml b/charts/auth/templates/ui.yaml
index 50296f7..8c07d74 100644
--- a/charts/auth/templates/ui.yaml
+++ b/charts/auth/templates/ui.yaml
@@ -18,15 +18,16 @@
 metadata:
   name: ui
   namespace: {{ .Release.Namespace }}
-  annotations:
-    cert-manager.io/cluster-issuer: {{ .Values.ui.certificateIssuer }}
-    acme.cert-manager.io/http01-edit-in-place: "true"
+  # annotations:
+  #   cert-manager.io/cluster-issuer: {{ .Values.ui.certificateIssuer }}
+  #   acme.cert-manager.io/http01-edit-in-place: "true"
 spec:
   ingressClassName: {{ .Values.ui.ingressClassName }}
   tls:
   - hosts:
     - accounts-ui.{{ .Values.ui.domain }}
-    secretName: cert-accounts-ui.{{ .Values.ui.domain }}
+    # secretName: cert-accounts-ui.{{ .Values.ui.domain }}
+    secretName: cert-wildcard.{{ .Values.ui.domain }}
   rules:
   - host: accounts-ui.{{ .Values.ui.domain }}
     http:
@@ -80,7 +81,7 @@
         - server
         - --port=8080
         - --kratos=https://accounts.{{ .Values.ui.domain }}
-        - --hydra=hydra{{ .Values.ui.internalDomain }}
+        - --hydra=hydra.{{ .Values.ui.internalDomain }}
         - --email-domain={{ .Values.ui.domain }}
         # resources:
         #   requests:
diff --git a/charts/matrix/.helmignore b/charts/matrix/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/charts/matrix/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/charts/matrix/Chart.yaml b/charts/matrix/Chart.yaml
new file mode 100644
index 0000000..40204e5
--- /dev/null
+++ b/charts/matrix/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: matrix
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/charts/matrix/templates/_helpers.tpl b/charts/matrix/templates/_helpers.tpl
new file mode 100644
index 0000000..063b2b4
--- /dev/null
+++ b/charts/matrix/templates/_helpers.tpl
@@ -0,0 +1,7 @@
+{{- define "clientSecret" -}}
+{{- if .Values.oauth2.clientSecret -}}
+{{- .Values.oauth2.clientSecret -}}
+{{- else -}}
+{{- randAlphaNum 32 -}}
+{{- end -}}
+{{- end -}}
diff --git a/charts/matrix/templates/config-to-merge.yaml b/charts/matrix/templates/config-to-merge.yaml
new file mode 100644
index 0000000..ba18144
--- /dev/null
+++ b/charts/matrix/templates/config-to-merge.yaml
@@ -0,0 +1,45 @@
+{{- $secret := include "clientSecret" . -}}
+---
+apiVersion: v1
+kind: Secret
+type: Opaque
+metadata:
+  name: {{ .Values.oauth2.secretName }}
+  namespace: {{ .Release.Namespace }}
+data:
+  client_id: {{ .Values.oauth2.clientId | b64enc  }}
+  client_secret: {{ $secret | b64enc }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Values.configMerge.configName }}
+  namespace: {{ .Release.Namespace }}
+data:
+  {{ .Values.configMerge.fileName }}: |
+    public_baseurl: https://matrix.{{ .Values.domain }}/
+    enable_registration: false
+    database:
+     name: psycopg2
+     txn_limit: 10000
+     args:
+       host: {{ .Values.postgresql.host }}
+       port: {{ .Values.postgresql.port }}
+       database: {{ .Values.postgresql.database }}
+       user: {{ .Values.postgresql.user }}
+       password: {{ .Values.postgresql.password }}
+       cp_min: 5
+       cp_max: 10
+    oidc_providers:
+      - idp_id: pcloud
+        idp_name: "PCloud"
+        skip_verification: true
+        issuer: {{ .Values.oauth2.hydraPublic }}
+        client_id: {{ .Values.oauth2.clientId }}
+        client_secret: {{ $secret }}
+        scopes: ["openid", "profile"]
+        allow_existing_users: true
+        user_mapping_provider:
+          config:
+            localpart_template: {{`"{{ user.username }}"`}}
+            display_name_template: "{{`{{ user.username }}"`}}
diff --git a/charts/matrix/templates/matrix.yaml b/charts/matrix/templates/matrix.yaml
new file mode 100644
index 0000000..6770c91
--- /dev/null
+++ b/charts/matrix/templates/matrix.yaml
@@ -0,0 +1,187 @@
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: CreateConfigMaps
+  namespace: {{ .Release.Namespace }}
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  verbs:
+  - get
+  - create
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: default-CreateConfigMaps
+  namespace: {{ .Release.Namespace }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: CreateConfigMaps
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: {{ .Release.Namespace }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: matrix
+  namespace: {{ .Release.Namespace }}
+spec:
+  type: ClusterIP
+  selector:
+    app: matrix
+  ports:
+  - name: http
+    port: 80
+    targetPort: http
+    protocol: TCP
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: ingress
+  namespace: {{ .Release.Namespace }}
+  annotations:
+    cert-manager.io/cluster-issuer: {{ .Values.certificateIssuer }}
+    acme.cert-manager.io/http01-edit-in-place: "true"
+spec:
+  ingressClassName: {{ .Values.ingressClassName }}
+  tls:
+  - hosts:
+    - matrix.{{ .Values.domain }}
+    # secretName: cert-matrix.{{ .Values.domain }}
+    secretName: cert-wildcard.{{ .Values.domain }}
+  rules:
+  - host: matrix.{{ .Values.domain }}
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: matrix
+            port:
+              name: http
+---
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: generate-config
+  namespace: {{ .Release.Namespace }}
+spec:
+  template:
+    metadata:
+      labels:
+        app: generate-config
+    spec:
+      restartPolicy: OnFailure
+      volumes:
+      - name: data
+        persistentVolumeClaim:
+          claimName: data
+      initContainers:
+      - name: matrix
+        image: matrixdotorg/synapse:v1.43.0
+        imagePullPolicy: IfNotPresent
+        ports:
+        - name: http
+          containerPort: 8008
+          protocol: TCP
+        env:
+        - name: SYNAPSE_SERVER_NAME
+          value: "{{ .Values.domain }}"
+        - name: SYNAPSE_REPORT_STATS
+          value: "no"
+        - name: SYNAPSE_CONFIG_DIR
+          value: "/data"
+        - name: SYNAPSE_CONFIG_PATH
+          value: "/data/homeserver.yaml"
+        - name: SYNAPSE_DATA_DIR
+          value: "/data"
+        command:
+        - /start.py
+        - generate
+        volumeMounts:
+        - name: data
+          mountPath: /data
+      containers:
+      - name: capture-config
+        image: giolekva/capture-config:latest
+        imagePullPolicy: Always
+        command:
+        - capture-config
+        - --config=/data/homeserver.yaml
+        - --namespace={{ .Release.Namespace }}
+        - --config-map-name=config
+        - --config-to-merge={{ .Values.configMerge.configName }}
+        - --to-merge-filename={{ .Values.configMerge.fileName }}
+        volumeMounts:
+        - name: data
+          mountPath: /data
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: matrix
+  namespace: {{ .Release.Namespace }}
+spec:
+  selector:
+    matchLabels:
+      app: matrix
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: matrix
+    spec:
+      volumes:
+      - name: data
+        persistentVolumeClaim:
+          claimName: data
+      - name: homeserver-config
+        configMap:
+          name: config
+      containers:
+      - name: matrix
+        image: matrixdotorg/synapse:v1.43.0
+        imagePullPolicy: IfNotPresent
+        ports:
+        - name: http
+          containerPort: 8008
+          protocol: TCP
+        env:
+        - name: SYNAPSE_SERVER_NAME
+          value: "{{ .Values.domain }}"
+        - name: SYNAPSE_REPORT_STATS
+          value: "no"
+        - name: SYNAPSE_CONFIG_DIR
+          value: "/data"
+        - name: SYNAPSE_CONFIG_PATH
+          value: "/homeserver-config/homeserver.yaml"
+        - name: SYNAPSE_DATA_DIR
+          value: "/data"
+        command: ["/start.py"]
+        volumeMounts:
+        - name: data
+          mountPath: /data
+        - name: homeserver-config
+          mountPath: /homeserver-config
+          readOnly: true
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: data
+  namespace: {{ .Release.Namespace }}
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 10Gi
diff --git a/charts/matrix/templates/oauth2-client.yaml b/charts/matrix/templates/oauth2-client.yaml
new file mode 100644
index 0000000..2a23c4f
--- /dev/null
+++ b/charts/matrix/templates/oauth2-client.yaml
@@ -0,0 +1,19 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: matrix
+  namespace: {{ .Release.Namespace }}
+spec:
+  grantTypes:
+  - authorization_code
+  responseTypes:
+  - code
+  scope: "openid profile"
+  secretName: {{ .Values.oauth2.secretName }}
+  redirectUris:
+  - https://matrix.{{ .Values.domain }}/_synapse/client/oidc/callback
+  hydraAdmin:
+    url: {{ .Values.oauth2.hydraAdmin }}
+    port: 80
+    endpoint: /clients
+    forwardedProto: https
diff --git a/charts/matrix/templates/well-known.yaml b/charts/matrix/templates/well-known.yaml
new file mode 100644
index 0000000..b8a921a
--- /dev/null
+++ b/charts/matrix/templates/well-known.yaml
@@ -0,0 +1,129 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: well-known
+  namespace: {{ .Release.Namespace }}
+spec:
+  type: ClusterIP
+  selector:
+    app: well-known
+  ports:
+  - name: http
+    port: 80
+    targetPort: http
+    protocol: TCP
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: well-known
+  namespace: {{ .Release.Namespace }}
+  annotations:
+    cert-manager.io/cluster-issuer: "{{ .Values.certificateIssuer }}"
+    acme.cert-manager.io/http01-edit-in-place: "true"
+spec:
+  ingressClassName: {{ .Values.ingressClassName }}
+  tls:
+  - hosts:
+    - {{ .Values.domain }}
+    secretName: cert-{{ .Values.domain }}
+  - hosts:
+    - www.{{ .Values.domain }}
+    secretName: cert-www.{{ .Values.domain }}
+  rules:
+  - host: {{ .Values.domain }}
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: well-known
+            port:
+              name: http
+  - host: www.{{ .Values.domain }}
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: well-known
+            port:
+              name: http
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: well-known
+  namespace: {{ .Release.Namespace }}
+data:
+  nginx.conf: |
+    # user       www www;
+    worker_processes  1;
+    error_log   /dev/null   crit;
+    # pid        logs/nginx.pid;
+    worker_rlimit_nofile 8192;
+    events {
+        worker_connections  1024;
+    }
+    http {
+        server {
+            listen 8080;
+            location /.well-known/matrix/client {
+                return 200 '{"m.homeserver": {"base_url": "https://matrix.{{ .Values.domain }}:443"}}';
+                default_type application/json;
+                add_header Access-Control-Allow-Origin *;
+            }
+            location /.well-known/matrix/server {
+                return 200 '{"m.server": "matrix.{{ .Values.domain }}:443"}';
+                default_type application/json;
+                add_header Access-Control-Allow-Origin *;
+            }
+        }
+    }
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: well-known
+  namespace: {{ .Release.Namespace }}
+spec:
+  selector:
+    matchLabels:
+      app: well-known
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: well-known
+    spec:
+      volumes:
+      - name: config
+        configMap:
+          name: well-known
+      containers:
+      - name: nginx
+        image: nginx:1.21.3-alpine
+        imagePullPolicy: IfNotPresent
+        ports:
+        - name: http
+          containerPort: 8080
+          protocol: TCP
+        volumeMounts:
+        - name: config
+          mountPath: /etc/nginx
+          readOnly: true
+        resources:
+          requests:
+            memory: "10Mi"
+            cpu: "10m"
+          limits:
+            memory: "20Mi"
+            cpu: "100m"
+      tolerations:
+      - key: "pcloud"
+        operator: "Equal"
+        value: "role"
+        effect: "NoSchedule"
diff --git a/charts/matrix/values.yaml b/charts/matrix/values.yaml
new file mode 100644
index 0000000..7add503
--- /dev/null
+++ b/charts/matrix/values.yaml
@@ -0,0 +1,18 @@
+domain: example.com
+oauth2:
+  hydraAdmin: http://hydra-admin
+  hydraPublic: https://hydra.example.com
+  clientId: matrix
+  clientSecret: ""
+  secretName: oauth2-client
+postgresql:
+  host: postgresql
+  port: 5432
+  database: synapse
+  user: synapse_user
+  password: password
+certificateIssuer: public
+ingressClassName: nginx
+configMerge:
+  configName: config-to-merge
+  fileName: to-merge.yaml
diff --git a/core/auth/hydra-maester/.gitignore b/core/auth/hydra-maester/.gitignore
new file mode 100644
index 0000000..901cf92
--- /dev/null
+++ b/core/auth/hydra-maester/.gitignore
@@ -0,0 +1 @@
+hydra-maester
diff --git a/core/auth/hydra-maester/Dockerfile b/core/auth/hydra-maester/Dockerfile
new file mode 100644
index 0000000..072d9b2
--- /dev/null
+++ b/core/auth/hydra-maester/Dockerfile
@@ -0,0 +1,4 @@
+FROM gcr.io/distroless/static:latest
+COPY hydra-maester/manager .
+USER 1000
+ENTRYPOINT ["/manager"]
diff --git a/core/auth/hydra-maester/Makefile b/core/auth/hydra-maester/Makefile
new file mode 100644
index 0000000..970da58
--- /dev/null
+++ b/core/auth/hydra-maester/Makefile
@@ -0,0 +1,15 @@
+clone:
+	git clone --depth 1 --branch v0.0.20 https://github.com/ory/hydra-maester.git
+
+push_arm64: export GOOS=linux
+push_arm64: export GOARCH=arm64
+push_arm64: export CGO_ENABLED=0
+push_arm64: export GO111MODULE=on
+build:
+	cd hydra-maester && go build -o manager main.go
+
+image_arm64: build
+	docker build --tag=giolekva/ory-hydra-maester:latest . --platform=linux/arm64
+
+push_arm64: image_arm64
+	docker push giolekva/ory-hydra-maester:latest
diff --git a/helmfile/users/helmfile.yaml b/helmfile/users/helmfile.yaml
index 8d4ecf1..8d94879 100644
--- a/helmfile/users/helmfile.yaml
+++ b/helmfile/users/helmfile.yaml
@@ -158,12 +158,13 @@
             - path: /
               pathType: Prefix
           annotations:
-            cert-manager.io/cluster-issuer: "{{ .Values.id }}-public-staging"
+            cert-manager.io/cluster-issuer: "{{ .Values.id }}-public"
             acme.cert-manager.io/http01-edit-in-place: "true"
           tls:
           - hosts:
             - accounts.{{ .Values.domain }}
-            secretName: cert-accounts.{{ .Values.domain }}
+            # secretName: cert-accounts.{{ .Values.domain }}
+            secretName: cert-wildcard.{{ .Values.domain }}
       secret:
         enabled: true
       kratos:
@@ -317,16 +318,26 @@
             - path: /
               pathType: Prefix
           annotations:
-            cert-manager.io/cluster-issuer: "{{ .Values.id }}-public-staging"
+            cert-manager.io/cluster-issuer: "{{ .Values.id }}-public"
             acme.cert-manager.io/http01-edit-in-place: "true"
           tls:
           - hosts:
             - hydra.{{ .Values.domain }}
-            secretName: cert-hydra.{{ .Values.domain }}
+            # secretName: cert-hydra.{{ .Values.domain }}
+            secretName: cert-wildcard.{{ .Values.domain }}
       secret:
         enabled: true
       maester:
-        enabled: false
+        enabled: true
+        hydraFullnameOverride: hydra
+      hydra-maester:
+        image:
+          repository: giolekva/ory-hydra-maester
+          tag: latest
+          pullPolicy: IfNotPresent
+        adminService:
+          name: hydra
+          port: 80
       hydra:
         autoMigrate: true
         config:
@@ -381,7 +392,7 @@
             level: trace
             leak_sensitive_values: false
   - ui:
-      certificateIssuer: {{ .Values.id }}-public-staging
+      certificateIssuer: {{ .Values.id }}-public
       ingressClassName: nginx
       domain: {{ .Values.domain }}
       internalDomain: {{ .Values.id }}
@@ -412,6 +423,58 @@
   - domain: bitwarden.{{ .Values.id }}
   - certificateIssuer: {{ .Values.id }}-private
   - ingressClassName: {{ .Values.id }}-ingress-private
+- name: matrix-storage  # TODO(giolekva): merge with core-auth
+  chart: bitnami/postgresql
+  version: 10.13.5
+  namespace: {{ .Values.id }}-app-matrix
+  createNamespace: true
+  values:
+  - fullnameOverride: postgres
+  - image:
+      repository: arm64v8/postgres
+      tag: 13.4
+  - service:
+      type: ClusterIP
+      port: 5432
+  - postgresqlPassword: psswd
+  - initdbScripts:
+      createdb.sh: |
+        #!/bin/sh
+        createdb -U postgres --encoding=UTF8 --locale=C --template=template0 --owner=postgres matrix
+  - persistence:
+      size: 1Gi
+  - securityContext:
+      enabled: true
+      fsGroup: 0
+  - containerSecurityContext:
+      enabled: true
+      runAsUser: 0
+  - volumePermissions:
+      securityContext:
+        runAsUser: 0
+- name: matrix
+  chart: ../../charts/matrix
+  namespace: {{ .Values.id }}-app-matrix
+  createNamespace: true
+  values:
+  - domain: {{ .Values.domain }}
+  - oauth2:
+      hydraAdmin: http://hydra-admin
+      hydraPublic: https://hydra.{{ .Values.domain }}
+      clientId: matrix
+      clientSecret: ""
+      secretName: oauth2-client
+  - postgresql:
+      host: postgres
+      port: 5432
+      database: matrix
+      user: postgres
+      password: psswd
+  - certificateIssuer: {{ .Values.id }}-public
+  - ingressClassName: nginx
+  - configMerge:
+      configName: config-to-merge
+      fileName: to-merge.yaml
 
 environments:
   shveli: