App updates over P2P (OTA)
You can send app updates to installed viewer apps. You do not need an app store or a file server. The panel stores the update files. The apps find them, download them over P2P, and ask the viewer to install them. This page tells you how to publish an update, and what the apps do with it.
What it does
- The panel keeps the update files in a public Hyperdrive (the
"updates" drive). The panel catalog holds the drive key in one signed
record (
meta/updatesKey). - You upload an APK to the panel. The panel adds it to a manifest,
under the app's
applicationIdandversionCode. - Viewer apps read the manifest through the panel record. Each app
looks only for its own
applicationId. - When a newer version is available, the app shows a banner. The viewer starts the download. The file comes over P2P.
- The app makes sure the file is correct (sha256) after the download. Then Android shows the system install dialog. The install never starts without the viewer's confirmation.
The app looks for updates when it starts, and when the viewer returns to the app. It does this not more than one time each 6 hours. A viewer can also check immediately: Settings → Check for updates…. On a metered network (for example, mobile data) the app does not start the download by itself. It tells the viewer and offers a "Download anyway" button.
Playback does not depend on updates. If the panel or the drive is not available, the apps play as before.
Before you start
Three conditions must be true. If one is false, the update cannot install.
- The installed app must contain the update client. Apps built from an OTA-capable SDK contain it. Older installed builds do not. They need one manual install first — see One-time bootstrap.
- The update must have the same signature as the installed app. Android refuses an update from a different signer. Sign every release of an app with the same private key. See Shipping to production.
- The update must have a higher
versionCode. IncreaseversionCodefor every release. An app never offers an update with an equal or lowerversionCode.
Publish an update
Step 1 — build the release APK
Build the brand APK with a version and the brand's signing configuration:
export ALIRAN_STORE_PASSWORD='...'
export ALIRAN_KEY_PASSWORD='...'
node tools/brand.mjs ../acme --version-code 5 --version-name 1.1.0
White-label branding tells
you how to set up signing.json and the keystore. At the end, the
builder prints the applicationId, the versionCode, and the
versionName. You need these values for the upload.
Step 2 — upload the APK to the panel
In the admin dashboard, open the App updates tab. Fill in the
applicationId, the versionCode, and the versionName from the
build output. Then click Choose file and publish…. Select the APK.
Or use the admin API. The request body is the raw file:
curl -X POST -H "Authorization: Bearer $TOKEN" \
--data-binary @app-acme-release.apk \
"http://localhost:3210/api/updates/com.aliranclient.acme?platform=android&versionCode=5&versionName=1.1.0"
| Parameter | Meaning |
|---|---|
platform |
android or windows. The panel accepts windows files as a base for a later release. The desktop app does not use them yet. |
versionCode |
Required integer. It must be higher than the published one. force=1 overrides this check. |
versionName |
Required. The version text the viewer sees, for example 1.1.0. |
minVersionCode |
Optional. Installed builds below this value see a mandatory update. |
notes |
Optional release notes text. |
The panel computes the file's size and sha256 itself. The default size limit is 512 MB. For each app, the panel keeps the current file and one older file. It removes older files automatically.
GET /api/updates lists the published manifest.
DELETE /api/updates/<applicationId> removes an app's entry and its
files.
Step 3 — wait for the devices
Devices see the new version at their next app start, or when the viewer returns to the app. A viewer can check immediately in Settings. The viewer then downloads, and confirms the install in the Android dialog.
The public (keyless) app
The stock keyless app updates through your panel too. GitHub releases stay the official source. The flow:
- Download the official APK from the project's GitHub releases page.
- Upload it to your panel with the app id
com.aliranclientand the release'sversionCode. - Keyless devices that are paired with your panel then update over P2P.
If you do not upload it, nothing breaks. Viewers update by hand from GitHub, as before. You cannot serve a changed public APK: official public builds must be release-signed, and a device refuses an update from a different signer.
Mandatory updates
Set minVersionCode to make an update mandatory. An installed build
below that value shows a persistent banner. The viewer cannot dismiss
it. Playback continues — a mandatory update never blocks the stream.
"Mandatory" is advisory. It forces nothing.
This is the whole of what minVersionCode does, and it is worth stating
plainly because operators plan around the word:
- It sets a
mandatory: trueflag on the app'scheckUpdate()result. - The app's update banner then renders without a "Later" button.
That is all. Nothing gates playback, login, the catalog or the account on it. A viewer who ignores the banner, backgrounds the app or never opens the screen keeps running the old build indefinitely, and the panel cannot make them update.
Two more ways a device can miss it entirely:
checkUpdate()answers{status: 'unknown'}when no updates drive is advertised or the manifest does not arrive inside its timeout — a cold replica with no reachable peer. That is "try again later", not an error, and the viewer is shown nothing at all.- The check is lazy: nothing opens the updates drive until an app calls
checkUpdate(). A device that is never opened is never told.
So: never gate an irreversible change on a mandatory update. If a change removes channels from the catalog for older builds — as switching ephemeral event sources onto the events drive does — the only sound input is a measurement of what the fleet is actually running:
node src/admin-cli.js app-versions
It counts enrolled devices, so the television that has been off for a
month is in the number, and it reports (unknown) for every device on a
build too old to report at all. See
reference.md.
Rules and warnings
- Never upload an internal or development build. The updates drive is public. Each viewer, and each person with the panel key, can download every file on it. A build with baked-in credentials or auto-login must never go there.
- Keep the keystore and its passwords out of git. Keep real brand directories outside the repository.
- Do not lose the keystore. Without it, no future build can update the installed apps. Each device then needs a manual reinstall.
platform=windowsis only a base. The panel stores Windows files, but the desktop app does not download updates yet.
One-time bootstrap
Devices with a build from before the update client cannot update over P2P. They do not know how. Install one OTA-capable build on them manually (sideload), one time. From then on, they update over P2P.
Troubleshooting
| Symptom | Possible cause | Action |
|---|---|---|
| The update never appears on a device | The manifest applicationId is not the installed app's id |
Compare the id in the App updates tab with the builder's output. Upload under the exact id. |
| The update never appears on a device | The uploaded versionCode is not higher than the installed one |
Check the installed version in Settings. Publish a higher versionCode. |
| The update never appears on a device | The device is a keyless build that is not paired with your panel | Pair the device first. An unpaired app has no update source. |
| The install fails at the Android dialog | The update's signature is not the installed app's signature | Sign with the same keystore as the installed build. A debug-signed install can never update to a release-signed build — reinstall once. |
| The install fails at the Android dialog | The viewer did not permit "install unknown apps" for the app | The app opens the permission screen itself. Tell the viewer to allow it and try again. |
| Google Play Protect blocks the install | The signing key is not known to Google. This is normal for self-hosted apps. | Tell the viewer to tap More details, then Install anyway. Google can also ask for the device password here. This is a device function, not an app function. |
| The download stops or does not start | The device is on a metered network | This is the default protection. The viewer can tap "Download anyway". |
| The download stops or does not start | No reachable peers hold the file | Make sure the panel is up and reachable. The panel always serves its own drive. |