35fa7cec05
Build and Deploy Verso / deploy (push) Successful in 1m21s
- README: show the Verso wordmark logo instead of a text title. - README: original Overleaf copyright now 2014-2026; Verso modifications 2026. - Instance/version title: 'alpha' -> 'Alpha'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
339 lines
13 KiB
YAML
339 lines
13 KiB
YAML
name: Build and Deploy Verso
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
SITE_URL: https://test.alocoq.fr
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: native
|
|
timeout-minutes: 240
|
|
|
|
steps:
|
|
- name: Build and push Verso images with BuildKit
|
|
run: |
|
|
kubectl -n ci delete job verso-buildkit --ignore-not-found=true --wait=true
|
|
|
|
cat <<'EOF' | kubectl apply -f -
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: verso-buildkit
|
|
namespace: ci
|
|
spec:
|
|
backoffLimit: 0
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
initContainers:
|
|
- name: prepare
|
|
image: alpine/git:latest
|
|
command: ["sh", "-c"]
|
|
args:
|
|
- |
|
|
set -eux
|
|
REG=registry.git.svc.cluster.local:5000
|
|
git clone --depth 1 https://git.alocoq.fr/alois/verso.git /workspace/repo
|
|
|
|
# (#1) Build the base image only when it actually changes.
|
|
# The base layers' only repo input is Dockerfile-base, so
|
|
# we key on a content hash of that file: the base is tagged
|
|
# verso-base:base-<hash> and the app builds FROM that exact
|
|
# tag. If a base with this hash is already in the registry,
|
|
# the heavy base build (apt, TeX Live, Quarto) is skipped.
|
|
BTAG=$(sha256sum /workspace/repo/server-ce/Dockerfile-base | cut -c1-16)
|
|
printf '%s' "$BTAG" > /workspace/base_tag
|
|
if wget -qO- "http://$REG/v2/verso-base/tags/list" 2>/dev/null | grep -q "\"base-$BTAG\""; then
|
|
echo "Base image base-$BTAG already present - skipping base build"
|
|
else
|
|
touch /workspace/build-base
|
|
echo "Base image base-$BTAG not found - base will be built"
|
|
fi
|
|
volumeMounts:
|
|
- name: workspace
|
|
mountPath: /workspace
|
|
|
|
containers:
|
|
- name: buildkit
|
|
image: moby/buildkit:latest
|
|
securityContext:
|
|
privileged: true
|
|
command: ["sh", "-c"]
|
|
args:
|
|
- |
|
|
set -eux
|
|
|
|
# Push to the in-cluster registry (plain HTTP) to bypass
|
|
# the Traefik ingress, whose read timeout was killing the
|
|
# multi-GB TeX Live layer upload mid-stream. Mark the
|
|
# registry http+insecure so both push and the base pull
|
|
# for the app build treat it as plain HTTP. Written inside
|
|
# the container so no extra k8s resources are needed.
|
|
REG=registry.git.svc.cluster.local:5000
|
|
|
|
mkdir -p /etc/buildkit
|
|
printf '[registry."%s"]\n http = true\n insecure = true\n' "$REG" > /etc/buildkit/buildkitd.toml
|
|
|
|
BTAG=$(cat /workspace/base_tag)
|
|
BASE_REF="$REG/verso-base:base-$BTAG"
|
|
|
|
# (#1) Base build, only when prepare flagged it changed.
|
|
# (#2) Import/export a registry layer cache so that, when
|
|
# the base does change, unchanged layers (e.g. apt) are
|
|
# still reused instead of rebuilt from scratch.
|
|
if [ -f /workspace/build-base ]; then
|
|
buildctl-daemonless.sh build \
|
|
--frontend=dockerfile.v0 \
|
|
--local context=/workspace/repo \
|
|
--local dockerfile=/workspace/repo/server-ce \
|
|
--opt filename=Dockerfile-base \
|
|
--import-cache type=registry,ref=$REG/verso-cache:base \
|
|
--export-cache type=registry,ref=$REG/verso-cache:base,mode=max \
|
|
--output type=image,name=$BASE_REF,push=true,registry.insecure=true
|
|
else
|
|
echo "Reusing existing base image $BASE_REF"
|
|
fi
|
|
|
|
# App image, built FROM the content-pinned base tag.
|
|
# (#2) The registry cache lets yarn install be skipped when
|
|
# package.json is unchanged; the web build only re-runs
|
|
# when the frontend source actually changes.
|
|
buildctl-daemonless.sh build \
|
|
--frontend=dockerfile.v0 \
|
|
--local context=/workspace/repo \
|
|
--local dockerfile=/workspace/repo/server-ce \
|
|
--opt filename=Dockerfile \
|
|
--opt build-arg:OVERLEAF_BASE_TAG=$BASE_REF \
|
|
--import-cache type=registry,ref=$REG/verso-cache:app \
|
|
--export-cache type=registry,ref=$REG/verso-cache:app,mode=max \
|
|
--output type=image,name=$REG/verso:latest,push=true,registry.insecure=true
|
|
volumeMounts:
|
|
- name: workspace
|
|
mountPath: /workspace
|
|
|
|
volumes:
|
|
- name: workspace
|
|
emptyDir: {}
|
|
EOF
|
|
|
|
- name: Wait for build
|
|
run: |
|
|
kubectl -n ci wait --for=condition=complete job/verso-buildkit --timeout=14400s
|
|
|
|
- name: Show build logs
|
|
if: always()
|
|
run: |
|
|
kubectl -n ci logs job/verso-buildkit -c prepare || true
|
|
kubectl -n ci logs job/verso-buildkit -c buildkit || true
|
|
|
|
- name: Recreate test dependencies
|
|
run: |
|
|
kubectl -n test delete deployment mongo redis --ignore-not-found=true --wait=true
|
|
kubectl -n test delete service mongo redis --ignore-not-found=true --wait=true
|
|
|
|
cat <<'EOF' | kubectl apply -f -
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: mongo
|
|
namespace: test
|
|
spec:
|
|
replicas: 1
|
|
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
|
|
emptyDir: {}
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: mongo
|
|
namespace: test
|
|
spec:
|
|
selector:
|
|
app: mongo
|
|
ports:
|
|
- name: mongo
|
|
port: 27017
|
|
targetPort: 27017
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: redis
|
|
namespace: test
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: redis
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: redis
|
|
spec:
|
|
containers:
|
|
- name: redis
|
|
image: redis:7
|
|
ports:
|
|
- containerPort: 6379
|
|
volumeMounts:
|
|
- name: redis-data
|
|
mountPath: /data
|
|
volumes:
|
|
- name: redis-data
|
|
emptyDir: {}
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: redis
|
|
namespace: test
|
|
spec:
|
|
selector:
|
|
app: redis
|
|
ports:
|
|
- name: redis
|
|
port: 6379
|
|
targetPort: 6379
|
|
EOF
|
|
|
|
kubectl -n test rollout status deployment/mongo --timeout=180s
|
|
kubectl -n test rollout status deployment/redis --timeout=180s
|
|
|
|
sleep 5
|
|
|
|
kubectl -n test exec deploy/mongo -- mongosh --eval '
|
|
rs.initiate({
|
|
_id: "rs0",
|
|
members: [{ _id: 0, host: "mongo:27017" }]
|
|
})
|
|
'
|
|
|
|
kubectl -n test exec deploy/mongo -- mongosh --eval '
|
|
while (rs.status().myState !== 1) {
|
|
sleep(1000)
|
|
}
|
|
print("Mongo replica set is PRIMARY")
|
|
'
|
|
|
|
- name: Ensure Verso deployment exists
|
|
run: |
|
|
# Stamp the instance name with this build number, e.g. "Verso V0.83 Alpha".
|
|
NAV_TITLE="Verso V0.${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}} Alpha"
|
|
cat <<'EOF' | sed "s|__NAV_TITLE__|${NAV_TITLE}|g" | kubectl apply -f -
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: verso
|
|
namespace: test
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: verso
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: verso
|
|
spec:
|
|
containers:
|
|
- name: verso
|
|
# Pull via the public address: the cluster nodes' containerd
|
|
# is configured for registry.alocoq.fr, not the in-cluster
|
|
# service name. Both front the same registry storage, so the
|
|
# image pushed via the in-cluster address resolves here too.
|
|
image: registry.alocoq.fr/verso:latest
|
|
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: "__NAV_TITLE__"
|
|
- name: OVERLEAF_SITE_URL
|
|
value: https://test.alocoq.fr
|
|
# Default UI language for the instance.
|
|
- name: OVERLEAF_SITE_LANGUAGE
|
|
value: fr
|
|
# Allow anonymous visitors to reach the site so link
|
|
# sharing and public presentation links work without a
|
|
# login. Per-project and per-route access checks still
|
|
# apply; private presentation links still require login.
|
|
- name: OVERLEAF_ALLOW_PUBLIC_ACCESS
|
|
value: "true"
|
|
# Also let anonymous visitors use read-AND-write share
|
|
# links (edit without an account). Read-only links only
|
|
# need OVERLEAF_ALLOW_PUBLIC_ACCESS above.
|
|
- name: OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING
|
|
value: "true"
|
|
# Let Quarto Python cells use a project's requirements.txt:
|
|
# the compiler installs it into a cached venv. Gated to the
|
|
# project owner + invited collaborators (never anonymous /
|
|
# link-sharing users).
|
|
- name: OVERLEAF_ENABLE_PROJECT_PYTHON_VENV
|
|
value: "true"
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: verso
|
|
namespace: test
|
|
spec:
|
|
selector:
|
|
app: verso
|
|
ports:
|
|
- name: http
|
|
port: 80
|
|
targetPort: 80
|
|
EOF
|
|
|
|
- name: Deploy Verso image
|
|
run: |
|
|
kubectl -n test set image deployment/verso \
|
|
verso=registry.alocoq.fr/verso:latest
|
|
|
|
kubectl -n test rollout restart deployment/verso
|
|
kubectl -n test rollout status deployment/verso --timeout=300s
|
|
|
|
- name: Create admin user
|
|
run: |
|
|
sleep 20
|
|
kubectl -n test exec deploy/verso -- bash -lc '
|
|
cd /overleaf/services/web
|
|
node modules/server-ce-scripts/scripts/create-user \
|
|
--admin \
|
|
--email=test@example.com || true
|
|
'
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
run: |
|
|
kubectl -n ci delete job verso-buildkit --ignore-not-found=true --wait=true |