Android / Build, or is the channel already serving this? (push) Successful in 2s
Android / Kotlin + Rust (APK) (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
`:<git-sha>` is on `main` only now — a sha tag per dev push was a rollback target nobody had ever pulled — and `:<version>` never existed as an image tag after rule 145 was narrowed. Both were still documented. `docs/android-distribution.md` also said `:dev`, `:latest` and `:<version>` all ship a client, which is now two-thirds true and misses the more useful fact: the channel IS the image you run, so a stable server serves a stable client. Worth saying because until step 3 it was hard-wired to the dev release on every branch and did the opposite. This push is also the skip-if-exists verification. It touches neither client's file set, so both `decide` jobs should report the channel already serving the current version and skip a 6- and a 9-minute build — while the guard still runs on that path (§6.3). #3146 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
4.5 KiB
Markdown
107 lines
4.5 KiB
Markdown
# 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 comes from
|
|
|
|
**Normally: nowhere. It is already in the image.**
|
|
|
|
CI fetches the published Android build into every server image it builds, so
|
|
`:dev` and `:latest` both ship a client. `docker compose pull && docker compose
|
|
up -d` delivers a new server and a new client together, and there is nothing to
|
|
copy.
|
|
|
|
**The channel is a property of the image you run.** A `:dev` image bakes in the
|
|
dev-channel APK, `:latest` the stable one — so pointing a phone at a stable
|
|
server gets it a stable client, with no second place holding that decision. (Until
|
|
M314 step 3 the fetch was hard-wired to the dev release on every branch, so a
|
|
stable server served a dev client.)
|
|
|
|
An image therefore carries the *newest* client on its channel rather than one
|
|
pinned to a version. That is deliberate: the two negotiate a sync protocol version
|
|
before they link, so a mismatch is caught by the handshake rather than by
|
|
pinning.
|
|
|
|
## Overriding it
|
|
|
|
If you want a specific build — testing something, or holding back — drop it in
|
|
`/var/thoughtsync/client/` and it wins over the image's copy.
|
|
|
|
Two files, both required:
|
|
|
|
| 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 — and survives an image
|
|
upgrade, which is the point of an override.
|
|
|
|
Both files are published to the rolling `dev` release on every green Android
|
|
build:
|
|
|
|
```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
|
|
|
|
# Copy the sidecar LAST. A sidecar that does not match the APK beside it is not a
|
|
# client, so a half-finished copy falls back to the image's build 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/
|
|
```
|
|
|
|
To go back to whatever the image ships, delete both files.
|
|
|
|
## 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
|
|
|
|
All of these describe an override in `/var/thoughtsync/client/`. A broken
|
|
override does not take the feature away — it falls through to the build the image
|
|
shipped with, which is the whole reason precedence runs in that direction.
|
|
|
|
- **Only the APK, no sidecar** — not a client. The server cannot state a version
|
|
it has no way to read.
|
|
- **Mismatched pair** (new APK, old sidecar) — not a client. It will not serve
|
|
one build while describing another.
|
|
- **Neither, and no baked copy either** — the server reports no client, the UI
|
|
hides the card, and `/api/client/android` returns 404. That is the state of any
|
|
image built before the first Android build ever published, 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.
|