Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 12s
CI & Build / Python tests (push) Successful in 23s
Android / Core and FFI clippy and tests (push) Successful in 51s
CI & Build / integration (push) Successful in 1m49s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m20s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m6s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m46s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m31s
Android / Build the server image (push) Successful in 1s
#6225 "Comment standard sweep: root, config, CI workflows, docs and packaging", milestone 546 "Bring the repo to the comment standard (rule 32) and compose defaults (rule 198)". - README restructured in newcomer order: what it is, quick start, configuration, setup pitfalls, updating and backups, features, troubleshooting, development, licence. Its quick-start compose carries no restart policy. - Dockerfile and .env.example trimmed to one or two lines per setting. - Workflows, packaging scripts and docs lose issue, run, note, rule and milestone references and their history narration. Comments that still described path filters and "step 6" as future work now state what the workflows do. - ci-requirements.md rewritten against the current workflows. It said the build gated on typecheck and lint only, the integration lane did not gate, a v* tag published :latest, and ci-tauri lacked bsdtar and zstd; none of those is true now (the last checked against run 9630's log). - Echo strings in guard-forward.sh and version.sh drop their note citations; every string a test asserts on is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.6 KiB
Markdown
109 lines
4.6 KiB
Markdown
# Getting the Android app onto your server
|
|
|
|
Inkwell'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.
|
|
|
|
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/inkwell/client/` and it wins over the image's copy.
|
|
|
|
That directory is shared with the desktop clients the server hands out, and
|
|
**precedence is decided per platform**: dropping in an APK overrides the baked APK
|
|
and leaves every other client alone. It is one directory, not one choice.
|
|
|
|
Two files, both required:
|
|
|
|
| File | What it is |
|
|
| --- | --- |
|
|
| `inkwell.apk` | the client |
|
|
| `inkwell-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/inkwell` 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-channel release (tag `dev-rolling`)
|
|
on every green Android build:
|
|
|
|
```sh
|
|
REPO=https://git.fabledsword.com/bvandeusen/inkwell
|
|
TOKEN=... # a Fabled-Git token with read access; the instance is private
|
|
|
|
for f in inkwell.apk inkwell-android.json; do
|
|
curl -fsSL -H "Authorization: token $TOKEN" \
|
|
-o "/tmp/$f" "$REPO/releases/download/dev-rolling/$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/inkwell.apk app:/var/inkwell/client/
|
|
docker compose cp /tmp/inkwell-android.json app:/var/inkwell/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/inkwell/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. If you are carrying a build from
|
|
before that, expect to uninstall once more and sync anything you care about first.
|