mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
feat/wakatime integration- #249 plus tools/reference_stack_compose with wakapi
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.env
|
||||
backup_seaweedfs_online
|
||||
|
||||
workbench
|
||||
@@ -0,0 +1,9 @@
|
||||
docker network create overleaf-network
|
||||
snetz=`docker network inspect overleaf-network | grep "Subnet" | sed s/" "/""/g | sed s/"\,"/""/g | sed s/":"/"\n"/g | grep -v "Subnet" | sed s/'"'/''/g | sed s/"{"/""/g`
|
||||
nid=`docker network ls | grep overleaf-network | awk '{print $1}'`
|
||||
|
||||
ufw allow in on br-$nid
|
||||
ufw route allow in on br-$nid
|
||||
ufw route allow out on br-$nid
|
||||
iptables -t nat -A POSTROUTING ! -o br-$nid -s $snetz -j MASQUERADE
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export AWS_ACCESS_KEY_ID
|
||||
export AWS_SECRET_ACCESS_KEY
|
||||
|
||||
ENDPOINT="http://localhost:8333"
|
||||
|
||||
echo "Testing connection to SeaweedFS S3 gateway..."
|
||||
aws --endpoint-url "$ENDPOINT" s3 ls
|
||||
|
||||
# Array of required Overleaf buckets
|
||||
BUCKETS=(
|
||||
"overleaf-user-files"
|
||||
"overleaf-template-files"
|
||||
"overleaf-project-blobs"
|
||||
"overleaf-chunks"
|
||||
)
|
||||
|
||||
echo "Checking and creating Overleaf buckets..."
|
||||
for bucket in "${BUCKETS[@]}"; do
|
||||
if ! aws --endpoint-url "$ENDPOINT" s3 ls "s3://$bucket" >/dev/null 2>&1; then
|
||||
echo "Creating bucket: s3://$bucket"
|
||||
aws --endpoint-url "$ENDPOINT" s3 mb "s3://$bucket"
|
||||
else
|
||||
echo "Bucket already exists: s3://$bucket"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Bucket setup complete. Current buckets:"
|
||||
aws --endpoint-url "$ENDPOINT" s3 ls
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# 0_ensure_local_auth_type.sh — manage wakapi's users-table normalization
|
||||
# (wakapi/ensure_local_auth_type.sql: trigger + one-time backfill).
|
||||
#
|
||||
# MODES
|
||||
# preflight Drop the trigger (no-op if absent / if the table doesn't
|
||||
# exist yet). Run BEFORE starting or recreating wakapi.
|
||||
# apply Wait for wakapi to serve, then (re)apply trigger + backfill.
|
||||
# Run AFTER wakapi is up. This is the default mode.
|
||||
#
|
||||
# WHY (twice-learned rule, 2026-10-26):
|
||||
# A trigger referencing `users` BLOCKS wakapi's pending startup DDL —
|
||||
# `ERROR: cannot alter type of a column used in a trigger definition
|
||||
# (SQLSTATE 0A000)` — and wakapi crashloops until the trigger is gone.
|
||||
# The batches are recorded per-run; while any of them is unrecorded, EVERY
|
||||
# startup re-runs the batch and can hit the collision. So the only safe
|
||||
# cycle is: drop -> wakapi up (batch runs free, records) -> apply.
|
||||
# up.sh and up_wakapi.sh do exactly this. Rows written during the short
|
||||
# window without the trigger are covered by the apply-time backfill.
|
||||
#
|
||||
# The SQL itself is idempotent (create or replace / drop if exists /
|
||||
# guarded updates), so the dance costs a few SQL statements per `up`.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
MODE="${1:-apply}"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
set -a; . ./.env; set +a
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
PGCLIENT="docker run --rm --network overleaf-network -e PGPASSWORD=${PG_PASSWORD}
|
||||
-e PGUSER=${WAKAPI_DB_USER:-wakapi} -e PGDATABASE=${WAKAPI_DB_NAME:-wakapi}
|
||||
${POSTGRES_IMAGE}"
|
||||
|
||||
wait_pg() {
|
||||
for i in $(seq 1 60); do
|
||||
${PGCLIENT} sh -c 'pg_isready -h wakapi-pg -p 5432' >/dev/null 2>&1 && return 0
|
||||
sleep 2
|
||||
done
|
||||
die "wakapi-pg not accepting connections after 120s"
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
preflight)
|
||||
echo "[preflight/1] waiting for wakapi-pg (needed to drop the trigger)..."
|
||||
wait_pg
|
||||
echo "[preflight/2] dropping normalization trigger (if any) so wakapi startup DDL can run..."
|
||||
${PGCLIENT} psql -h wakapi-pg -p 5432 -v ON_ERROR_STOP=1 -c \
|
||||
"do \$\$ begin execute 'drop trigger if exists trg_wakapi_normalize_local_auth on users'; exception when undefined_object then null; end \$\$;" \
|
||||
>/dev/null
|
||||
echo "[preflight] done (no-op if the trigger wasn't there)"
|
||||
;;
|
||||
apply)
|
||||
echo "[1/3] waiting for wakapi-pg to accept connections..."
|
||||
wait_pg
|
||||
|
||||
echo "[2/3] waiting for users table + wakapi serving (startup migrations complete)..."
|
||||
READY=0
|
||||
for i in $(seq 1 90); do
|
||||
TBL=$( ${PGCLIENT} psql -h wakapi-pg -p 5432 -tAq -c "select to_regclass('public.users')")
|
||||
if [ "$TBL" = "users" ] \
|
||||
&& docker run --rm --network overleaf-network ${PROBE_IMAGE} \
|
||||
wget -q --spider http://wakapi:3000/login >/dev/null 2>&1; then
|
||||
READY=1; break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
[ "$READY" = "1" ] \
|
||||
|| die "users table or wakapi not ready/serving after 180s — check: bash logs_wakapi.sh (look for 'migration failed')"
|
||||
|
||||
echo "[3/3] applying wakapi/ensure_local_auth_type.sql ..."
|
||||
docker run --rm --network overleaf-network \
|
||||
-e PGPASSWORD="${PG_PASSWORD}" \
|
||||
-e PGUSER="${WAKAPI_DB_USER:-wakapi}" \
|
||||
-e PGDATABASE="${WAKAPI_DB_NAME:-wakapi}" \
|
||||
-v "$(pwd)/wakapi/ensure_local_auth_type.sql:/init.sql:ro" \
|
||||
${POSTGRES_IMAGE} \
|
||||
psql -h wakapi-pg -p 5432 -v ON_ERROR_STOP=1 -f /init.sql
|
||||
|
||||
echo "active trigger(s) on users:"
|
||||
${PGCLIENT} psql -h wakapi-pg -p 5432 -tAq \
|
||||
-c "select ' '||tgname from pg_trigger where tgrelid='public.users'::regclass and not tgisinternal order by 1"
|
||||
echo "OK — local-auth normalization is active (staff 'Forgot password?' path works)."
|
||||
;;
|
||||
*)
|
||||
die "unknown mode '$MODE' (expected: preflight | apply)"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,2 @@
|
||||
sudo apt update
|
||||
sudo apt install -y awscli
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Path to your .env file (adjust if your script runs from a different directory)
|
||||
ENV_FILE="./.env"
|
||||
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found at $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure AWS variables are exported for the AWS CLI
|
||||
export AWS_ACCESS_KEY_ID
|
||||
export AWS_SECRET_ACCESS_KEY
|
||||
|
||||
ENDPOINT="http://localhost:8333"
|
||||
BACKUP_DIR="./backup_seaweedfs_online"
|
||||
TIMESTAMP=$(date +%F_%H-%M-%S)
|
||||
|
||||
mkdir -p "$BACKUP_DIR/$TIMESTAMP"
|
||||
|
||||
echo "1. Capturing online metadata snapshot..."
|
||||
docker exec seaweedfs-filer weed shell -master="master:9333" <<< "fs.meta.save /data/filer_meta_$TIMESTAMP.json"
|
||||
|
||||
echo "2. Syncing S3 buckets live..."
|
||||
for bucket in overleaf-user-files overleaf-template-files overleaf-project-blobs overleaf-chunks; do
|
||||
echo "Syncing s3://$bucket..."
|
||||
aws --endpoint-url "$ENDPOINT" s3 sync "s3://$bucket" "$BACKUP_DIR/$TIMESTAMP/$bucket"
|
||||
done
|
||||
|
||||
echo "Online backup completed successfully at $TIMESTAMP"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker pull ${TEX_LIVE_REPO}/texlive-full:2026.1
|
||||
docker pull ${TEX_LIVE_REPO}/texlive-full:2025.1
|
||||
docker pull ${TEX_LIVE_REPO}/texlive-full:2024.1
|
||||
docker pull ${TEX_LIVE_REPO}/texlive-full:2023.1
|
||||
|
||||
docker pull nginx:stable-alpine
|
||||
docker pull mongo:${MONGO_VERSION}
|
||||
docker pull redis:${REDIS_VERSION}
|
||||
docker pull chrislusf/seaweedfs:latest
|
||||
@@ -0,0 +1,180 @@
|
||||
# Overleaf CE — self-hosted stack (`psintern.neuro.uni-bremen.de`)
|
||||
|
||||
One Docker Compose project that runs the entire server: Overleaf CE (languagetool CE
|
||||
fork), its storage backend (MongoDB + Redis + SeaweedFS/S3), Git sync (git-bridge),
|
||||
grammar checking (LanguageTool), WakaTime-style activity tracking (Wakapi), and the
|
||||
TLS edge (nginx). Everything runs in the shared external network **`overleaf-network`**.
|
||||
|
||||
- Public URL: `https://psintern.neuro.uni-bremen.de` (Overleaf)
|
||||
- Wakapi UI: `https://psintern.neuro.uni-bremen.de/wakapi/` (same host, nginx subpath)
|
||||
- Server source: **not this repo** — the Overleaf image is built from the
|
||||
`overleaf-cep` checkout (`ext-ce-v6.3.1` branch); this directory only *deploys*.
|
||||
|
||||
---
|
||||
|
||||
## 1. Layout
|
||||
|
||||
```
|
||||
compose.yaml # root project: composes all services below via `extends`,
|
||||
# adds depends_on ordering, declares the external network
|
||||
<service>/compose.yaml # one service per directory (see table)
|
||||
.env # ALL configuration incl. secrets — git-ignored, never commit
|
||||
env.sample # committed, secret-free template of .env (cp env.sample .env)
|
||||
0_*.sh # one-time setup (run once, in this order)
|
||||
up_*.sh down_*.sh # per-service lifecycle (they cd to the project root themselves)
|
||||
logs_*.sh exec_*.sh # per-service logs / shell
|
||||
autoupdate_*.sh # pull next image version for a service (then up_*.sh)
|
||||
cycle_{nginx,overleaf}.sh
|
||||
0_ensure_local_auth_type.sh # wakapi users-table normalization trigger, 2 modes:
|
||||
# preflight — drop trigger (BEFORE wakapi up, unblocks its DDL)
|
||||
# apply — trigger + backfill (AFTER wakapi serves, default)
|
||||
# Both run automatically from up.sh / up_wakapi.sh.
|
||||
# Manual: bash 0_ensure_local_auth_type.sh [preflight|apply]
|
||||
up.sh / down.sh # the whole project (up.sh first auto-pins OVERLEAF_IP, see §5)
|
||||
<service>/data* # live data volumes (mongo, redis, seaweedfs-*, overleaf, ...) — git-ignored
|
||||
<service>/backup/ # backup dumps (git-ignored)
|
||||
backup_seaweedfs_online/# timestamped SeaweedFS online backups (git-ignored)
|
||||
<service>/backup.sh # mongo/, wakapi-pg/: dump into <service>/backup/
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
| dir | container | image (env pin) | role |
|
||||
|---|---|---|---|
|
||||
| `overleaf/` | `overleaf` | `sharelatex/sharelatex:${OVERLEAF_VERSION}` | the app (CE fork). `./data` = /var/lib/overleaf, `./log` = /var/log/overleaf, docker.sock mounted |
|
||||
| `mongo/` | `mongo` | `mongo:${MONGO_VERSION}` | documents/projects. Runs as replica set `overleaf`. `./data_db`, `./backup` |
|
||||
| `redis/` | `redis` | `redis:${REDIS_VERSION}` | sessions/queues. AOF persistence. `./data`, `./backup` |
|
||||
| `seaweedfs-{master,volume,filer,s3}/` | same | `chrislusf/seaweedfs:latest` | S3-compatible file storage for overleaf (compiles, uploads, exports). Host ports bound to 127.0.0.1 only (9333/19333 master, 18080 volume gRPC, 8888 filer, 8333 S3 API). In-network aliases `master`, `volume`, `filer`, `s3` |
|
||||
| `git-bridge/` | `git-bridge` | `quay.io/sharelatex/git-bridge:${GIT_BRIDE_VERSION}` | Git export/sync for projects (texlive repo `TEX_LIVE_REPO`) |
|
||||
| `languagetool/` | `languagetool` | `erikvl87/languagetool:latest` | grammar checking (enabled in overleaf: `LANGUAGE_TOOL_*`); ngram models in `./ngrams` |
|
||||
| `wakapi-pg/` | `wakapi-pg` | `postgres:${POSTGRES_IMAGE}` | Wakapi's database (db/user `wakapi`, pw `${PG_PASSWORD}`). `./data`, `./backup` |
|
||||
| `wakapi/` | `wakapi` | `${WAKA_API_IMAGE}` (stock ghcr or local build) | WakaTime-compatible time tracking, self-hosted |
|
||||
| `wakapi-probe/` | `wakapi-probe` | `${PROBE_IMAGE}` (busybox, `sleep infinity`) | nothing it runs — a *healthcheck-only* container: "HTTP serving" proof for wakapi; nginx depends on it being healthy |
|
||||
| `nginx/` | `nginx` | `nginx:stable-alpine` | TLS edge (Let's Encrypt files from `SSL_KEY_FILE`/`SSL_CERT_FILE`), ACME dir, serves Overleaf at `/` and Wakapi at `/wakapi/` |
|
||||
|
||||
Startup order (enforced by `depends_on: service_healthy` in the root file):
|
||||
`redis, mongo, seaweedfs-* → overleaf → nginx`; `wakapi-pg → wakapi → wakapi-probe → nginx`;
|
||||
`git-bridge`, `languagetool` independent.
|
||||
|
||||
---
|
||||
|
||||
## 2. Configuration (`.env`)
|
||||
|
||||
- **`env.sample` is the source of truth for *what keys exist* and what they mean** —
|
||||
copy it (values included where safe), then fill the `<PLACEHOLDER>`s. `.env` is
|
||||
git-ignored; **never** commit it, and **never** paste values from it into
|
||||
`env.sample` (it's the template, not a backup).
|
||||
- Plain `KEY=value` lines. Compose interpolates `${OTHER_KEY}` between keys, but
|
||||
**no shell**: `$(docker ...)`, backticks, `;` do **not** run — a line like
|
||||
`FOO=$(cmd)` stores the literal string. (Wakapi, e.g., would then warn and drop it.)
|
||||
- Secrets in play: AWS S3 keys (SeaweedFS S3 API), Zotero app creds, GitHub App
|
||||
creds, Keycloak OIDC client, invite-token secret, Overleaf SMTP password,
|
||||
wakapi-pg password. Generator hints are in the comments.
|
||||
- `OVERLEAF_IP` is **auto-maintained**: `up.sh` / `up_wakapi.sh` rewrite it from
|
||||
`docker inspect overleaf` before bringing (re)created containers up. Don't fight it.
|
||||
|
||||
---
|
||||
|
||||
## 3. First-time setup order
|
||||
|
||||
```bash
|
||||
bash 0_create_network.sh # overleaf-network + ufw/iptables for it
|
||||
bash 0_install_aws.sh # aws cli (for bucket ops)
|
||||
bash 0_create_s3_buckets.sh # seaweedfs S3 buckets for overleaf
|
||||
bash 0_pull_other_containers.sh # image pulls
|
||||
cp env.sample .env && vi .env # fill secrets
|
||||
bash up.sh # everything (pins OVERLEAF_IP first)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Day-to-day
|
||||
|
||||
```bash
|
||||
bash up_<svc>.sh # start/recreate one service (mongo, redis, overleaf, nginx,
|
||||
# wakapi, wakapi-pg, wakapi-probe, seaweedfs-*, git-bridge, languagetool)
|
||||
bash down_<svc>.sh # stop+remove it
|
||||
bash logs_<svc>.sh # follow logs
|
||||
bash exec_<svc>.sh # interactive shell (where the image allows)
|
||||
bash cycle_nginx.sh # down+up nginx (config change, cert renewal)
|
||||
bash up.sh # whole project
|
||||
# backups
|
||||
bash mongo/backup.sh # mongodump -> mongo/backup/
|
||||
bash wakapi-pg/backup.sh # pg_dump -> wakapi-pg/backup/
|
||||
# see backup_seaweedfs_online/ + 0_online_backup_s3.sh for the seaweedfs side
|
||||
```
|
||||
|
||||
**Image upgrades:** bump the `*_VERSION` / `WAKA_API_IMAGE` pins in `.env`
|
||||
(or edit the `image:` line in the service's compose.yaml), then `bash up_<svc>.sh` —
|
||||
compose replaces the container. `autoupdate_*.sh` do the pin-bump for
|
||||
mongo/redis/nginx/languagetool/seaweedfs.
|
||||
|
||||
---
|
||||
|
||||
## 5. Special behaviors that are easy to step on
|
||||
|
||||
- **Overleaf rebuilds**: the image comes from the `overleaf-cep` checkout
|
||||
(`ext-ce-v6.3.1`). Rebuild there (see that repo's `server-ce/` + the
|
||||
`rebuild-overleaf-docker-image` skill), tag/push per `.env`'s `OVERLEAF_VERSION`,
|
||||
then `bash up_overleaf.sh`. A plain `docker compose up` does **not** swap a
|
||||
same-tag image unless pulled/retagged.
|
||||
- **Wakapi identity model** (no external signup, no per-user keys):
|
||||
- `WAKAPI_ALLOW_SIGNUP=false`, trusted-header auth on: Overleaf (the **only** trusted
|
||||
source IP — `WAKAPI_TRUST_REVERSE_PROXY_IPS` = `OVERLEAF_IP`) asserts the logged-in
|
||||
user's email in `Remote-User`; wakapi auto-creates/identifies the user.
|
||||
Hostname values in that list are **silently dropped** by wakapi (IP/CIDR only).
|
||||
- Staff get a UI password **self-service**: `…/wakapi/login` → "Forgot password?"
|
||||
→ reset mail via the university relay (`OVERLEAF_EMAIL_*`, sender
|
||||
`overleaf@uni-bremen.de`) → set password. Requires `WAKAPI_PUBLIC_URL`
|
||||
(`${OVERLEAF_SITE_URL}/wakapi` — the ship default `http://localhost:3000` would
|
||||
mail useless links).
|
||||
- **Normalization trigger for trusted-header users** (kept in the DB, not in
|
||||
wakapi): `wakapi/ensure_local_auth_type.sql` — a PG trigger on `wakapi-pg`
|
||||
rewriting header-created rows to `auth_type='local'` + `email=id`, else
|
||||
wakapi refuses the "Forgot password?" reset (routes/login.go).
|
||||
**Fully automatic safe cycle** (see `0_ensure_local_auth_type.sh` header):
|
||||
`up.sh` / `up_wakapi.sh` first run `preflight` (drop the trigger if present)
|
||||
so wakapi's startup DDL can never be blocked by it, start wakapi (pending
|
||||
batches run free and record), then `apply` (trigger + backfill — the
|
||||
backfill also covers rows written in the short unpinned window). This is
|
||||
mandatory while **any** wakapi migration batch is unrecorded: then every
|
||||
startup re-runs the batch and hits the trigger (`SQLSTATE 0A000`, the
|
||||
2026-10-26 crashloop). Once all batches are recorded the drop/apply dance
|
||||
remains a cheap no-cost safety net (e.g. after wipes or wakapi upgrades).
|
||||
**Only if you start wakapi manually** outside those scripts
|
||||
(`docker compose up -d wakapi`), run the preflight first:
|
||||
`bash 0_ensure_local_auth_type.sh preflight`
|
||||
- **Wakapi base path**: the app serves at `/` and only shapes *generated* URLs with
|
||||
`WAKAPI_BASE_PATH=/wakapi`. That's why nginx does
|
||||
`location /wakapi/ { proxy_pass http://wakapi:3000/; }` — the **trailing slash**
|
||||
strips the prefix. Removing it 404s the UI.
|
||||
- **nginx strips `Remote-User` on browser traffic** (`proxy_set_header Remote-User ""`)
|
||||
so clients cannot inject identities; only the overleaf container (pinned IP) may.
|
||||
- **mongo** runs `--replSet overleaf` with the init-in-healthcheck trick, and
|
||||
`GLIBC_TUNABLES: glibc.cpu.hwcaps=-SHSTK` (CPU feature-hole workaround — don't
|
||||
"clean" it).
|
||||
- **seaweedfs**: four cooperating containers; in-network names are the classic
|
||||
`master/filer/volume/s3` (aliases), host ports are loopback-only. The volume
|
||||
deliberately does **not** publish 8080 to the host (collision) — keep it that way.
|
||||
- **Health is load-bearing**: `nginx` waits for `overleaf` + `wakapi-probe`
|
||||
(healthchecks) before being healthy; `wakapi-probe` is an idle busybox whose
|
||||
healthcheck is the wakapi HTTP probe.
|
||||
- **UFW/iptables**: `0_create_network.sh` opens the bridge interface — after
|
||||
reboots or network recreation, make sure these rules still exist.
|
||||
- **Cold start fails with `Conflict. The container name "/<svc>" is already in
|
||||
use`**: an orphan container from another project (e.g. the old
|
||||
`tools/*_server_compose` recipes) still holds the name and `down.sh` didn't
|
||||
remove it (it's not this project's). Find it: `docker ps -a --filter name=<svc>`,
|
||||
then `docker rm <name>` (volumes, if any, stay on disk) and re-run `up.sh`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Related locations
|
||||
|
||||
- Overleaf server source & build: `overleaf-cep` checkout, branch `ext-ce-v6.3.1`
|
||||
(modules: `services/web/modules/{wakime,zotero,track-changes,…}`)
|
||||
- Wakapi upstream source checkout (for template/feature work against a pinned tag):
|
||||
`/data_1/compare/wakapi` (has tags incl. `2.18.2`)
|
||||
- Skills with the muscle memory for this box:
|
||||
`overleaf-dev-server-testing`, `rebuild-overleaf-docker-image`,
|
||||
`overleaf-agent-workflows`
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SERVICE_NAME="languagetool"
|
||||
IMAGE_NAME="erikvl87/languagetool:latest"
|
||||
YAML_DIRECTORY_NAME="${COMPOSE_DIRECTORY:-$(pwd)}"
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="mongo"
|
||||
IMAGE_NAME="mongo:${MONGO_VERSION}"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="nginx"
|
||||
IMAGE_NAME="nginx:stable-alpine"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="redis"
|
||||
IMAGE_NAME="redis:${REDIS_VERSION}"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="seaweedfs-filer"
|
||||
IMAGE_NAME="chrislusf/seaweedfs:latest"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="seaweedfs-master"
|
||||
IMAGE_NAME="chrislusf/seaweedfs:latest"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="seaweedfs-s3"
|
||||
IMAGE_NAME="chrislusf/seaweedfs:latest"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
else
|
||||
echo "Error: .env file not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_NAME="seaweedfs-volume"
|
||||
IMAGE_NAME="chrislusf/seaweedfs:latest"
|
||||
YAML_DIRECTORY_NAME=${COMPOSE_DIRECTORY}
|
||||
|
||||
# Get current image ID (short format)
|
||||
CURRENT_IMAGE=$(docker inspect --format='{{.Image}}' "${SERVICE_NAME}" 2>/dev/null | cut -d: -f2 | cut -c1-12) || true
|
||||
|
||||
# Pull the latest image
|
||||
echo "Pulling latest image..."
|
||||
docker pull "${IMAGE_NAME}"
|
||||
|
||||
# Get new image ID (short format)
|
||||
NEW_IMAGE=$(docker images "${IMAGE_NAME}" --format '{{.ID}}')
|
||||
|
||||
echo "Current image: $CURRENT_IMAGE"
|
||||
echo "Latest image: $NEW_IMAGE"
|
||||
|
||||
# Compare and restart only if different
|
||||
if [ -z "$CURRENT_IMAGE" ]; then
|
||||
echo "Container not found, starting it up..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
elif [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
|
||||
echo "New image detected, restarting container..."
|
||||
cd "${YAML_DIRECTORY_NAME}"
|
||||
docker compose down "${SERVICE_NAME}"
|
||||
docker compose up -d "${SERVICE_NAME}"
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already running the latest version, no restart needed."
|
||||
fi
|
||||
@@ -0,0 +1,102 @@
|
||||
services:
|
||||
redis:
|
||||
extends:
|
||||
file: ./redis/compose.yaml
|
||||
service: redis
|
||||
|
||||
mongo:
|
||||
extends:
|
||||
file: ./mongo/compose.yaml
|
||||
service: mongo
|
||||
|
||||
seaweedfs-master:
|
||||
extends:
|
||||
file: ./seaweedfs-master/compose.yaml
|
||||
service: seaweedfs-master
|
||||
|
||||
seaweedfs-volume:
|
||||
extends:
|
||||
file: ./seaweedfs-volume/compose.yaml
|
||||
service: seaweedfs-volume
|
||||
depends_on:
|
||||
seaweedfs-master:
|
||||
condition: service_healthy
|
||||
|
||||
seaweedfs-filer:
|
||||
extends:
|
||||
file: ./seaweedfs-filer/compose.yaml
|
||||
service: seaweedfs-filer
|
||||
depends_on:
|
||||
seaweedfs-master:
|
||||
condition: service_healthy
|
||||
seaweedfs-volume:
|
||||
condition: service_healthy
|
||||
|
||||
seaweedfs-s3:
|
||||
extends:
|
||||
file: ./seaweedfs-s3/compose.yaml
|
||||
service: seaweedfs-s3
|
||||
depends_on:
|
||||
seaweedfs-filer:
|
||||
condition: service_healthy
|
||||
|
||||
git-bridge:
|
||||
extends:
|
||||
file: ./git-bridge/compose.yaml
|
||||
service: git-bridge
|
||||
|
||||
languagetool:
|
||||
extends:
|
||||
file: ./languagetool/compose.yaml
|
||||
service: languagetool
|
||||
|
||||
overleaf:
|
||||
extends:
|
||||
file: ./overleaf/compose.yaml
|
||||
service: overleaf
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
seaweedfs-s3:
|
||||
condition: service_healthy
|
||||
git-bridge:
|
||||
condition: service_healthy
|
||||
languagetool:
|
||||
condition: service_healthy
|
||||
|
||||
wakapi-pg:
|
||||
extends:
|
||||
file: ./wakapi-pg/compose.yaml
|
||||
service: wakapi-pg
|
||||
|
||||
wakapi:
|
||||
extends:
|
||||
file: ./wakapi/compose.yaml
|
||||
service: wakapi
|
||||
depends_on:
|
||||
wakapi-pg:
|
||||
condition: service_healthy
|
||||
|
||||
wakapi-probe:
|
||||
extends:
|
||||
file: ./wakapi-probe/compose.yaml
|
||||
service: wakapi-probe
|
||||
depends_on:
|
||||
wakapi:
|
||||
condition: service_healthy
|
||||
|
||||
nginx:
|
||||
extends:
|
||||
file: ./nginx/compose.yaml
|
||||
service: nginx
|
||||
depends_on:
|
||||
overleaf:
|
||||
condition: service_healthy
|
||||
wakapi-probe:
|
||||
condition: service_healthy
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
docker compose down nginx
|
||||
docker compose up -d nginx
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
docker compose down overleaf
|
||||
docker compose up -d overleaf
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down git-bridge
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
docker compose down languagetool
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down mongo
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down nginx
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down overleaf
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down redis
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down seaweedfs-filer
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down seaweedfs-master
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down seaweedfs-s3
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down seaweedfs-volume
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down wakapi-pg
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down wakapi-probe
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose down wakapi
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# ============================================================================
|
||||
# env.sample — template for /data_1/docker/compose/.env
|
||||
#
|
||||
# HOW TO USE: cp env.sample .env then fill in the <PLACEHOLDER> values.
|
||||
# .env is git-ignored and is the ONLY place where real secrets live; compose
|
||||
# injects these into the containers (no shell expansion in .env values —
|
||||
# ${VAR} references work between keys, $(cmd) does NOT).
|
||||
#
|
||||
# NOTE: some values are auto-maintained (marked below) — do not hand-edit
|
||||
# those or the change will be overwritten on the next up.
|
||||
#
|
||||
# THIS FILE CONTAINS NO SECRETS (it is committed). Never copy real values
|
||||
# from .env into here.
|
||||
# ============================================================================
|
||||
|
||||
# Image versions
|
||||
OVERLEAF_VERSION=ext-ce-v6.3.1
|
||||
MONGO_VERSION=9.0
|
||||
REDIS_VERSION=8.10-alpine
|
||||
GIT_BRIDE_VERSION=6.3.1
|
||||
|
||||
# Global
|
||||
# You need here the correct path (the compose.yaml is in /docker/compose_cep/overleaf)
|
||||
COMPOSE_DIRECTORY=/data_1/docker/compose
|
||||
TEX_LIVE_REPO=git.fb1.uni-bremen.de/davrot
|
||||
OVERLEAF_LEFT_FOOTER='[]'
|
||||
OVERLEAF_LOGIN_SUPPORT_TEXT="<img src=\"ub/logo.svg\" height=\"250\"></br><a href=\"https://sso.fb1.uni-bremen.de\"> University of Bremen Overleaf Wiki </a>"
|
||||
|
||||
|
||||
COMPILES_HOST_DIR=${COMPOSE_DIRECTORY}/overleaf/data/data/compiles
|
||||
COMPILES_HOST_DIR_CACHE=${COMPOSE_DIRECTORY}/overleaf/data/data/cache
|
||||
CONVERSION_SANDBOX_HOST_DIR=${COMPOSE_DIRECTORY}/overleaf/data/tmp/conversions
|
||||
OVERLEAF_OIDC_IS_ADMIN_FIELD_VALUE=davrot@uni-bremen.de
|
||||
|
||||
# Nginx SSL
|
||||
SSL_KEY_FILE=/etc/letsencrypt/live/psintern.neuro.uni-bremen.de/privkey.pem
|
||||
SSL_CERT_FILE=/etc/letsencrypt/live/psintern.neuro.uni-bremen.de/fullchain.pem
|
||||
|
||||
#S3 Buckets
|
||||
# openssl rand -base64 30 | tr -dc 'a-zA-Z0-9' | head -c 20; echo
|
||||
AWS_ACCESS_KEY_ID=<AWS_ACCESS_KEY_ID>
|
||||
# openssl rand -base64 60 | tr -dc 'a-zA-Z0-9' | head -c 40; echo
|
||||
AWS_SECRET_ACCESS_KEY=<AWS_SECRET_ACCESS_KEY>
|
||||
|
||||
# Zotero
|
||||
# (credentials from your Zotero account: Settings -> Advanced -> Credentials)
|
||||
ZOTERO_CLIENT_KEY=<ZOTERO_CLIENT_KEY>
|
||||
ZOTERO_CLIENT_SECRET=<ZOTERO_CLIENT_SECRET>
|
||||
|
||||
# Github
|
||||
# (from your GitHub App settings)
|
||||
GITHUB_SYNC_CLIENT_ID=<GITHUB_SYNC_CLIENT_ID>
|
||||
GITHUB_SYNC_CLIENT_SECRET=<GITHUB_SYNC_CLIENT_SECRET>
|
||||
|
||||
# openssl rand -hex 32; echo
|
||||
OVERLEAF_INVITE_TOKEN_SECRET=<OVERLEAF_INVITE_TOKEN_SECRET>
|
||||
|
||||
# Keycloak OpenID Connect Configuration
|
||||
OVERLEAF_OIDC_ISSUER=https://sso.fb1.uni-bremen.de/sso/realms/master
|
||||
# (from your SSO realm / client registration)
|
||||
OVERLEAF_OIDC_CLIENT_ID=<OVERLEAF_OIDC_CLIENT_ID>
|
||||
OVERLEAF_OIDC_CLIENT_SECRET=<OVERLEAF_OIDC_CLIENT_SECRET>
|
||||
OVERLEAF_OIDC_IDENTITY_SERVICE_NAME='Log in with SSO FB1 Uni Bremen'
|
||||
|
||||
# eMail Account Configuration
|
||||
# (SMTP password for the user in OVERLEAF_EMAIL_SMTP_USER)
|
||||
OVERLEAF_EMAIL_PASSWORD=<OVERLEAF_EMAIL_PASSWORD>
|
||||
OVERLEAF_EMAIL_FROM_ADDRESS=overleaf@uni-bremen.de
|
||||
OVERLEAF_EMAIL_SMTP_HOST=smtp.uni-bremen.de
|
||||
OVERLEAF_EMAIL_SMTP_PORT=465
|
||||
OVERLEAF_EMAIL_SMTP_SECURE=true
|
||||
OVERLEAF_EMAIL_SMTP_USER=overleaf
|
||||
OVERLEAF_EMAIL_SMTP_IGNORE_TLS=false
|
||||
|
||||
# Other Overleaf Configurations Configuration
|
||||
OVERLEAF_SITE_URL=https://psintern.neuro.uni-bremen.de
|
||||
OVERLEAF_ADMIN_EMAIL=overleaf@uni-bremen.de
|
||||
OVERLEAF_APP_NAME="University of Bremen -- Overleaf"
|
||||
OVERLEAF_NAV_TITLE="University of Bremen -- Overleaf"
|
||||
OVERLEAF_CUSTOM_EMAIL_FOOTER="University of Bremen -- Overleaf"
|
||||
|
||||
##########
|
||||
# Wakapi #
|
||||
##########
|
||||
|
||||
# openssl rand -base64 30 | tr -dc "a-zA-Z0-9" | head -c 24; echo
|
||||
PG_PASSWORD=<PG_PASSWORD>
|
||||
|
||||
# HOST port for the wakapi web UI + API (3000 by convention; use 3001 if taken)
|
||||
WAKA_API_PORT=3000
|
||||
|
||||
|
||||
# image pins (bump + autoupdate on upgrade)
|
||||
WAKA_API_IMAGE=ghcr.io/muety/wakapi:2.18.2
|
||||
POSTGRES_IMAGE=postgres:18-alpine
|
||||
PROBE_IMAGE=busybox:1.36
|
||||
|
||||
# overleaf container IP on this network (WAKAPI_TRUST_REVERSE_PROXY_IPS)
|
||||
# — AUTO-MAINTAINED: up.sh / up_wakapi.sh rewrite this line from
|
||||
# `docker inspect overleaf` on every (re)start-adjacent run. Leave empty:
|
||||
OVERLEAF_IP=
|
||||
|
||||
# wakapi SMTP implicit TLS (smtp.uni-bremen.de:465)
|
||||
WAKAPI_MAIL_SMTP_TLS=true
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec -it git-bridge sh
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
docker exec -it mongo sh
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
docker exec -it nginx sh
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec -it overleaf sh
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
docker exec -it redis sh
|
||||
@@ -0,0 +1 @@
|
||||
docker exec -it seaweedfs-filer sh
|
||||
@@ -0,0 +1 @@
|
||||
docker exec -it seaweedfs-master sh
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec -it seaweedfs-s3 sh
|
||||
@@ -0,0 +1 @@
|
||||
docker exec -it seaweedfs-volume sh
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec -it wakapi-pg sh
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec -it wakapi-probe sh
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
docker exec -it wakapi sh
|
||||
@@ -0,0 +1 @@
|
||||
data
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
git-bridge:
|
||||
restart: always
|
||||
image: quay.io/sharelatex/git-bridge:${GIT_BRIDE_VERSION}
|
||||
volumes:
|
||||
- ./data:/data/git-bridge
|
||||
container_name: git-bridge
|
||||
hostname: git-bridge
|
||||
expose:
|
||||
- "8000"
|
||||
environment:
|
||||
GIT_BRIDGE_API_BASE_URL: "http://overleaf:3000/api/v0/"
|
||||
GIT_BRIDGE_OAUTH2_SERVER: "http://overleaf"
|
||||
GIT_BRIDGE_POSTBACK_BASE_URL: "http://git-bridge:8000"
|
||||
GIT_BRIDGE_ROOT_DIR: "/data/git-bridge"
|
||||
user: root
|
||||
command: ["/server-pro-start.sh"]
|
||||
|
||||
healthcheck:
|
||||
# /status is git-bridge's liveness route (200 'ok'); the root path
|
||||
# needs an Overleaf session (500) and /healthz wants auth (401).
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:8000/status >/dev/null 2>&1 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
networks:
|
||||
- overleaf-network
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ngrams
|
||||
@@ -0,0 +1,31 @@
|
||||
services:
|
||||
languagetool:
|
||||
image: erikvl87/languagetool:latest
|
||||
container_name: languagetool
|
||||
hostname: languagetool
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
Java_Xms: 512m
|
||||
Java_Xmx: 2g
|
||||
langtool_languageModel: /ngrams
|
||||
|
||||
ports:
|
||||
- "127.0.0.1:32768:8010"
|
||||
|
||||
volumes:
|
||||
- ./ngrams:/ngrams:ro
|
||||
|
||||
networks:
|
||||
- overleaf-network
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --quiet --spider --timeout=5 http://localhost:8010/v2/languages 2>&1 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download & extract LanguageTool n-gram data (main set: de en es fr nl)
|
||||
# into ngrams/ next to this script.
|
||||
#
|
||||
# Sources: https://languagetool.org/download/ngram-data/
|
||||
# ngrams-de-20150819.zip (~1.7 GB)
|
||||
# ngrams-en-20150817.zip (~9.0 GB)
|
||||
# ngrams-es-20150915.zip (~1.7 GB)
|
||||
# ngrams-fr-20150913.zip (~1.8 GB)
|
||||
# ngrams-nl-20181229.zip (~1.2 GB)
|
||||
#
|
||||
# Usage: ./download_ngrams.sh
|
||||
# Re-running is safe: already-downloaded & valid zips are skipped (download is
|
||||
# resumable via curl -C).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
NGRAMS_DIR="$BASE/ngrams"
|
||||
BASE_URL="https://languagetool.org/download/ngram-data"
|
||||
|
||||
# lang -> zip filename (verified against server directory listing)
|
||||
declare -a FILES=(
|
||||
"ngrams-de-20150819.zip"
|
||||
"ngrams-en-20150817.zip"
|
||||
"ngrams-es-20150915.zip"
|
||||
"ngrams-fr-20150913.zip"
|
||||
"ngrams-nl-20181229.zip"
|
||||
)
|
||||
|
||||
command -v curl >/dev/null || { echo "ERROR: curl not found" >&2; exit 1; }
|
||||
command -v unzip >/dev/null || { echo "ERROR: unzip not found" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$NGRAMS_DIR"
|
||||
echo "Target dir: $NGRAMS_DIR"
|
||||
|
||||
failed=0
|
||||
for zip in "${FILES[@]}"; do
|
||||
url="$BASE_URL/$zip"
|
||||
dest="$NGRAMS_DIR/$zip"
|
||||
|
||||
echo "=================================================================="
|
||||
echo "$zip"
|
||||
|
||||
if [ -f "$dest" ] && unzip -t "$dest" >/dev/null 2>&1; then
|
||||
echo " [skip] already downloaded and valid: $zip"
|
||||
else
|
||||
echo " [dl] $url"
|
||||
if ! curl --fail --location --retry 3 --retry-delay 5 -C - \
|
||||
--connect-timeout 30 --progress-bar -o "$dest" "$url"; then
|
||||
echo " [FAIL] download failed for $url (partial file kept for resume: $dest)" >&2
|
||||
rm -f "$dest" # 404/error body would corrupt the name for future runs
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " [extract] -> $NGRAMS_DIR"
|
||||
if ! unzip -o -q "$dest" -d "$NGRAMS_DIR"; then
|
||||
echo " [FAIL] extraction failed for $zip" >&2
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=================================================================="
|
||||
if [ "$failed" -ne 0 ]; then
|
||||
echo "Finished with ERRORS — re-run to resume failed downloads." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "All done. Contents:"
|
||||
ls -lh "$NGRAMS_DIR"
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download & extract LanguageTool n-gram data (UNTESTED set) into ngrams/untested/
|
||||
# next to this script.
|
||||
#
|
||||
# Sources: https://languagetool.org/download/ngram-data/untested/
|
||||
# ngram-he-20150916.zip (~225 MB)
|
||||
# ngram-it-20150915.zip (~1.1 GB)
|
||||
# ngram-ru-20150914.zip (~2.3 GB)
|
||||
# ngram-zh-20150916.zip (~1.7 GB)
|
||||
#
|
||||
# NOTE: "an" (Aragonese) was requested but does NOT exist on the server
|
||||
# (checked ngram-data/, untested/, temp/, archive/ — only he/it/ru/zh are
|
||||
# offered). Kept commented out so it can be enabled if it appears later.
|
||||
#
|
||||
# Usage: ./download_ngrams_untested.sh
|
||||
# Re-running is safe: already-downloaded & valid zips are skipped (download is
|
||||
# resumable via curl -C).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
NGRAMS_DIR="$BASE/ngrams"
|
||||
UNTESTED_DIR="$NGRAMS_DIR"
|
||||
BASE_URL="https://languagetool.org/download/ngram-data/untested"
|
||||
|
||||
# zip filenames (verified against server directory listing)
|
||||
declare -a FILES=(
|
||||
"ngram-he-20150916.zip"
|
||||
"ngram-it-20150915.zip"
|
||||
"ngram-ru-20150914.zip"
|
||||
"ngram-zh-20150916.zip"
|
||||
# "ngram-an-*.zip (404 — not available on the server as of 2025/2026)"
|
||||
)
|
||||
|
||||
command -v curl >/dev/null || { echo "ERROR: curl not found" >&2; exit 1; }
|
||||
command -v unzip >/dev/null || { echo "ERROR: unzip not found" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$UNTESTED_DIR"
|
||||
echo "Target dir: $UNTESTED_DIR"
|
||||
|
||||
failed=0
|
||||
for zip in "${FILES[@]}"; do
|
||||
url="$BASE_URL/$zip"
|
||||
dest="$UNTESTED_DIR/$zip"
|
||||
|
||||
echo "=================================================================="
|
||||
echo "$zip"
|
||||
|
||||
if [ -f "$dest" ] && unzip -t "$dest" >/dev/null 2>&1; then
|
||||
echo " [skip] already downloaded and valid: $zip"
|
||||
else
|
||||
echo " [dl] $url"
|
||||
if ! curl --fail --location --retry 3 --retry-delay 5 -C - \
|
||||
--connect-timeout 30 --progress-bar -o "$dest" "$url"; then
|
||||
echo " [FAIL] download failed for $url (partial file kept for resume: $dest)" >&2
|
||||
rm -f "$dest" # 404/error body would corrupt the name for future runs
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " [extract] -> $UNTESTED_DIR"
|
||||
if ! unzip -o -q "$dest" -d "$UNTESTED_DIR"; then
|
||||
echo " [FAIL] extraction failed for $zip" >&2
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=================================================================="
|
||||
if [ "$failed" -ne 0 ]; then
|
||||
echo "Finished with ERRORS — re-run to resume failed downloads." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "All done. Contents:"
|
||||
ls -lh "$UNTESTED_DIR"
|
||||
@@ -0,0 +1,102 @@
|
||||
% LanguageTool level test — ext-ce-v6.3.1 + erikvl87/languagetool
|
||||
%
|
||||
% Section A: expected to be flagged ONLY in Picky mode (level=picky).
|
||||
% Section B: expected to be flagged in BOTH modes (default and picky).
|
||||
%
|
||||
% All sentences below were verified live against the running server
|
||||
% (2026-10-26), with the exact disabledRules the Overleaf proxy sends.
|
||||
%
|
||||
% Usage: open in Overleaf, wait ~2 s after the last edit,
|
||||
% hover a wavy underline for the message + one-click replacements.
|
||||
% Toggle Settings → Spelling and Language → LanguageTool → "Picky mode"
|
||||
% off/on and compare: section A marks should appear/disappear.
|
||||
|
||||
\documentclass{article}
|
||||
\begin{document}
|
||||
|
||||
\section*{A: Picky-only (missed at default level)}
|
||||
|
||||
% PASSIVE_VOICE_SIMPLE — passive voice
|
||||
The experiment was conducted by our group.
|
||||
|
||||
% PASSIVE_VOICE_SIMPLE — passive voice
|
||||
The error was detected by the test suite.
|
||||
|
||||
\section*{B: Both modes (default and picky)}
|
||||
|
||||
% EN_A_VS_AN — wrong article
|
||||
An sequence off independent variables.
|
||||
|
||||
% MORFOLOGIK_RULE_EN_US — spelling mistake
|
||||
You can recieve a bonus for this.
|
||||
|
||||
% MORFOLOGIK_RULE_EN_US — spelling mistake
|
||||
This is a realy interesting result.
|
||||
|
||||
% MORFOLOGIK_RULE_EN_US — spelling mistake
|
||||
He shoulda told us about the crash.
|
||||
|
||||
% AGREEMENT_SENT_START — agreement error
|
||||
The results shows a significant difference.
|
||||
|
||||
% BEEN_PART_AGREEMENT — possible agreement error
|
||||
A new paper was publish last week.
|
||||
|
||||
% THERE_S_MANY — possible agreement error
|
||||
There is three reasons for this choice.
|
||||
|
||||
% IT_IS — possible typo (missing apostrophe)
|
||||
Its time to finish the manuscript.
|
||||
|
||||
% AFFECT_EFFECT — commonly confused word
|
||||
This should have an affect on the outcome.
|
||||
|
||||
% LOOSE_LOSE — possible typo
|
||||
You will loose the key if you leave it here.
|
||||
|
||||
% MODAL_OF — possible typo
|
||||
You should of checked the configuration.
|
||||
|
||||
% MODAL_OF — possible typo
|
||||
It might of happened last night.
|
||||
|
||||
% MODAL_OF — possible typo
|
||||
We could of fixed the bug yesterday.
|
||||
|
||||
% INFORMATIONS — nonstandard word
|
||||
We need more informations about this topic.
|
||||
|
||||
% EN_A_VS_AN — wrong article (uncountable)
|
||||
He gave me a advice yesterday morning.
|
||||
|
||||
% ENGLISH_WORD_REPEAT_RULE — word repetition
|
||||
The the method was tested twice.
|
||||
|
||||
% ADMIT_ENJOY_VB — use 'ing'
|
||||
I look forward to hear from you again.
|
||||
|
||||
% TOO_TO — possible typo
|
||||
I want to too participate in this.
|
||||
|
||||
% AGREEMENT_SENT_START — agreement error
|
||||
Each of the students have passed the exam.
|
||||
|
||||
% AGREEMENT_SENT_START — agreement error
|
||||
The dog which barks is friendly.
|
||||
|
||||
% RB_RB_COMMA — intensifier comma
|
||||
We have a very very good result here.
|
||||
|
||||
% FREE_GIFT — redundant phrase
|
||||
This is a free gift for all of you.
|
||||
|
||||
% ADMIT_ENJOY_VB — use 'ing'
|
||||
The manual recommends to restart the server.
|
||||
|
||||
% ALLOW_TO — use a gerund
|
||||
The tool allows to configure the output.
|
||||
|
||||
% ALLOW_TO — use a gerund
|
||||
The server requires to restart after the change.
|
||||
|
||||
\end{document}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f git-bridge
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f languagetool
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f mongo
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f nginx
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f overleaf
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f redis
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f seaweedfs-filer
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f seaweedfs-master
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f seaweedfs-s3
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f seaweedfs-volume
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f wakapi-pg
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f wakapi-probe
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
docker compose logs -f wakapi
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
backup
|
||||
data_db
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec mongo bash -c "mongodump --out /backup/"
|
||||
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:${MONGO_VERSION}
|
||||
container_name: mongo
|
||||
hostname: mongo
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: "mongosh --quiet --eval 'rs.hello().setName ? rs.hello().setName : rs.initiate({_id: \"overleaf\",members:[{_id: 0, host:\"mongo:27017\"}]})'"
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
command: "--replSet overleaf"
|
||||
environment:
|
||||
GLIBC_TUNABLES: glibc.cpu.hwcaps=-SHSTK
|
||||
volumes:
|
||||
- ./data_db:/data/db
|
||||
- ./backup:/backup
|
||||
|
||||
networks:
|
||||
- overleaf-network
|
||||
extra_hosts:
|
||||
- "mongo:127.0.0.1"
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1,37 @@
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:stable-alpine
|
||||
container_name: nginx
|
||||
hostname: nginx
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
- ${SSL_KEY_FILE}:/certs/nginx_key.pem:ro
|
||||
- ${SSL_CERT_FILE}:/certs/nginx_certificate.pem:ro
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- /var/www/acme:/var/www/acme:ro
|
||||
- ./static:/var/www/static
|
||||
|
||||
ports:
|
||||
- "0.0.0.0:443:443"
|
||||
- "0.0.0.0:80:80"
|
||||
- ":::443:443"
|
||||
- ":::80:80"
|
||||
|
||||
environment:
|
||||
NGINX_WORKER_PROCESSES: "4"
|
||||
NGINX_WORKER_CONNECTIONS: "768"
|
||||
|
||||
networks:
|
||||
- overleaf-network
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsI --connect-timeout 10 http://localhost || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1,71 @@
|
||||
events {}
|
||||
http {
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
# ACME challenge — must come before the redirect
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/acme;
|
||||
}
|
||||
# Everything else redirects to HTTPS
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate /certs/nginx_certificate.pem;
|
||||
ssl_certificate_key /certs/nginx_key.pem;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
|
||||
server_tokens off;
|
||||
client_max_body_size 50M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://overleaf:80;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_read_timeout 3m;
|
||||
proxy_send_timeout 3m;
|
||||
proxy_hide_header Cache-Control;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
location /ub/ {
|
||||
alias /var/www/static/ub/;
|
||||
try_files $uri =404;
|
||||
types {
|
||||
image/svg+xml svg;
|
||||
}
|
||||
}
|
||||
|
||||
# Bundled Wakapi (WakaTime-compatible server) — UI + API under /wakapi.
|
||||
# Wakapi 2.18.2 contract: the app serves its routes at / and uses
|
||||
# WAKAPI_BASE_PATH=/wakapi only for the URLs/cookies it generates —
|
||||
# so the proxy must STRIP the prefix (trailing slash on proxy_pass):
|
||||
# /wakapi/login → /login, while its links stay /wakapi/… end-to-end.
|
||||
# Security: strip any client-supplied identity header — only the overleaf
|
||||
# container (pinned source IP) may use the trusted-header identity.
|
||||
# /wakapi (exact) → canonical form with trailing slash
|
||||
location = /wakapi {
|
||||
return 301 /wakapi/;
|
||||
}
|
||||
location /wakapi/ {
|
||||
proxy_pass http://wakapi:3000/;
|
||||
proxy_set_header Remote-User "";
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 39.761 39.761" width="32" height="32"><path d="M8.184 15.336c0 3.648 3.035 6.551 6.679 6.551 3.649 0 6.684-2.836 6.684-6.551v-13.5h-3.309v13.5c0 1.824-1.484 3.242-3.304 3.242-1.758 0-3.309-1.418-3.309-3.242v-13.5H8.184Z" style="fill:#d7193a;fill-opacity:1;fill-rule:nonzero;stroke:none" transform="scale(1.33333)"/><path d="M24.789 1.836v13.5c0 5.469-4.523 9.855-9.992 9.855-5.535 0-9.988-4.32-9.988-9.855v-13.5H1.5v13.5C1.5 22.695 7.508 28.5 14.797 28.5c7.359 0 13.297-5.805 13.297-13.164v-13.5z" style="fill:#d7193a;fill-opacity:1;fill-rule:nonzero;stroke:none" transform="scale(1.33333)"/></svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,2 @@
|
||||
data
|
||||
log
|
||||
@@ -0,0 +1,216 @@
|
||||
services:
|
||||
overleaf:
|
||||
image: docker.io/sharelatex/sharelatex:${OVERLEAF_VERSION}
|
||||
container_name: overleaf
|
||||
hostname: overleaf
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data:/var/lib/overleaf
|
||||
- ./log:/var/log/overleaf
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
environment:
|
||||
GIT_BRIDGE_ENABLED: true
|
||||
GIT_BRIDGE_HOST: git-bridge
|
||||
GIT_BRIDGE_PORT: 8000
|
||||
|
||||
## LanguageTool grammar check (services/web/modules/languagetool)
|
||||
## Requires the `languagetool` container reachable on the attached
|
||||
## `languagetools` network (see tools/languagetools_server_compose).
|
||||
## Picky level is the default on the client side.
|
||||
LANGUAGE_TOOL_ENABLED: true
|
||||
LANGUAGE_TOOL_HOST: languagetool
|
||||
LANGUAGE_TOOL_PORT: "8010"
|
||||
|
||||
## WakaTime/Wakapi activity tracking (PR 249 + bundled Wakapi server)
|
||||
## — see tools/wakapi_server_compose in the overleaf-cep repo).
|
||||
## Shared identity (trusted header, muety/wakapi#808): NO per-user
|
||||
## registration, NO API keys — logged-in users appear in Wakapi
|
||||
## automatically. The 'wakapi' DNS alias + this env must both be in
|
||||
## place; re-run tools/wakapi_server_compose/1_attach_overleaf_network.sh
|
||||
## after every overleaf container recreation.
|
||||
WAKATIME_SHARED_IDENTITY: true
|
||||
WAKATIME_SHARED_IDENTITY_API_URL: http://wakapi:3000/api/compat/wakatime/v1
|
||||
WAKATIME_SHARED_IDENTITY_HEADER: Remote-User
|
||||
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
OVERLEAF_REDIS_HOST: redis
|
||||
|
||||
V1_HISTORY_URL: http://overleaf:3100/api
|
||||
|
||||
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex
|
||||
|
||||
OVERLEAF_APP_NAME: ${OVERLEAF_APP_NAME}
|
||||
|
||||
ENABLED_LINKED_FILE_TYPES: project_file,project_output_file,url,zotero
|
||||
ENABLE_CONVERSIONS: true
|
||||
EMAIL_CONFIRMATION_DISABLED: true
|
||||
OVERLEAF_BEHIND_PROXY: true
|
||||
OVERLEAF_SECURE_COOKIE: true
|
||||
OVERLEAF_SITE_URL: ${OVERLEAF_SITE_URL}
|
||||
OVERLEAF_NAV_TITLE: ${OVERLEAF_NAV_TITLE}
|
||||
NAV_HIDE_POWERED_BY: true
|
||||
OVERLEAF_ADMIN_EMAIL: ${OVERLEAF_ADMIN_EMAIL}
|
||||
OVERLEAF_LOGIN_SUPPORT_TEXT: ${OVERLEAF_LOGIN_SUPPORT_TEXT}
|
||||
SHOW_UNSUPPORT_WARNING: false
|
||||
ENABLE_LOCAL_LOGIN_MASK: false
|
||||
|
||||
########################
|
||||
## eMail ##
|
||||
########################
|
||||
|
||||
OVERLEAF_EMAIL_FROM_ADDRESS: ${OVERLEAF_EMAIL_FROM_ADDRESS}
|
||||
OVERLEAF_EMAIL_SMTP_HOST: ${OVERLEAF_EMAIL_SMTP_HOST}
|
||||
OVERLEAF_EMAIL_SMTP_PORT: ${OVERLEAF_EMAIL_SMTP_PORT}
|
||||
OVERLEAF_EMAIL_SMTP_SECURE: ${OVERLEAF_EMAIL_SMTP_SECURE}
|
||||
OVERLEAF_EMAIL_SMTP_USER: ${OVERLEAF_EMAIL_SMTP_USER}
|
||||
OVERLEAF_EMAIL_SMTP_PASS: ${OVERLEAF_EMAIL_PASSWORD}
|
||||
OVERLEAF_EMAIL_SMTP_IGNORE_TLS: ${OVERLEAF_EMAIL_SMTP_IGNORE_TLS}
|
||||
OVERLEAF_EMAIL_SMTP_LOGGER: true
|
||||
OVERLEAF_CUSTOM_EMAIL_FOOTER: ${OVERLEAF_CUSTOM_EMAIL_FOOTER}
|
||||
|
||||
#########################################
|
||||
## Overleaf Filestore S3 Configuration ##
|
||||
#########################################
|
||||
|
||||
OVERLEAF_FILESTORE_BACKEND: s3
|
||||
OVERLEAF_FILESTORE_USER_FILES_BUCKET_NAME: overleaf-user-files
|
||||
OVERLEAF_FILESTORE_TEMPLATE_FILES_BUCKET_NAME: overleaf-template-files
|
||||
|
||||
OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
|
||||
OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
|
||||
OVERLEAF_FILESTORE_S3_ENDPOINT: http://s3:8333
|
||||
OVERLEAF_FILESTORE_S3_PATH_STYLE: "true"
|
||||
OVERLEAF_FILESTORE_S3_REGION: "us-east-1"
|
||||
|
||||
#########################################
|
||||
## Overleaf History S3 Configuration ##
|
||||
#########################################
|
||||
|
||||
OVERLEAF_HISTORY_BACKEND: s3
|
||||
OVERLEAF_HISTORY_PROJECT_BLOBS_BUCKET: overleaf-project-blobs
|
||||
OVERLEAF_HISTORY_CHUNKS_BUCKET: overleaf-chunks
|
||||
|
||||
OVERLEAF_HISTORY_S3_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
|
||||
OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
|
||||
OVERLEAF_HISTORY_S3_ENDPOINT: http://s3:8333
|
||||
OVERLEAF_HISTORY_S3_PATH_STYLE: "true"
|
||||
OVERLEAF_HISTORY_S3_REGION: "us-east-1"
|
||||
|
||||
########################
|
||||
## OIDC ##
|
||||
########################
|
||||
|
||||
EXTERNAL_AUTH: oidc
|
||||
OVERLEAF_OIDC_PROVIDER_ID: oidc
|
||||
OVERLEAF_OIDC_ISSUER: ${OVERLEAF_OIDC_ISSUER}
|
||||
OVERLEAF_OIDC_AUTHORIZATION_URL: ${OVERLEAF_OIDC_ISSUER}/protocol/openid-connect/auth
|
||||
OVERLEAF_OIDC_TOKEN_URL: ${OVERLEAF_OIDC_ISSUER}/protocol/openid-connect/token
|
||||
OVERLEAF_OIDC_USER_INFO_URL: ${OVERLEAF_OIDC_ISSUER}/protocol/openid-connect/userinfo
|
||||
OVERLEAF_OIDC_LOGOUT_URL: ${OVERLEAF_OIDC_ISSUER}/protocol/openid-connect/logout
|
||||
OVERLEAF_OIDC_IDENTITY_SERVICE_NAME: ${OVERLEAF_OIDC_IDENTITY_SERVICE_NAME}
|
||||
OVERLEAF_OIDC_CLIENT_ID: ${OVERLEAF_OIDC_CLIENT_ID}
|
||||
OVERLEAF_OIDC_CLIENT_SECRET: ${OVERLEAF_OIDC_CLIENT_SECRET}
|
||||
OVERLEAF_OIDC_UPDATE_USER_DETAILS_ON_LOGIN: true
|
||||
OVERLEAF_OIDC_IS_ADMIN_FIELD: email
|
||||
OVERLEAF_OIDC_IS_ADMIN_FIELD_VALUE: ${OVERLEAF_OIDC_IS_ADMIN_FIELD_VALUE}
|
||||
|
||||
OVERLEAF_LEFT_FOOTER: ${OVERLEAF_LEFT_FOOTER}
|
||||
OVERLEAF_RIGHT_FOOTER: '[{"text":"Powered by Overleaf CEP", "url":"https://github.com/yu-i-i/overleaf-cep"}]'
|
||||
|
||||
########################
|
||||
## Sandboxed Compiles ##
|
||||
########################
|
||||
|
||||
SERVER_PRO: true
|
||||
DOCKER_RUNNER: true
|
||||
|
||||
SIBLING_CONTAINERS_ENABLED: true
|
||||
DOCKER_SOCKET_PATH: /var/run/docker.sock
|
||||
|
||||
SANDBOXED_COMPILES: true
|
||||
SANDBOXED_COMPILES_SIBLING_CONTAINERS: true
|
||||
|
||||
# WARNING: CHANGE DIR!!!!
|
||||
SANDBOXED_COMPILES_HOST_DIR: ${COMPILES_HOST_DIR}
|
||||
COMPILES_HOST_DIR: ${COMPILES_HOST_DIR}
|
||||
# png2pdf: CLSI's per-project cache dir must be host-visible so the
|
||||
# png2pdf sibling container can bind-mount it (CLSI_CACHE_PATH inside
|
||||
# the container == SANDBOXED_COMPILES_HOST_DIR_CACHE on the host,
|
||||
# via the ./data -> /var/lib/overleaf mount)
|
||||
CLSI_CACHE_PATH: /var/lib/overleaf/data/cache
|
||||
SANDBOXED_COMPILES_HOST_DIR_CACHE: ${COMPILES_HOST_DIR_CACHE}
|
||||
PNG2PDF_MIN_FILE_SIZE_BYTES: 50000
|
||||
|
||||
# filestore PDF→image conversion backend (template gallery preview/thumb).
|
||||
# Choices:
|
||||
# imagemagick local `convert` (image must ship ImageMagick)
|
||||
# pdftocairo local `pdftocairo` (image must ship poppler)
|
||||
# pdftocairo-sandbox sibling container (no network, CapDrop ALL, mem cap) —
|
||||
# works on images WITHOUT any converter binaries
|
||||
CONVERTER: pdftocairo-sandbox
|
||||
CONVERSION_SANDBOX_IMAGE: sharelatex/pdftocairo:24.02
|
||||
# Scratch dir bound into the sandbox container — the same directory seen from
|
||||
# two sides (the overleaf container mounts ./data):
|
||||
CONVERSION_SANDBOX_DIR: /var/lib/overleaf/tmp/conversions
|
||||
CONVERSION_SANDBOX_HOST_DIR: ${CONVERSION_SANDBOX_HOST_DIR}
|
||||
|
||||
ALL_TEX_LIVE_DOCKER_IMAGES: ${TEX_LIVE_REPO}/texlive-full:2026.1, ${TEX_LIVE_REPO}/texlive-full:2025.1, ${TEX_LIVE_REPO}/texlive-full:2024.1, ${TEX_LIVE_REPO}/texlive-full:2023.1
|
||||
ALL_TEX_LIVE_DOCKER_IMAGE_NAMES: TeXLive 2026, TeXLive 2025, TeXLive 2024, TeXLive 2023
|
||||
TEX_LIVE_DOCKER_IMAGE: ${TEX_LIVE_REPO}/texlive-full:2026.1
|
||||
TEX_COMPILER_EXTRA_FLAGS: -shell-escape
|
||||
TEXLIVE_IMAGE_USER: www-data
|
||||
COMPILE_TIMEOUT: 599
|
||||
|
||||
#################
|
||||
# TEMPLATES #
|
||||
#################
|
||||
OVERLEAF_TEMPLATE_GALLERY: true
|
||||
OVERLEAF_NON_ADMIN_CAN_PUBLISH_TEMPLATES: false
|
||||
OVERLEAF_TEMPLATE_CATEGORIES: presentation thesis
|
||||
|
||||
TEMPLATE_PRESENTATION_NAME: Presentations
|
||||
TEMPLATE_PRESENTATION_DESCRIPTION: Templates for Beamer and other presentation formats, tailored for academic talks and lectures.
|
||||
|
||||
TEMPLATE_THESIS_NAME: Theses
|
||||
TEMPLATE_THESIS_DESCRIPTION: Templates for writing theses and dissertations, following institutional formatting and citation guidelines.
|
||||
|
||||
TEMPLATE_ALL_NAME: All templates
|
||||
TEMPLATE_ALL_DESCRIPTION: Browse a collection of all available LaTeX templates, categorized by document type, style, and purpose.
|
||||
|
||||
####################
|
||||
# Special settings #
|
||||
####################
|
||||
COMPILE_BODY_SIZE_LIMIT_MB: 50
|
||||
COMPILE_SIZE_LIMIT: 50mb
|
||||
|
||||
ENABLE_PANDOC_CONVERSIONS: true
|
||||
PANDOC_IMAGE: sharelatex/sharelatex-pandoc:${OVERLEAF_VERSION}
|
||||
|
||||
ENABLE_PNG2PDF_CONVERSIONS: true
|
||||
PNG2PDF_IMAGE: sharelatex/png2pdf:2026-06-24
|
||||
|
||||
ZOTERO_CLIENT_KEY: ${ZOTERO_CLIENT_KEY}
|
||||
ZOTERO_CLIENT_SECRET: ${ZOTERO_CLIENT_SECRET}
|
||||
|
||||
GITHUB_SYNC_ENABLED: true
|
||||
GITHUB_SYNC_CLIENT_ID: ${GITHUB_SYNC_CLIENT_ID}
|
||||
GITHUB_SYNC_CLIENT_SECRET: ${GITHUB_SYNC_CLIENT_SECRET}
|
||||
|
||||
OVERLEAF_INVITE_TOKEN_SECRET: ${OVERLEAF_INVITE_TOKEN_SECRET}
|
||||
|
||||
networks:
|
||||
- overleaf-network
|
||||
|
||||
healthcheck:
|
||||
test: bash -c "curl -fsI --connect-timeout 10 http://localhost || exit 1"
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 600s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
data
|
||||
backup
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec redis sh -c "redis-cli save && cp -f /data/dump.rdb /backup"
|
||||
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis:${REDIS_VERSION}
|
||||
container_name: redis
|
||||
hostname: redis
|
||||
restart: always
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
|
||||
start_period: 20s
|
||||
interval: 30s
|
||||
retries: 5
|
||||
timeout: 3s
|
||||
|
||||
command: --save 60 1 --loglevel warning
|
||||
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./backup:/backup
|
||||
|
||||
networks:
|
||||
- overleaf-network
|
||||
|
||||
environment:
|
||||
REDIS_AOF_PERSISTENCE: "true"
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
data
|
||||
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
seaweedfs-filer:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
container_name: seaweedfs-filer
|
||||
hostname: seaweedfs-filer
|
||||
ports:
|
||||
- "127.0.0.1:8888:8888" # filer HTTP
|
||||
- "127.0.0.1:18888:18888" # filer gRPC
|
||||
command: 'filer -master="master:9333" -ip=filer -ip.bind=0.0.0.0'
|
||||
volumes:
|
||||
- ./data:/data
|
||||
networks:
|
||||
overleaf-network:
|
||||
aliases:
|
||||
- filer
|
||||
restart: unless-stopped
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8888/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1 @@
|
||||
data
|
||||
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
seaweedfs-master:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
container_name: seaweedfs-master
|
||||
hostname: seaweedfs-master
|
||||
ports:
|
||||
- "127.0.0.1:9333:9333" # master HTTP
|
||||
- "127.0.0.1:19333:19333" # master gRPC
|
||||
command: "master -ip=master -ip.bind=0.0.0.0 -metricsPort=9327"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
networks:
|
||||
overleaf-network:
|
||||
# classic seaweedfs names (master:9333 etc.) must resolve on the shared
|
||||
# network while the container keeps its seaweedfs-* name.
|
||||
aliases:
|
||||
- master
|
||||
restart: unless-stopped
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9333/cluster/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
seaweedfs-s3:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
container_name: seaweedfs-s3
|
||||
hostname: seaweedfs-s3
|
||||
ports:
|
||||
- "127.0.0.1:8333:8333" # S3 API
|
||||
command: 's3 -filer="filer:8888" -ip.bind=0.0.0.0'
|
||||
|
||||
environment:
|
||||
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
|
||||
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
aliases:
|
||||
- s3
|
||||
restart: unless-stopped
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -S -o- http://localhost:8333/ 2>&1 | grep -q 'HTTP/'"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1 @@
|
||||
data
|
||||
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
seaweedfs-volume:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
container_name: seaweedfs-volume
|
||||
# no host port mapping: volume is only reached inside the network as
|
||||
# volume:8080 (host 8080 is taken by another service) — gRPC stays mapped.
|
||||
ports:
|
||||
- "127.0.0.1:18080:18080" # volume gRPC
|
||||
command: 'volume -mserver="master:9333" -ip=volume -ip.bind=0.0.0.0 -port=8080 -dir=/data -max=0'
|
||||
volumes:
|
||||
- ./data:/data
|
||||
networks:
|
||||
overleaf-network:
|
||||
aliases:
|
||||
- volume
|
||||
restart: unless-stopped
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/status"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
|
||||
networks:
|
||||
overleaf-network:
|
||||
external: true
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# up.sh — cold/hot start of the whole stack, wakapi-safe ordering:
|
||||
# 1) wakapi-pg up, drop the normalization trigger (if any) so wakapi's
|
||||
# pending startup DDL can never be blocked by it (SQLSTATE 0A000 brick —
|
||||
# see 0_ensure_local_auth_type.sh header).
|
||||
# 2) rest of the stack up (overleaf included, so its IP becomes known).
|
||||
# 3) pin OVERLEAF_IP (was valid only while overleaf was running before).
|
||||
# 4) recreate wakapi — automatically, only if the pin changed it.
|
||||
# 5) re-apply trigger + backfill (no-op if the DB is already normalized).
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# 1) pg first + trigger removed for the startup window
|
||||
docker compose up -d wakapi-pg
|
||||
bash 0_ensure_local_auth_type.sh preflight
|
||||
|
||||
# 2) everything else (.env may carry a stale OVERLEAF_IP; corrected in 3)
|
||||
docker compose up -d
|
||||
|
||||
# 3) pin OVERLEAF_IP to the now-known overleaf container IP (single-IP
|
||||
# least-privilege trusted-source model for wakapi)
|
||||
ip="$(docker inspect -f '{{(index .NetworkSettings.Networks "overleaf-network").IPAddress}}' overleaf)"
|
||||
grep -q '^OVERLEAF_IP=' .env && sed -i "s/^OVERLEAF_IP=.*/OVERLEAF_IP=$ip/" .env || echo "OVERLEAF_IP=$ip" >> .env
|
||||
echo "pinned OVERLEAF_IP=$ip"
|
||||
|
||||
# 4) pick up the new pin if it changed (no-op otherwise; heartbeats in the
|
||||
# 2→4 window may be rejected — fire-and-forget client, self-heals)
|
||||
docker compose up -d wakapi
|
||||
|
||||
# 5) normalization back on (waits until wakapi serves)
|
||||
bash 0_ensure_local_auth_type.sh apply
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d git-bridge
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d languagetool
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d mongo
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d nginx
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d overleaf
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d redis
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d seaweedfs-filer
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d seaweedfs-master
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d seaweedfs-s3
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d seaweedfs-volume
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d wakapi-pg
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose up -d wakapi-probe
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# up_wakapi.sh — start/re-warm only the wakapi pair (hot-stack tooling:
|
||||
# expects overleaf to be running for the IP pin). Same trigger-safe cycle as
|
||||
# up.sh: preflight drop -> up -> apply.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
docker compose up -d wakapi-pg
|
||||
bash 0_ensure_local_auth_type.sh preflight
|
||||
|
||||
ip="$(docker inspect -f '{{(index .NetworkSettings.Networks "overleaf-network").IPAddress}}' overleaf)"
|
||||
grep -q '^OVERLEAF_IP=' .env && sed -i "s/^OVERLEAF_IP=.*/OVERLEAF_IP=$ip/" .env || echo "OVERLEAF_IP=$ip" >> .env
|
||||
echo "pinned OVERLEAF_IP=$ip"
|
||||
|
||||
docker compose up -d wakapi
|
||||
bash 0_ensure_local_auth_type.sh apply
|
||||
@@ -0,0 +1,2 @@
|
||||
backup
|
||||
data
|
||||
+1
@@ -0,0 +1 @@
|
||||
docker exec wakapi-pg bash -c "pg_dump -U wakapi -d wakapi -F c -f /backup/backup.sql"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user