# overleaf-ce-sync [Español](README.es.md) 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](https://pipx.pypa.io) (isolated venv, `overleaf-ce-sync` on PATH): ```bash 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 ```bash # 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 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//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:`) via `POST /Project//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): ```bash docker compose exec mongo mongosh sharelatex --quiet --eval \ 'printjson(db.projects.findOne({_id:ObjectId("")},{ "rootFolder._id":1}))' overleaf-ce-sync push --root-folder-id ``` ## License MIT