# 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 `QHttpServer` serves an embedded vanilla HTML/CSS/JavaScript frontend. - The same HTTP listener upgrades `/ws` with Qt WebSockets. - Each short-lived WebSocket owns exactly one job, one `SyncSession`, two `ImmichClient` instances, and their `QNetworkAccessManager` objects. - 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`/`QJsonObject` responses and compares the base64-encoded SHA-1 checksums in Immich's shared-link response. - Each missing original is a sequential `QNetworkReply` used as the `QIODevice` body 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. ```sh 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`. After both share URLs are valid, starting a job updates the browser fragment in the form `#left=&right=`. Reloading or bookmarking that URL restores both fields and automatically starts a new inspection. Passwords are intentionally excluded from the fragment and must be entered again for protected shares. ## 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: ```json { "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: ```json { "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 ```sh 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. ## Continuous delivery The Gitea Actions workflow in `.gitea/workflows/container.yml` builds and pushes `registry.brunner.ninja/feedc0de/immich-sync`. Every pushed commit receives an immutable tag containing its full Git SHA. A commit pushed to `main` receives both its SHA tag and `latest` from the same image build. The workflow deliberately loads the completed image into the Docker engine and pushes it with a separate plain `docker push`. It does not request provenance attestations, SBOM manifests, or OCI referrers, retaining compatibility with the self-hosted Quay registry and with Gitea runners whose default builder uses BuildKit's container driver. The workflow expects these Gitea Actions repository secrets: - `QUAY_USERNAME`: the complete Quay robot account name, including the `feedc0de+` prefix. - `QUAY_TOKEN`: the robot account token. Give the robot account `Write` permission only on the `feedc0de/immich-sync` repository. Public image pulls do not use these credentials and do not require a Gitea secret. ## 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.