Immich Share Sync
An ephemeral Qt 6 web service that compares two Immich public shares by SHA-1 checksum and streams missing originals in either direction. It never deletes assets and never writes transfer data to disk.
How it works
- A
QHttpServerserves an embedded vanilla HTML/CSS/JavaScript frontend. - The same HTTP listener upgrades
/wswith Qt WebSockets. - Each short-lived WebSocket owns exactly one job, one
SyncSession, twoImmichClientinstances, and theirQNetworkAccessManagerobjects. - Link inspection tries Immich's share key and custom-slug authentication forms. Current password-protected shares use
POST /api/shared-links/login; Qt's cookie jar retains the short-lived share cookie for that socket session. - The service reads
QJsonValue/QJsonObjectresponses and compares the base64-encoded SHA-1 checksums in Immich's shared-link response. - Each missing original is a sequential
QNetworkReplyused as theQIODevicebody of the destination multipart request. A 1 MiB source read buffer provides backpressure; there is no temporary file or whole-asset buffer. - The browser sends one complete job request immediately after connecting. The service closes the WebSocket when that inspection or synchronization finishes.
- Closing the WebSocket early destroys the session and aborts every active network reply. No inspection ID, plan, credentials, or recovery state survives the connection.
The destination upload deliberately omits Immich's x-immich-checksum optimization. That optimization returns duplicates before Immich associates an already-owned asset with the destination shared album. The normal duplicate path both detects the checksum and adds the existing asset to the share.
Build and run
Requirements: CMake 3.28+, a C++23 compiler, and Qt 6.9+ with Core, Network, HttpServer, and WebSockets.
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
PORT=8090 ./build/immich-sync
Open http://localhost:8090.
WebSocket protocol
All messages are JSON. Each WebSocket accepts exactly one complete job request and closes after its terminal response. A client that does not send a request within 10 seconds is disconnected. Passwords are accepted only in client messages and are never placed in URLs or echoed in status responses.
Inspect two links:
{
"type": "inspect",
"left": {"url": "https://one.example/share/key", "password": ""},
"right": {"url": "https://two.example/share/key", "password": ""}
}
The service emits granular side-status messages. Stages include connecting, authenticating, password-required, ready, and error. Once both sides resolve, an inspection response includes public share details, upload/download permissions, missing counts, and the allowed left-to-right, right-to-left, and bidirectional options. The service then closes the inspection socket.
To synchronize, open a new WebSocket and send both shares again along with one of the directions offered by inspection:
{
"type": "sync",
"direction": "bidirectional",
"left": {"url": "https://one.example/share/key", "password": ""},
"right": {"url": "https://two.example/share/key", "password": ""}
}
The synchronization job repeats inspection so its checksum plan and permissions reflect current Immich state. Status messages include the inspection events followed by sync-status, asset-status, and throttled asset-progress events. The service closes the socket after completion; close it from the client to abort and discard the whole session immediately.
Container and Kubernetes
docker build -t registry.brunner.ninja/feedc0de/immich-sync:latest .
docker run --rm -p 8090:8090 registry.brunner.ninja/feedc0de/immich-sync:latest
./install.sh
The Kubernetes manifest follows the neighboring brunner-ninja and visual-studio-code layout. It assumes the image name and immich-sync.brunner.ninja hostname shown in the manifest. It enables the existing Authentik Traefik middleware because accepting arbitrary server URLs creates an SSRF/bandwidth-abuse surface; remove that annotation only if intentionally exposing the service publicly.
Current scope
- Assets without a checksum are ignored by planning.
- Metadata processing on the destination is left to Immich after upload.
- Sidecar files and Live Photo pairing are not reconstructed yet; the primary original asset is transferred.
- Immich serves the edited rendition for edited assets through a shared link even when the original endpoint is requested. Such an asset can acquire a different destination checksum and may be proposed again on a later, stateless run. This cannot be fully resolved with current shared-link permissions alone.
- TLS certificates must be valid. The service never ignores SSL errors.