Merge "app/onlyoffice: init"
diff --git a/app/onlyoffice/README.md b/app/onlyoffice/README.md
new file mode 100644
index 0000000..3a48441
--- /dev/null
+++ b/app/onlyoffice/README.md
@@ -0,0 +1,10 @@
+ONLYOFFICE Document Server
+==========================
+
+Production running at office.hackerspace.pl.
+
+JWT secret kept in Kubernetes secrets. Can work with any nextcloud instance as long as the JWT secret is configured correctly.
+
+Has a volume for some persistent data - but this is mostly for caching. As far as I (q3k) undestand, these can be nuked with no repercussions, maybe apart from losing in flight edits.
+
+See [prod.jsonnet](prod.jsonnet) for more information.
diff --git a/app/onlyoffice/prod.jsonnet b/app/onlyoffice/prod.jsonnet
new file mode 100644
index 0000000..927d294
--- /dev/null
+++ b/app/onlyoffice/prod.jsonnet
@@ -0,0 +1,113 @@
+// ONLYOFFICE document server.
+// JWT secret needs to be generated as follows per environment:
+//     kubectl -n onlyoffice-prod create secret generic documentserver-jwt --from-literal=jwt=$(pwgen 32 1)
+
+local kube = import "../../kube/kube.libsonnet";
+local policies = import "../../kube/policies.libsonnet";
+
+{
+    onlyoffice:: {
+        local oo = self,
+        local cfg = oo.cfg,
+        cfg:: {
+            namespace: error "cfg.namespace must be set",
+            image: "onlyoffice/documentserver:5.6.4.20",
+            storageClassName: "waw-hdd-redundant-3",
+            domain: error "cfg.domain must be set",
+        },
+
+        ns: kube.Namespace(cfg.namespace),
+
+        pvc: oo.ns.Contain(kube.PersistentVolumeClaim("documentserver")) {
+            spec+: {
+                storageClassName: cfg.storageClassName,
+                accessModes: [ "ReadWriteOnce" ],
+                resources: {
+                    requests: {
+                        storage: "10Gi",
+                    },
+                },
+            },
+        },
+
+        deploy: oo.ns.Contain(kube.Deployment("documentserver")) {
+            spec+: {
+                template+: {
+                    spec+: {
+                        containers_: {
+                            documentserver: kube.Container("default") {
+                                image: cfg.image,
+                                resources: {
+                                    requests: { memory: "4G", cpu: "100m" },
+                                    limits: { memory: "8G", cpu: "2" },
+                                },
+                                env_: {
+                                    JWT_ENABLED: "true",
+                                    JWT_SECRET: { secretKeyRef: { name: "documentserver-jwt", key: "jwt", }},
+                                },
+                                ports_: {
+                                    http: { containerPort: 80 },
+                                },
+                                local make(sp, p) = { name: "data", mountPath: p, subPath: sp },
+                                volumeMounts: [
+                                    // Per upstream Dockerfile:
+                                    // VOLUME /var/log/$COMPANY_NAME /var/lib/$COMPANY_NAME 
+                                    //        /var/www/$COMPANY_NAME/Data /var/lib/postgresql
+                                    //        /var/lib/rabbitmq /var/lib/redis
+                                    //        /usr/share/fonts/truetype/custom
+                                    make("log", "/var/log/onlyoffice"),
+                                    make("www-data", "/var/www/onlyoffice/Data"),
+                                    make("postgres", "/var/lib/postgresql"),
+                                    make("rabbit", "/var/lib/rabbitmq"),
+                                    make("redis", "/var/lib/redis"),
+                                    make("fonts", "/usr/share/fonts/truetype/custom"),
+                                ],
+                            },
+                        },
+                        volumes_: {
+                            data: kube.PersistentVolumeClaimVolume(oo.pvc),
+                        },
+                    },
+                },
+            },
+        },
+
+        svc: oo.ns.Contain(kube.Service("documentserver")) {
+            target_pod:: oo.deploy.spec.template,
+        },
+        
+        ingress: oo.ns.Contain(kube.Ingress("office")) {
+            metadata+: {
+                annotations+: {
+                    "kubernetes.io/tls-acme": "true",
+                    "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+                },
+            },
+            spec+: {
+                tls: [{ hosts: [cfg.domain], secretName: "office-tls" }],
+                rules: [
+                    {
+                        host: cfg.domain,
+                        http: {
+                            paths: [
+                                { path: "/", backend: oo.svc.name_port, },
+                            ],
+                        },
+                    },
+                ],
+            },
+        },
+
+        // Needed because the documentserver runs its own supervisor, and:
+        //  - rabbitmq wants to mkdir in /run, which starts out with the wrong permissions
+        //  - nginx wants to bind to port 80
+        insecure: policies.AllowNamespaceInsecure(cfg.namespace),
+    },
+
+    prod: self.onlyoffice {
+        cfg+: {
+            namespace: "onlyoffice-prod",
+            domain: "office.hackerspace.pl",
+        },
+    },
+}