A real-time synchronization bridge between Relay Server collaborative documents and Git repositories, enabling automatic version control and backup for collaborative workspaces.
Relay Server Git Sync monitors collaborative documents in Relay Server over websocket subscriptions and automatically maintains synchronized copies in local Git repositories. When users edit documents in Relay Server, the changes are reflected in the local file system and committed to Git with timestamps.
- Real-time synchronization - Websocket subscriptions receive live document updates
- Automatic Git versioning - Timestamped commits preserve change history
- Conflict-free operations - Smart handling of renames, moves, and deletions
- Persistent state management - Maintains sync integrity across restarts
- Team collaboration backup - Preserve collaborative work in version control
- Integration workflows - Connect collaborative editing to existing Git-based processes
- Document archival - Maintain historical records of collaborative sessions
- Git Sync subscribes to configured Shared Folders over Relay websocket connections
- Subdocument index heads identify which documents actually changed
- Folder metadata is diffed for incremental creates, renames, moves, and deletes
- Only changed document bodies are fetched and written locally
- Changes are automatically committed to Git with timestamps
Use the provided docker container as a starting point.
docker pull docker.system3.md/relay-git-sync:latest
Create git_connectors.toml first. This file is the sync plan: each entry maps one
Shared Folder to one local Git repository. Add url when that repository should also
push to a remote.
[relay]
url = "https://your-relay-server.com"
id = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
[[git_connector]]
shared_folder_id = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
url = "git@github.com:example/repository.git"
branch = "main"
remote_name = "origin"
prefix = "" # Optional subdirectory inside the repository
# Local-only snapshots: commits are kept in the local Git repo and are not pushed
[[git_connector]]
shared_folder_id = "12345678-1234-5678-9abc-123456789abc"
prefix = "shared"Add one [[git_connector]] per Shared Folder you want Git Sync to mirror. Omit url
for local-only snapshots.
Websocket listening is the default transport. Git Sync opens an outbound connection to Relay Server and receives change events there. This is the simplest deployment because Git Sync does not need to accept inbound connections from Relay Server.
Git Sync sends websocket keepalive pings and uses Relay's subdocument index for reconnect catch-up. Index heads are persisted locally, so reconnecting compares the known documents cheaply and fetches only documents whose heads advanced.
Webhook transport is available when you intentionally deploy Git Sync behind a stable URL that Relay Server can reach. Webhooks avoid keeping a long-lived listener connected and fit deployments that wake on inbound HTTP requests, but they require network routing from Relay Server to Git Sync, TLS, and webhook authentication.
To enable webhook transport, add the exact Git Sync webhook endpoint:
[webhook]
url = "https://your-git-sync-server.com/webhooks"git_connectors.toml stores the webhook endpoint URL. The setup output generates the
matching WEBHOOK_SECRET for the Git Sync runtime environment.
The Relay API key is not scoped to a Shared Folder; it is scoped to the relay prefix, so one API key covers the configured Shared Folders for that relay.
Generate the Relay auth setup after the TOML file exists:
uv run git-sync generate-authgit-sync setup is the same command. Git Sync reads [relay].url, [relay].id,
and optional [webhook].url from <data-dir>/git_connectors.toml. Use
-c ./path/to/git_connectors.toml only when the file is somewhere else.
The setup output gives you:
- a
[[auth]]block for Relay Serverrelay.toml - a
RELAY_SERVER_API_KEYvalue for Git Sync - if
[webhook].urlis configured, a[[webhooks]]block andWEBHOOK_SECRET
Runtime API keys are only read from RELAY_SERVER_API_KEY; do not pass them as command
arguments when running the server or sync command.
API keys do not expire unless you pass --expires-days.
Use --json for machine-readable output.
Inspect an existing API key with uv run git-sync inspect-token "$RELAY_SERVER_API_KEY".
Pass --webhook-url https://your-git-sync-server.com/webhooks only to override
[webhook].url.
When [webhook].url is configured, the setup output generates a plain shared webhook
secret. Add the generated [[webhooks]] block to Relay Server relay.toml, and set the
generated WEBHOOK_SECRET value in the Git Sync deployment.
If WEBHOOK_SECRET is unset, /webhooks is not mounted.
Use this only when your webhook delivery provider signs requests and gives you a whsec_*
signing secret. Native Relay Server webhooks use the generated shared secret above.
Install the svix extra before using that secret format:
uv sync --extra svixThen configure the signing secret:
export WEBHOOK_SECRET=whsec_your_signing_secretSSH keys must be provided via the SSH_PRIVATE_KEY environment variable.
First, generate an SSH key pair externally:
ssh-keygen -t ed25519 -f git_sync_key -N ""Then set the environment variable:
export SSH_PRIVATE_KEY="$(cat git_sync_key)"View the public key from the private key:
uv run git-sync ssh show-pubkeyAdd the public key to your Git hosting service (GitHub, GitLab, etc.) as a deploy key with write permissions.
GitHub, GitLab.com, and Bitbucket Cloud SSH remotes automatically fetch the providers'
published host keys. For offline deployments, custom hosts, or self-hosted Git hosts,
add host keys to top-level known_hosts in git_connectors.toml:
known_hosts = [
"git.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleHostKey",
]For custom hosts, copy the provider's published known_hosts entries or get host key
lines with ssh-keyscan <git-host>, then verify the fingerprint against your Git
provider before deploying. Explicit known_hosts entries override provider fetching
for that host. HTTPS Git remotes and local-only connectors do not use SSH host keys.
Start the server:
# Set required environment variables
export RELAY_SERVER_API_KEY=your-server-api-key # Generated by the setup output
export WEBHOOK_SECRET=your-webhook-secret # Generated by the setup output
export SSH_PRIVATE_KEY="$(cat git_sync_key)" # Required for Git push operations
export RELAY_GIT_DATA_DIR=/path/to/data # Contains git_connectors.toml
# Start the server
python app.py --port 8000 --commit-interval 10The Docker image treats container command arguments as app arguments:
docker run \
-e RELAY_SERVER_API_KEY="$RELAY_SERVER_API_KEY" \
-e WEBHOOK_SECRET="$WEBHOOK_SECRET" \
-e SSH_PRIVATE_KEY="$SSH_PRIVATE_KEY" \
-v "$PWD/git_connectors.toml:/data/git_connectors.toml:ro" \
docker.system3.md/relay-git-sync:latest \
--data-dir /dataTo generate auth from the container, omit RELAY_SERVER_API_KEY. The container reads
git_connectors.toml, prints the setup output, and exits:
docker run --rm \
-v "$PWD/git_connectors.toml:/data/git_connectors.toml:ro" \
docker.system3.md/relay-git-sync:latest \
--data-dir /data--port: HTTP server port (default: 8000)--commit-interval: Git commit interval in seconds (default: 10)--relay-server-url: Relay server URL override--relay-id: Relay UUID override-c, --config: Path togit_connectors.tomlwhen it is not<data-dir>/git_connectors.toml--data-dir: Data storage directory (default: fromRELAY_GIT_DATA_DIRenv var or current directory)--webhook-secret: Optional webhook secret (or setWEBHOOK_SECRET); enables/webhooks--websocket-reconnect-delay: Seconds to wait before reconnecting Relay websocket listeners (default: 5)
You can also perform one-time syncs within your container by using the CLI:
export RELAY_SERVER_API_KEY=your-server-api-key
uv run git-sync sync --folder-id <folder-uuid>The server organizes data as follows:
data-dir/
├── repos/
│ └── <relay-id>/
│ └── <folder-id>/ # Git repository for each folder
└── state/
└── <relay-id>/
├── document_hashes.json # Change tracking
├── subdoc_heads.json # Persisted Relay subdocument index heads
├── shared_folders.json # Folder metadata
└── local_state.json # File state per folder
The server provides public API endpoints for management. The webhook endpoint is only mounted when WEBHOOK_SECRET is configured.
Interactive Documentation: Visit /docs when the server is running for interactive Swagger UI documentation.
OpenAPI Specification: See openapi.yaml for complete API documentation with request/response schemas, authentication details, and examples.
Key Endpoints:
GET /health- Health check (public)GET /api/pubkey- SSH public key retrieval (public)GET /docs- Interactive API documentation (public)GET /openapi.yaml- OpenAPI specification (public)POST /webhooks- Optional webhook ingestion, mounted only whenWEBHOOK_SECRETis set