aboutsummaryrefslogtreecommitdiffstats

overleaf-ce-sync

Español

Two-way "git-like" sync for a self-hosted Overleaf Community Edition.

The native Git integration (git-bridge) is a Server Pro / paid feature and is not shippable with CE — the Snapshot API and OAuth2 server it needs live in closed-source modules. This tool gives you the next best thing: pull a project to disk, version it with your own git, and push changes back.

It talks to the same HTTP endpoints the web UI uses. The plain-HTTP login that is broken on overleaf.com (CAPTCHA) works fine on CE because CE has no CAPTCHA.

Install

With pipx (isolated venv, overleaf-ce-sync on PATH):

git clone https://git.all.ar/pub/overleaf-ce-sync.git
cd overleaf-ce-sync
pipx install -e .          # -e = editable: source edits apply with no reinstall

The only dependency is requests; Python 3.8 or newer.

If pipx warns that ~/.local/bin isn't on PATH, run pipx ensurepath and open a new shell.

Usage

# 1. log in once per host (cookie saved to ~/.config/overleaf-ce, 0600)
overleaf-ce-sync login --host overleaf.example.com --email you@example.com

# 2. find your project id
overleaf-ce-sync list

# 3. clone it into a git repo
overleaf-ce-sync clone <project-id> mybook
cd mybook

# 4. normal loop
overleaf-ce-sync pull          # bring down remote changes (overwrites local)
#   ...edit, git commit...
overleaf-ce-sync push          # upload locally-changed files

After the first pull/clone, the host + project id are remembered in .overleaf-ce.json, so inside the project dir you can just run overleaf-ce-sync pull / overleaf-ce-sync push.

How it works

The sync is anchored to git commits, so push publishes only what you have committed — never your working-tree scratch edits.

  • pull → GET /Project/<id>/download/zip, extract over the working tree, then make a "sync commit" and remember it as last_sync_commit in .overleaf-ce.json.
  • push → upload the committed state (HEAD). It diffs last_sync_commit..HEAD, uploads each changed file's committed bytes (git show HEAD:<path>) via POST /Project/<id>/upload (overwrites by name), then advances last_sync_commit to HEAD. Uncommitted edits are ignored.
  • The upload endpoint needs the project's root folder id. It's discovered once over socket.io xhr-polling (plain HTTP — works behind any reverse proxy; no websocket upgrade needed) via the joinProjectResponse event, and cached in .overleaf-ce.json.

Limitations / notes

  • push only sends committed changes. Edit, git commit, then push. A file with uncommitted changes is skipped (you'll get a heads-up note).
  • Deletions are not propagated. Files removed in your commits are reported but NOT deleted on Overleaf (safer default). Delete them in the web UI; use pull --prune to mirror remote deletions into your working tree.
  • pull overwrites the working tree and refuses if you have uncommitted changes (pass --force to override). It auto-commits the pulled state, so your history interleaves "overleaf pull" sync commits with your own.
  • Binary vs doc: Overleaf decides per file; re-uploading creates new history versions, so push only sends files whose committed content changed.
  • Don't log out in the browser session you used — Overleaf may revoke the cookie.

Fallback: root folder id without socket.io

If the xhr-polling discovery fails against your setup, fetch the id once from Mongo on the server and pass it with --root-folder-id (it gets cached afterwards):

docker compose exec mongo mongosh sharelatex --quiet --eval \
  'printjson(db.projects.findOne({_id:ObjectId("<project-id>")},{ "rootFolder._id":1}))'

overleaf-ce-sync push --root-folder-id <root-folder-id>

License

MIT