diff --git a/.gitea/workflows/deploy-verso-prod.yml b/.gitea/workflows/deploy-verso-prod.yml index d03b91726e..d6718eea02 100644 --- a/.gitea/workflows/deploy-verso-prod.yml +++ b/.gitea/workflows/deploy-verso-prod.yml @@ -133,15 +133,14 @@ jobs: kubectl -n ci logs job/verso-buildkit-prod -c prepare || true kubectl -n ci logs job/verso-buildkit-prod -c buildkit || true - - name: Ensure namespace + persistent data services (never deleted) + - name: Ensure data services (Mongo + Redis, never deleted) run: | - kubectl create namespace verso --dry-run=client -o yaml | kubectl apply -f - - # Mongo/Redis. Applied idempotently — this step must never delete - # these, so project data survives every deploy. The PVCs themselves - # are provisioned out of band (server-ce/k8s/verso-prod-pvcs.yaml) so - # the storageClass is under your control; this step assumes - # mongo-data / redis-data / verso-data already exist. + # these, so project data survives every deploy. The namespace and the + # PVCs (server-ce/k8s/verso-prod-pvcs.yaml) are provisioned out of + # band, so the runner only needs namespaced rights in `verso` (like + # `test`). This step assumes the namespace and the + # mongo-data / redis-data / verso-data PVCs already exist. cat <<'EOF' | kubectl apply -f - apiVersion: apps/v1 kind: Deployment diff --git a/server-ce/k8s/verso-prod-app.yaml b/server-ce/k8s/verso-prod-app.yaml new file mode 100644 index 0000000000..4f01302d4a --- /dev/null +++ b/server-ce/k8s/verso-prod-app.yaml @@ -0,0 +1,139 @@ +# App tier for the prod (verso namespace) instance: the Verso Deployment and +# Service. Matches what the deploy workflow applies, except OVERLEAF_NAV_TITLE +# is a static "Verso Alpha" here — the workflow overwrites it with the build +# number ("Verso V0. Alpha") on each deploy. +# +# The image registry.alocoq.fr/verso:stable is produced by the prod workflow +# (push to the `prod` branch). If you apply this file before the first prod +# build, the pod will sit in ImagePullBackOff until that image exists — that's +# expected. +# +# kubectl apply -f server-ce/k8s/verso-prod-app.yaml + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: verso + namespace: verso +spec: + replicas: 1 + # RWO data volume → can't run two pods at once; recreate on update. + strategy: + type: Recreate + selector: + matchLabels: + app: verso + template: + metadata: + labels: + app: verso + spec: + securityContext: + # App runs as www-data (uid/gid 33); make the data volume + # group-writable by it. + fsGroup: 33 + initContainers: + - name: init-data-perms + image: busybox:latest + command: ["sh", "-c"] + args: + - | + set -eux + mkdir -p /data/template_files /data/user_files \ + /data/compiles /data/cache /data/output /data/published + chown -R 33:33 /data + volumeMounts: + - name: verso-data + mountPath: /data + containers: + - name: verso + image: registry.alocoq.fr/verso:stable + # :stable is a fixed tag, so force a pull on every rollout to pick up + # the freshly built image. + imagePullPolicy: Always + ports: + - containerPort: 80 + env: + - name: OVERLEAF_MONGO_URL + value: mongodb://mongo:27017/sharelatex?replicaSet=rs0 + - name: OVERLEAF_REDIS_HOST + value: redis + - name: REDIS_HOST + value: redis + - name: OVERLEAF_APP_NAME + value: Verso + - name: OVERLEAF_NAV_TITLE + value: "Verso Alpha" + - name: OVERLEAF_SITE_URL + value: https://verso.alocoq.fr + - name: OVERLEAF_SITE_LANGUAGE + value: fr + # Allow anonymous visitors so public published-presentation links + # and read-only share links work without login. + - name: OVERLEAF_ALLOW_PUBLIC_ACCESS + value: "true" + # NB: anonymous read-AND-write sharing is intentionally NOT enabled + # (compiles are unsandboxed → only trusted accounts may trigger + # them). Public self-registration is also off (CE default). + - name: OVERLEAF_ENABLE_PROJECT_PYTHON_VENV + value: "true" + # SMTP for password-reset / invite emails. Values come from the + # 'verso-smtp' Secret; all optional so the app boots before the + # secret exists (email stays off until OVERLEAF_EMAIL_FROM_ADDRESS + # is present). + - name: OVERLEAF_EMAIL_FROM_ADDRESS + valueFrom: + secretKeyRef: + name: verso-smtp + key: from-address + optional: true + - name: OVERLEAF_EMAIL_SMTP_HOST + valueFrom: + secretKeyRef: + name: verso-smtp + key: smtp-host + optional: true + - name: OVERLEAF_EMAIL_SMTP_PORT + valueFrom: + secretKeyRef: + name: verso-smtp + key: smtp-port + optional: true + - name: OVERLEAF_EMAIL_SMTP_SECURE + valueFrom: + secretKeyRef: + name: verso-smtp + key: smtp-secure + optional: true + - name: OVERLEAF_EMAIL_SMTP_USER + valueFrom: + secretKeyRef: + name: verso-smtp + key: smtp-user + optional: true + - name: OVERLEAF_EMAIL_SMTP_PASS + valueFrom: + secretKeyRef: + name: verso-smtp + key: smtp-pass + optional: true + volumeMounts: + - name: verso-data + mountPath: /var/lib/overleaf/data + volumes: + - name: verso-data + persistentVolumeClaim: + claimName: verso-data +--- +apiVersion: v1 +kind: Service +metadata: + name: verso + namespace: verso +spec: + selector: + app: verso + ports: + - name: http + port: 80 + targetPort: 80 diff --git a/server-ce/k8s/verso-prod-data.yaml b/server-ce/k8s/verso-prod-data.yaml new file mode 100644 index 0000000000..0c1a4d6e02 --- /dev/null +++ b/server-ce/k8s/verso-prod-data.yaml @@ -0,0 +1,102 @@ +# Data tier for the prod (verso namespace) instance: Mongo + Redis Deployments +# and Services. Identical to what the deploy workflow applies — provided as a +# standalone file so you can bootstrap and validate the namespace before the +# first prod build (and before granting the runner access). +# +# Order: +# 1. kubectl apply -f server-ce/k8s/verso-prod-pvcs.yaml (with storageClass) +# 2. kubectl apply -f server-ce/k8s/verso-prod-data.yaml (this file) +# 3. wait for mongo to be Ready, then initialise the replica set ONCE: +# kubectl -n verso exec deploy/mongo -- mongosh --quiet --eval \ +# 'rs.initiate({_id:"rs0",members:[{_id:0,host:"mongo:27017"}]})' +# (the workflow also does this idempotently, so it's optional here) + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongo + namespace: verso +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: mongo + template: + metadata: + labels: + app: mongo + spec: + containers: + - name: mongo + image: mongo:8 + command: ["mongod", "--replSet", "rs0", "--bind_ip_all"] + ports: + - containerPort: 27017 + volumeMounts: + - name: mongo-data + mountPath: /data/db + volumes: + - name: mongo-data + persistentVolumeClaim: + claimName: mongo-data +--- +apiVersion: v1 +kind: Service +metadata: + name: mongo + namespace: verso +spec: + selector: + app: mongo + ports: + - name: mongo + port: 27017 + targetPort: 27017 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: verso +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:7 + # AOF persistence so a restart doesn't drop in-flight edits before + # they're flushed to Mongo. + command: ["redis-server", "--appendonly", "yes"] + ports: + - containerPort: 6379 + volumeMounts: + - name: redis-data + mountPath: /data + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-data +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: verso +spec: + selector: + app: redis + ports: + - name: redis + port: 6379 + targetPort: 6379