Simple file versioning and locking for game dev teams. SVN-inspired. Works with Unity, Godot, Unreal — any binary files.
Each member keeps a local working copy on their own disk; the server is a dumb blob + metadata store that never reads anyone's filesystem. The desktop app scans your folder natively, diffs it against the server, and pushes/pulls per file.
- File locking — nobody overwrites your work (HTTP 423 on locked uploads, auto-unlock after a timeout)
- Version history — every upload is a new version; revert to any previous one
- Push / Pull — client-side sync of your local folder against the server, with delete propagation in both directions (a removed file stays removed instead of resurrecting)
- Resumable uploads — a dropped connection resumes a multi-GB asset from the last received byte instead of restarting (tus-lite session flow)
- Conflict detection — stale uploads are rejected (HTTP 409) until you force or pull; each file carries a sync base so changes are attributed to the side that actually moved
- Unity-aware locking — auto-pairs an asset with its
.meta, locks dependencies as a group, and suggests GUID-referenced assets to lock together; warns when a scene/prefab is edited without a lock - Ignore patterns — Unity/Godot/Unreal junk skipped by default, extendable via
.crowsyncignore - Real-time notifications — WebSocket events when teammates lock/upload/revert
- Activity log — full audit trail per project
- Riso-styled desktop UI — custom dark/light themeable window chrome; collapsible side panels
docker-compose up -dServer runs on http://localhost:8001, data lives in the named volume crowsync-data.
Note: to allow registering members after the first one, the server needs
CROWSYNC_ADMIN_TOKENset in its environment. Add it to theenvironment:section ofdocker-compose.yml— a.envfile next to the compose file is not passed into the container by itself.
pip install -r server/requirements.txt
python -m server.mainServes on http://localhost:8001 (auto-reload enabled).
npm install
npm run devOpens at http://localhost:1420. Browser mode has no native filesystem access — scan/push/pull are disabled and the UI degrades to read-only-ish (lock, unlock, manual upload/download, history and activity still work).
npm run tauri devThe server is pure Python — no compiled dependencies — so it runs natively on all three OSes, not just Linux. Pick whichever fits:
- Docker (Linux, or Docker Desktop on Windows/macOS) —
docker-compose up -d. Most reproducible; same image everywhere. - Helper script (creates a venv, installs deps, starts the server):
./scripts/run-server.sh # macOS / Linux scripts\run-server.bat # Windows
- Plain Python (Python 3.11+):
pip install -r server/requirements.txt python -m server.main
- Standalone binary — download
crowsync-server-<os>-x64from a GitHub Release (built by the release workflow); no Python install needed. Run it directly; it honours the sameCROWSYNC_*env vars below.
| Variable | Default | Purpose |
|---|---|---|
CROWSYNC_PORT |
8001 |
HTTP port |
CROWSYNC_DB_PATH |
./crowsync.db |
SQLite database file |
CROWSYNC_STORAGE_ROOT |
./storage |
Versioned blob storage (overridable at runtime via the storage_root setting) |
CROWSYNC_ADMIN_TOKEN |
(empty) | Required as X-Admin-Token header to register members once the first member exists. Generate with python -c "import secrets; print(secrets.token_hex(32))" |
CROWSYNC_OPEN_REGISTRATION |
0 |
1/true = trusted-LAN mode: anyone on the network registers (or recovers a lost key) with just a name, no admin token. For firewalled teams only — never a public server. Destructive ops still require the admin token. |
- Every API request needs two headers:
X-Member-Name+X-Api-Key. - The API key is generated by the server on
POST /membersand returned only then — store it. Only a SHA-256 hash of the key is kept in the database, so a DB leak doesn't expose keys. - The first member registers without any token (bootstrap mode). Every
following registration requires the
X-Admin-Tokenheader matchingCROWSYNC_ADMIN_TOKEN. - Open LAN mode (
CROWSYNC_OPEN_REGISTRATION=1) skips the token entirely so anyone on a trusted/firewalled network can register or recover by name. The setup screen detects this (via/health) and hides the admin-token field. - Re-posting
/memberswith an existing name (with a valid admin token) issues a fresh API key — the recovery path for a new machine. Because only the hash is stored the old key can't be returned, so any previous machine's key stops working after a recovery. The admin token is a master key; keep it secret. - Destructive operations — deleting a project (
DELETE /projects/{id}), deactivating a member (DELETE /members/{id}) and changing server settings (PUT /settings) require theX-Admin-Tokenheader too. - WebSocket auth is sent in the first message after connecting (not the URL), so keys don't leak into access logs.
API keys cross the network in the X-Api-Key header / WS auth message. Put the
server behind a TLS-terminating reverse proxy so that link is encrypted — a
ready-to-use Caddy config is included:
# 1. set your domain (and ideally an ACME email) in a sibling .env:
# CROWSYNC_DOMAIN=crowsync.example.com
# CROWSYNC_ACME_EMAIL=ops@example.com
# 2. start backend + Caddy (auto HTTPS via Let's Encrypt):
docker compose -f docker-compose.yml -f docker-compose.tls.yml up -dThen point the desktop app at https://your-domain (no :8001). For local
testing leave CROWSYNC_DOMAIN unset — it defaults to localhost, where Caddy
issues a locally-trusted cert. The TLS compose file stops publishing :8001, so
the backend is only reachable through Caddy. See AUDIT.md S3 and .env.example.
- Server URL:
http://YOUR-SERVER-IP:8001 - Name: your member name (the API key is fetched and stored automatically on first registration)
Defaults cover Unity (Library/, Temp/…), Godot (.godot/…), Unreal
(Binaries/, Saved/…) and common junk — see DEFAULT_IGNORE_PATTERNS in
server/file_manager.py (served to clients via GET /ignore-patterns).
Per-project additions go into a .crowsyncignore file in your local project
root (gitignore-like syntax, see .crowsyncignore.example).
server/ Python FastAPI backend — SQLite + on-disk versioned blobs
src/ React 19 + Vite + Tailwind v4 frontend
src-tauri/ Tauri v2 Rust shell + native scan/upload/download commands
See CLAUDE.md for the full architecture notes and AUDIT.md for the current
list of known issues and limitations.
- Backend: Python FastAPI + SQLite (raw SQL, no ORM)
- Frontend: Tauri v2 + React 19 + TypeScript + Tailwind CSS v4
- Native layer: Rust (walkdir + MD5 manifest scan, streaming transfers via reqwest)
- Real-time: WebSocket
MIT — see LICENSE.
