# Getting the Android app onto your server ThoughtSync's server hands out the Android client it syncs with. Once an APK is in place, anyone with an account on that server can download it from **Account → Linked devices**, and linked phones can update themselves from it. This is deliberate rather than incidental. The build is not on an app store and the Fabled-Git instance is private, so a release page is no use to a self-hoster — but the server holding their notes is something they already trust and already reach. It also keeps the two in step: client and server negotiate a sync protocol version before linking, so a server that serves the client cannot hand out a phone it cannot talk to. ## Where it goes Two files, both required, in `/var/thoughtsync/client/`: | File | What it is | | --- | --- | | `thoughtsync.apk` | the client | | `thoughtsync-android.json` | `{version_name, version_code, size, sha256}` | The sidecar exists because an APK keeps its version in a binary manifest that needs the Android build tools to read. CI writes it beside the APK, where the real values are already known. `/var/thoughtsync` is the same volume that holds attachments (`Config.DATA_DIR`), so a build dropped there survives container recreation. Nothing is baked into the image: the APK is ~55 MiB and an install that never touches Android should not carry it. ## Putting a build there Both files are published to the rolling `dev` release on every green Android build. From the machine running the server: ```sh REPO=https://git.fabledsword.com/bvandeusen/thoughtsync TOKEN=... # a Fabled-Git token with read access; the instance is private for f in thoughtsync.apk thoughtsync-android.json; do curl -fsSL -H "Authorization: token $TOKEN" \ -o "/tmp/$f" "$REPO/releases/download/dev/$f" done # Into the app container's volume. Copy the sidecar LAST: the server treats a # sidecar that does not match the APK beside it as "no client at all", so a # half-finished copy advertises nothing rather than advertising a lie. docker compose cp /tmp/thoughtsync.apk app:/var/thoughtsync/client/ docker compose cp /tmp/thoughtsync-android.json app:/var/thoughtsync/client/ ``` `docker compose cp` creates `/var/thoughtsync/client/` if it does not exist. ## Checking it took ```sh curl -s http://localhost:5000/api/client/android ``` A server with a client answers with the version, size and digest. A server without one answers `404` — and the download card in the web UI is hidden rather than offering a button that fails. ## What happens if you get it wrong - **Only the APK, no sidecar** — the server reports no client. It cannot state a version it has no way to read. - **Mismatched pair** (new APK, old sidecar) — the server reports no client, because the recorded size does not match the file. It will not serve one build while describing another. - **Neither** — the server reports no client, the UI hides the card, and `/api/client/android` returns 404. This is the ordinary state of a server whose owner does not use Android, and nothing about it is an error. ## Signing, and why replacing the APK is safe Every release build is signed with the same key, so a phone can install a newer one straight over the old one and keep its notes. That was not true before August 2026 — builds until then were signed with a throwaway key per CI run, and each install required uninstalling the last (Scribe #2803). If you are carrying a build from before that, expect to uninstall once more and sync anything you care about first.