feat/wakatime integration- #249 plus tools/reference_stack_compose with wakapi

This commit is contained in:
David Rotermund
2026-10-27 00:20:07 +00:00
parent a99a12887c
commit 6f2cf1c060
123 changed files with 3344 additions and 4 deletions
+4
View File
@@ -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
+89
View File
@@ -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
View File
@@ -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"
+22
View File
@@ -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
+180
View File
@@ -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`
+34
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+44
View File
@@ -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
+102
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
docker compose down nginx
docker compose up -d nginx
+3
View File
@@ -0,0 +1,3 @@
docker compose down overleaf
docker compose up -d overleaf
+2
View File
@@ -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
+105
View File
@@ -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
View File
@@ -0,0 +1 @@
docker exec -it git-bridge sh
+1
View File
@@ -0,0 +1 @@
docker exec -it mongo sh
+1
View File
@@ -0,0 +1 @@
docker exec -it nginx sh
+1
View File
@@ -0,0 +1 @@
docker exec -it overleaf sh
+1
View File
@@ -0,0 +1 @@
docker exec -it redis sh
+1
View File
@@ -0,0 +1 @@
docker exec -it seaweedfs-filer sh
+1
View File
@@ -0,0 +1 @@
docker exec -it seaweedfs-master sh
+1
View File
@@ -0,0 +1 @@
docker exec -it seaweedfs-s3 sh
+1
View File
@@ -0,0 +1 @@
docker exec -it seaweedfs-volume sh
+1
View File
@@ -0,0 +1 @@
docker exec -it wakapi-pg sh
+1
View File
@@ -0,0 +1 @@
docker exec -it wakapi-probe sh
+1
View File
@@ -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
View File
@@ -0,0 +1,2 @@
docker compose logs -f git-bridge
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f languagetool
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f mongo
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f nginx
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f overleaf
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f redis
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f seaweedfs-filer
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f seaweedfs-master
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f seaweedfs-s3
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f seaweedfs-volume
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f wakapi-pg
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f wakapi-probe
+2
View File
@@ -0,0 +1,2 @@
docker compose logs -f wakapi
@@ -0,0 +1,2 @@
backup
data_db
+1
View File
@@ -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
View File
@@ -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
+31
View File
@@ -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
View File
@@ -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