676663ffcc
Build and Deploy Verso / deploy (push) Successful in 9m41s
- Editor rail: the active item used the Overleaf green accent. Switch --ide-rail-link-active-color/background to the blue scale (--blue-10/70, --bg-info-03) to match the Verso palette. - Footers: remove the default "Fork on GitHub!" right_footer item (redundant with the "Built on Overleaf" link); right_footer now defaults to []. - Login: move the hero wordmark into a full-width centered block and bump it to max-width 480px so it's no longer constrained by the form column. - Projects dashboard: restore the instance name in the top-left navbar (set OVERLEAF_NAV_TITLE="Verso V1.0 Alpha") instead of the wordmark logo, and move the full Verso wordmark to the sidebar's lower section (where the old "Digital Science" mark sat). Revert HeaderLogoOrTitle to its title-first behaviour now that the dashboard no longer passes a logo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
337 lines
13 KiB
YAML
337 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: |
|
|
cat <<'EOF' | 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: Verso V1.0 Alpha
|
|
- 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 |