installer scripts
diff --git a/scripts/homelab/installer/cert-manager.sh b/scripts/homelab/installer/cert-manager.sh
new file mode 100644
index 0000000..c3a46cd
--- /dev/null
+++ b/scripts/homelab/installer/cert-manager.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+helm upgrade --create-namespace \
+     --namespace cert-manager \
+     cert-manager jetstack/cert-manager \
+     --version v1.4.0 \
+     --set installCRDs=true \
+     --set resources.requests.cpu="100m" \
+     --set resources.limits.cpu="250m" \
+     --set resources.requests.memory="50M" \
+     --set resources.limits.memory="150M" \
+     --set tolerations[0].key="pcloud" \
+     --set tolerations[0].operator="Equal" \
+     --set tolerations[0].value="role" \
+     --set tolerations[0].effect="NoSchedule" \
+     --set cainjector.resources.requests.cpu="100m" \
+     --set cainjector.cpu="250m" \
+     --set cainjector.resources.requests.memory="50M" \
+     --set cainjector.resources.limits.memory="150M" \
+     --set cainjector.tolerations[0].key="pcloud" \
+     --set cainjector.tolerations[0].operator="Equal" \
+     --set cainjector.tolerations[0].value="role" \
+     --set cainjector.tolerations[0].effect="NoSchedule" \
+     --set webhook.resources.requests.cpu="100m" \
+     --set webhook.resources.limits.cpu="250m" \
+     --set webhook.resources.requests.memory="50M" \
+     --set webhook.resources.limits.memory="150M" \
+     --set webhook.tolerations[0].key="pcloud" \
+     --set webhook.tolerations[0].operator="Equal" \
+     --set webhook.tolerations[0].value="role" \
+     --set webhook.tolerations[0].effect="NoSchedule"
+
+kubectl apply -f cert-manager-webhook-gandi/rbac.yaml
+
+helm upgrade --namespace cert-manager  \
+     cert-manager-webhook-gandi ./cert-manager-webhook-gandi/deploy/cert-manager-webhook-gandi \
+     --set image.repository=giolekva/cert-manager-webhook-gandi \
+     --set image.tag=latest \
+     --set image.pullPolicy=Always \
+     --set logLevel=2 \
+     --set resources.requests.cpu="100m" \
+     --set resources.limits.cpu="250m" \
+     --set resources.requests.memory="50M" \
+     --set resources.limits.memory="150M" \
+     --set tolerations[0].key="pcloud" \
+     --set tolerations[0].operator="Equal" \
+     --set tolerations[0].value="role" \
+     --set tolerations[0].effect="NoSchedule"
+
+kubectl apply -f cluster-issuer.yaml
+kubectl apply -f root-ca-server.yaml
diff --git a/scripts/homelab/installer/cluster-issuer.yaml b/scripts/homelab/installer/cluster-issuer.yaml
new file mode 100644
index 0000000..9ea191b
--- /dev/null
+++ b/scripts/homelab/installer/cluster-issuer.yaml
@@ -0,0 +1,107 @@
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+  name: letsencrypt-prod
+  namespace: cert-manager
+spec:
+  acme:
+    server: https://acme-v02.api.letsencrypt.org/directory
+    email: giolekva@gmail.com
+    privateKeySecretRef:
+      name: cluster-issuer-letsencrypt-prod-account-key
+    solvers:
+    - selector: {}
+      http01:
+        ingress:
+          class: nginx
+---
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+  name: letsencrypt-staging-dns
+  namespace: cert-manager
+spec:
+  acme:
+    # server: https://acme-v02.api.letsencrypt.org/directory
+    server: https://acme-staging-v02.api.letsencrypt.org/directory
+    email: giolekva@gmail.com
+    privateKeySecretRef:
+      name: cluster-issuer-letsencrypt-staginig-dns-account-key
+    solvers:
+    - dns01:
+        webhook:
+          groupName: acme.bwolf.me
+          solverName: gandi
+          config:
+            apiKeySecretRef:
+              key: api-token
+              name: gandi-credentials
+---
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+  name: letsencrypt-prod-dns
+  namespace: cert-manager
+spec:
+  acme:
+    server: https://acme-v02.api.letsencrypt.org/directory
+    email: giolekva@gmail.com
+    privateKeySecretRef:
+      name: cluster-issuer-letsencrypt-prod-dns-account-key
+    solvers:
+    - dns01:
+        webhook:
+          groupName: acme.bwolf.me
+          solverName: gandi
+          config:
+            apiKeySecretRef:
+              key: api-token
+              name: gandi-credentials
+---
+# TODO(giolekva): move to ingerss-nginx-private namespace
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+  name: selfsigned
+  namespace: cert-manager
+spec:
+  selfSigned: {}
+---
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+  name: selfsigned-ca-root
+  namespace: cert-manager
+spec:
+  isCA: true
+  commonName: selfsigned-ca-root
+  secretName: selfsigned-ca-root
+  privateKey:
+    algorithm: ECDSA
+    size: 256
+  issuerRef:
+    name: selfsigned
+    kind: ClusterIssuer
+    group: cert-manager.io
+---
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+  name: selfsigned-ca
+  namespace: cert-manager
+spec:
+  ca:
+    secretName: selfsigned-ca-root
+---
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+  name: wildcard-lekva.me
+  namespace: ingress-nginx
+spec:
+  dnsNames:
+  - '*.lekva.me'
+  issuerRef:
+    name: letsencrypt-prod-dns
+    kind: ClusterIssuer
+  secretName: cert-wildcard.lekva.me
diff --git a/scripts/homelab/installer/ingress-nginx.sh b/scripts/homelab/installer/ingress-nginx.sh
new file mode 100644
index 0000000..65a45aa
--- /dev/null
+++ b/scripts/homelab/installer/ingress-nginx.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+helm upgrade --create-namespace \
+     --namespace ingress-nginx \
+     nginx ingress-nginx/ingress-nginx \
+     --version 4.0.3 \
+     --set fullNameOverride=nginx \
+     --set controller.service.type=LoadBalancer \
+     --set controller.ingressClassByName=true \
+     --set controller.ingressClassResource.name=nginx \
+     --set controller.ingressClassResource.enabled=true \
+     --set controller.ingressClassResource.default=true \
+     --set controller.ingressClassResource.controllerValue="k8s.io/ingress-nginx" \
+     --set controller.extraArgs.default-ssl-certificate=ingress-nginx/cert-wildcard.lekva.me \
+     --set controller.config.proxy-body-size="100M" \
+     --set tcp.25="app-maddy/maddy:25" \
+     --set tcp.143="app-maddy/maddy:143" \
+     --set tcp.993="app-maddy/maddy:993" \
+     --set tcp.587="app-maddy/maddy:587" \
+     --set tcp.465="app-maddy/maddy:465"
+# #    --set udp.4242="ingress-nginx-private/lighthouse:4242"
+
+# kubectl create configmap \
+# 	-n ingress-nginx-private \
+# 	lighthouse-cert \
+# 	--from-file ../../apps/nebula/lighthouse-cert/
+# kubectl create configmap \
+# 	-n ingress-nginx-private \
+# 	ca-cert \
+# 	--from-file ../../apps/nebula/ca-cert/ca.crt
+# kubectl create configmap \
+# 	-n ingress-nginx-private \
+# 	lighthouse-config \
+# 	--from-file ../../apps/nebula/lighthouse.yaml
+
+helm upgrade --create-namespace \
+     --namespace ingress-nginx-private \
+     nginx ingress-nginx/ingress-nginx \
+     --version 4.0.3 \
+     --set fullnameOverride=nginx-private \
+     --set controller.service.type=ClusterIP \
+     --set controller.ingressClassByName=true \
+     --set controller.ingressClassResource.name=nginx-private \
+     --set controller.ingressClassResource.enabled=true \
+     --set controller.ingressClassResource.default=false \
+     --set controller.ingressClassResource.controllerValue="k8s.io/ingress-nginx-private" \
+     --set controller.extraVolumes[0].name="lighthouse-cert" \
+     --set controller.extraVolumes[0].configMap.name="lighthouse-cert" \
+     --set controller.extraVolumes[1].name=ca-cert \
+     --set controller.extraVolumes[1].configMap.name=ca-cert \
+     --set controller.extraVolumes[2].name=config \
+     --set controller.extraVolumes[2].configMap.name=lighthouse-config \
+     --set controller.extraContainers[0].name=nebula \
+     --set controller.extraContainers[0].image=giolekva/nebula:latest \
+     --set controller.extraContainers[0].imagePullPolicy=IfNotPresent \
+     --set controller.extraContainers[0].securityContext.capabilities.add[0]=NET_ADMIN \
+     --set controller.extraContainers[0].securityContext.privileged=true \
+     --set controller.extraContainers[0].ports[0].name=nebula \
+     --set controller.extraContainers[0].ports[0].containerPort=4242 \
+     --set controller.extraContainers[0].ports[0].protocol=UDP \
+     --set controller.extraContainers[0].command[0]="nebula" \
+     --set controller.extraContainers[0].command[1]="--config=/etc/nebula/config/lighthouse.yaml" \
+     --set controller.extraContainers[0].volumeMounts[0].name=lighthouse-cert \
+     --set controller.extraContainers[0].volumeMounts[0].mountPath=/etc/nebula/lighthouse \
+     --set controller.extraContainers[0].volumeMounts[1].name=ca-cert \
+     --set controller.extraContainers[0].volumeMounts[1].mountPath=/etc/nebula/ca \
+     --set controller.extraContainers[0].volumeMounts[2].name=config \
+     --set controller.extraContainers[0].volumeMounts[2].mountPath=/etc/nebula/config \
+     --set controller.config.bind-address="111.0.0.1" \
+     --set controller.config.proxy-body-size="0" \
+     --set udp.53="pihole/pihole-dns-udp:53" \
+     --set tcp.53="pihole/pihole-dns-tcp:53"
diff --git a/scripts/homelab/installer/longhorn.sh b/scripts/homelab/installer/longhorn.sh
new file mode 100644
index 0000000..a671ebc
--- /dev/null
+++ b/scripts/homelab/installer/longhorn.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+helm install --create-namespace \
+     --namespace longhorn-system \
+     longhorn longhorn/longhorn \
+     --set defaultSettings.defaultDataPath=/pcloud-storage/longhorn \
+     --set persistence.defaultClassReplicaCount=2 \
+     --set ingress.enabled=true \
+     --set ingress.ingressClassName=nginx-private \
+     --set ingress.tls=true \
+     --set ingress.host=longhorn.pcloud \
+     --set ingress.annotations."cert-manager\.io/cluster-issuer"="selfsigned-ca" \
+     --set ingress.annotations."acme\.cert-manager\.io/http01-edit-in-place"="\"true\""
diff --git a/scripts/homelab/installer/matrix.sh b/scripts/homelab/installer/matrix.sh
new file mode 100644
index 0000000..31680fc
--- /dev/null
+++ b/scripts/homelab/installer/matrix.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+kubectl apply -f ../../apps/matrix/install.yaml
+export KUBE_EDITOR="emacs -nw" && k edit configmap config -n app-matrix
+helm install --create-namespace postgresql bitnami/postgresql \
+     --namespace app-matrix \
+     --set image.repository=arm64v8/postgres \
+     --set image.tag=13.4 \
+     --set image.pullPolicy=IfNotPresent \
+     --set persistence.size=100Gi \
+     --set securityContext.enabled=true \
+     --set securityContext.fsGroup=0 \
+     --set containerSecurityContext.enabled=true \
+     --set containerSecurityContext.runAsUser=0 \
+     --set volumePermissions.securityContext.runAsUser=0 \
+     --set service.type=ClusterIP \
+     --set service.port=5432 \
+     --set postgresqlUsername=postgres \
+     --set postgresqlPassword=foo \
+     --set initdbScripts."createuser\.sh"="echo foo | createuser --pwprompt synapse_user" \
+     --set initdbScripts."createdb\.sh"="createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapse"
+
+kubectl apply -f www.yaml
diff --git a/scripts/homelab/installer/metallb.sh b/scripts/homelab/installer/metallb.sh
new file mode 100644
index 0000000..abbf6ce
--- /dev/null
+++ b/scripts/homelab/installer/metallb.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml
+kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml
+# On first install only
+kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
+kubectl apply -f metallb-config.yaml
diff --git a/scripts/homelab/installer/pihole.sh b/scripts/homelab/installer/pihole.sh
new file mode 100644
index 0000000..f7e26ba
--- /dev/null
+++ b/scripts/homelab/installer/pihole.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+helm upgrade --create-namespace \
+     --namespace pihole \
+     pihole mojo2600/pihole \
+     --set image.repository="pihole/pihole" \
+     --set image.tag=v5.8.1 \
+     --set persistentVolumeClaim.enabled=true \
+     --set persistentVolumeClaim.size="5Gi" \
+     --set ingress.enabled=true \
+     --set ingress.hosts={"pihole.pcloud"} \
+     --set ingress.tls[0].hosts[0]="pihole.pcloud" \
+     --set ingress.tls[0].secretName="cert-pihole.pcloud" \
+     --set ingress.annotations."kubernetes\.io/ingress\.class"="nginx-private" \
+     --set ingress.annotations."cert-manager\.io/cluster-issuer"="selfsigned-ca" \
+     --set ingress.annotations."acme\.cert-manager\.io/http01-edit-in-place"="\"true\"" \
+     --set serviceDhcp.enabled=false \
+     --set serviceDns.type=ClusterIP \
+     --set serviceWeb.type=ClusterIP \
+     --set serviceWeb.https.enabled=false \
+     --set virtualHost="pihole.pcloud" \
+     --set resources.requests.cpu="250m" \
+     --set resources.limits.cpu="500m" \
+     --set resources.requests.memory="100M" \
+     --set resources.limits.memory="250M"
diff --git a/scripts/homelab/installer/root-ca-server.yaml b/scripts/homelab/installer/root-ca-server.yaml
new file mode 100644
index 0000000..caa2cf1
--- /dev/null
+++ b/scripts/homelab/installer/root-ca-server.yaml
@@ -0,0 +1,85 @@
+# TODO(giolekva): move to ingerss-nginx-private namespace
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: selfsigned-root-ca
+  namespace: cert-manager
+spec:
+  selector:
+    matchLabels:
+      app: selfsigned-root-ca
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: selfsigned-root-ca
+    spec:
+      volumes:
+      - name: root-ca-secret
+        secret:
+          secretName: selfsigned-ca-root
+          items:
+          - key: ca.crt
+            path: selfsigned-root-ca.crt
+      containers:
+      - name: file-server
+        image: giolekva/static-file-server:latest
+        imagePullPolicy: Always
+        ports:
+        - name: http
+          containerPort: 80
+        command: ["static-file-server"]
+        args: ["-port=80", "-dir=/etc/static-file-server/data"]
+        volumeMounts:
+        - name: root-ca-secret
+          mountPath: /etc/static-file-server/data/
+          readOnly: true
+        resources:
+          requests:
+            memory: "10Mi"
+            cpu: "10m"
+          limits:
+            memory: "20Mi"
+            cpu: "100m"
+      tolerations:
+      - key: "pcloud"
+        operator: "Equal"
+        value: "role"
+        effect: "NoSchedule"
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: selfsigned-root-ca
+  namespace: cert-manager
+spec:
+  type: ClusterIP
+  selector:
+    app: selfsigned-root-ca
+  ports:
+    - name: http
+      port: 80
+      targetPort: http
+      protocol: TCP
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: selfsigned-root-ca
+  namespace: cert-manager
+  annotations:
+    nginx.ingress.kubernetes.io/ssl-redirect: "false"
+spec:
+  ingressClassName: nginx-private
+  rules:
+  - host: root-ca.pcloud
+    http:
+      paths:
+      - pathType: Prefix
+        path: "/"
+        backend:
+          service:
+            name: selfsigned-root-ca
+            port:
+              name: http