Use
Update the kiosk
On this page
- The ways to update a frame
- How an update travels
- Two keys, two jobs
- Build a release
- Update over ADB
- Upload in the admin
- Add a release from the shell
- Choose which frame gets a release
- The rollback watchdog
- Failure states
- Roll back by rebuilding
- Keystore and release key hygiene
- What the server logs
- Related
This page is for the person who builds and sends new versions of the kiosk app. When you finish, you can put a new kiosk build on a frame, roll it out to the rest, and recover when a build goes wrong, even when the frame is hundreds of miles away.
You install the first kiosk on a frame over ADB. After that, the frame can update itself from a release you upload to your server. The server only stores the file and tells each frame which build it should have. The frame decides whether to install it.
The ways to update a frame
| Method | Use it for | Needs | Section |
|---|---|---|---|
| ADB, first install | A frame that has no kiosk yet. | USB or network ADB. | Provisioning, step 7 |
| ADB, manual update | A frame next to you, or one that cannot self-update. | ADB, the same keystore. | Update over ADB |
| Admin upload | The normal way. | The admin, a built APK and its .sig. |
Upload in the admin |
| Shell on the server | A server where you have a shell, or for scripting. | bin/kiosk-release.php. |
Add a release from the shell |
The admin upload and the shell command both store a release. You then choose which frame gets it in the admin.
How an update travels
sequenceDiagram
autonumber
participant You as You (offline)
participant Admin as Admin + server
participant Frame as Frame (kiosk)
participant Root as Root watchdog
You->>You: Build APK, sign with APK keystore
You->>You: Sign hash + version code with offline release key
You->>Admin: Upload APK and .sig (password re-check)
You->>Admin: Target ONE frame first
Admin->>Frame: Offer release
Frame->>Frame: Verify release signature (key baked into app)
Frame->>Frame: Verify SHA-256, size, plain ZIP (Janus-safe)
Frame->>Frame: Verify package, higher version, same signing certificate
Frame->>Frame: Wait until the screen is quiet
Frame->>Frame: Install silently
Frame->>Admin: Report running new build
Admin->>Admin: Mark release "Proven"
You->>Admin: Roll out to all
Note over Frame,Root: If the new build is not healthy after about 15 minutes
Root->>Frame: Reinstall last known good build
Frame->>Admin: Report the failed release ("unhealthy")
Four things keep this safe to send and safe to get wrong:
- Two signatures. Android checks the APK certificate. The frame also checks a second, separate signature from an offline ECDSA key that never touches the server. A stolen admin password cannot push a build you did not sign.
- The frame decides. It refuses downgrades, other packages, mismatched certificates, and files with anything glued in front of the ZIP.
- Staged rollout. A release is marked Proven only after a frame has downloaded it and reported running it. Only then can you roll it out to everyone.
- A root watchdog. If a new build never becomes healthy, the frame reinstalls the last one that was, on its own. A crashing build costs roughly 15 minutes.
Two keys, two jobs
| APK keystore | Release key | |
|---|---|---|
| What it is | An RSA key in a Java keystore, default ~/.android/frame-kiosk.keystore |
An EC P-256 key as a PEM file, default ~/.android/frame-release-key.pem |
| What it signs | The kiosk app (the APK) | The text <sha256>:<versionCode> of each build |
| Who checks it | Android, when installing an update | The kiosk, from the public key baked into the app. The server too, if you set kiosk_release_pubkey. |
| Lose it, and | Every frame needs the kiosk uninstalled and installed again | Every frame needs one more adb install with a new public key |
| Keep it | Backed up, private, off the server | Backed up, private, offline, off the server |
Make both before your first build. The commands are in Provisioning, step 5, and the .env keys are KEYSTORE, KEYSTORE_PASS, RELEASE_KEY and RELEASE_PUBKEY.
The release signature is ECDSA with SHA-256 over the ASCII text <sha256 hex of the APK>:<versionCode>, for example ab12...ef:15. The .sig file holds the base64 of its DER encoding.
To let the server reject bad signatures early, put the public key in config.php:
'kiosk_release_pubkey' => "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n",
Without it the server stores the signature as given, and only the frame checks it.
Build a release
-
Raise the version code. Every release needs a higher number than the last. Either edit
android:versionCodeinkiosk/AndroidManifest.xml, or pass it as an option. -
Build:
cd kiosk lando build-kiosk -- --version-code 16 --version-name 16
The build prints:
built kiosk/build/frame-kiosk.apk
versionCode 16
sha256 <64 hex characters>
signature kiosk/build/frame-kiosk.apk.sig (upload with the APK)
You now have two files in kiosk/build/: frame-kiosk.apk and frame-kiosk.apk.sig. Warnings that begin WARNING: mean a release key setting is missing, and the build then cannot self-update. Provisioning, step 6 lists every build option.
Optional: check the signature yourself with OpenSSL. This is standard OpenSSL and is not part of the project's scripts:
printf '%s' '<sha256 from the build>:16' > msg.txt
base64 -d kiosk/build/frame-kiosk.apk.sig > sig.der
openssl dgst -sha256 -verify ~/.android/frame-release-pub.pem -signature sig.der msg.txt
It prints Verified OK.
Rules for every release
- A higher version code than the last. Android and the kiosk both refuse a lower one. To go back, see Roll back by rebuilding.
- Data compatibility. A rollback keeps the app's saved data. A release must not write preferences or files that the previous release cannot read, or a rollback will crash.
- Sign it with the same keystore. A different key makes the frame refuse the update (
cert). - Never upload a test build. See Keystore and release key hygiene.
Update over ADB
For a frame next to you, or one that cannot self-update, install a newly built APK over the old one. It must be signed with the same keystore:
adb install -r kiosk/build/frame-kiosk.apk
Use -s <serial> to pick a device. This keeps the app's data and the device owner setting. Use the same command when you change the baked-in release public key, KIOSK_PIN or FRAME_SERVER_URL, because each is built into the app.
A frame updated over ADB has no .sig check. Nothing but you and Android is involved.
Upload in the admin
- In the admin, select Library.
- Under Kiosk app, select Upload release.
- Choose
frame-kiosk.apkas the APK file (up to 16 MiB). - Choose
frame-kiosk.apk.sigas the Release signature. - Add Notes (optional) if you like, up to 500 characters.
- Select Upload. If you have not entered your password in the last five minutes, the admin asks for it.
You see "Release uploaded". The table lists the release with its size, who uploaded it, how many frames run it, and whether it is Proven or Not yet proven.
The server checks the file before it keeps it:
- It is a plain ZIP, with nothing hidden before or after the entries (Janus-safe).
- Its package is
com.calevans.frame. - Its version code is a number from 1 to 2,100,000,000.
- If you set
kiosk_release_pubkey, the signature verifies. - No release with the same file hash or version code already exists.
The server does not check the APK's signing certificate. The frame does.
Uploaded builds are stored in var/kiosk/, named by their SHA-256 hash. Back the folder up with var/frames.db.
If the upload fails, the box shows the reason. A 413 means a size limit. See Troubleshooting.
Add a release from the shell
On the server, you can add a release without the admin. Run it as the web user (it refuses root), from the folder that holds app/:
sudo -u www-data php app/bin/kiosk-release.php add frame-kiosk.apk [frame-kiosk.apk.sig] [--notes "text"]
sudo -u www-data php app/bin/kiosk-release.php list
- The signature file defaults to
<apk>.sig. --notestakes up to 500 characters.- The web user must be able to read both files. Copy them to a folder it can read first.
- It makes the same checks as the admin upload, refuses symlinks and files that are not regular files, and stops at 16 MiB.
- On success it prints
version_code,version_name,sha256andid. - Failures go to standard error with the admin's codes:
duplicate_sha256,duplicate_version_code,not_apkand others. - The upload is logged as
cli:<your login>(theSUDO_USERname when you use sudo).
list prints one line per release, with tabs between the columns: ID, version code, version name, SHA-256, size, upload time (UTC), uploaded by, and proven or unproven.
Choosing which frame gets the release is still done in the admin.
Choose which frame gets a release
Always target one frame first.
- Open the frame in the admin, then Details.
- Find the Kiosk version row. It shows the build the frame runs ("Build 15").
- In the list, choose the release. Each entry reads like
16 (build 16). No update cancels. It asks for your password.
You see "Update queued". The frame installs the release the next time its screen is quiet.
The row shows the state:
| Pill | Meaning |
|---|---|
| Up to date | The frame runs the target, or newer. |
| Update pending | The frame has been offered the release and has not installed it yet. |
| Waiting for a report | The frame has not yet said which build it runs. |
| Update failed | See Failure states. |
| No rollback watchdog | The frame's boot script has no watchdog. See below. |
A frame with no rollback watchdog can only be sent a release that has already run on another frame. The server refuses with "this frame has no rollback watchdog, so it can only take a release that already runs on another frame". Send the first copy to a frame that has the watchdog. Install the boot script on every frame before you use self-update on it.
What the frame checks
The frame installs a release only if all of these hold. Otherwise it holds a failure that the admin shows.
- The detached release signature verifies against the public key baked into the app.
- The file's SHA-256 and size match the offer, it is at most 32 MiB, and it is a plain ZIP.
- It is the kiosk package. Its version code is the offered one and is higher than the installed one. A frame never downgrades itself.
- Its signing certificates are exactly the installed ones.
- Nobody is using the frame: the network is up, the PIN panel and Wi-Fi setup are closed, and the screen is dimmed (bedtime or Dim) or untouched for 30 minutes. The frame checks every 30 seconds.
The server lets each frame download a release at most 12 times an hour.
Staged rollout and proven releases
A release is Proven once a frame has downloaded it in full and then reported running that version or newer. Only a proven release can be rolled out to everyone.
- Target one frame (above).
- Wait. Check that its Kiosk version row shows Up to date, and look at the frame's Screen to see the dashboard is fine.
- In Library, under Kiosk app, the release now says Proven.
- Select Roll out to all on that release. It asks for your password.
The admin says how many frames will update, and how many are already on it or queued. The button is disabled before then, with the hint "Target one frame first and let it prove this release".
Roll out to all offers the release to every active frame that reports an older build, unless that frame is already pointed at this release or a newer one.
When you claim a new frame, the server points it at the newest release that has already run on a frame. If no release has run yet, the frame stays on the build you installed.
The rollback watchdog
The boot script device/frame-fixmac.sh carries the line # frame-watchdog: 1. The kiosk reports it, and the admin shows No rollback watchdog for frames without it.
How it works:
- The kiosk writes
healthy(its version code) after it has been in front for 5 minutes without crashing, or after its first good server answer plus a loaded dashboard. - Root copies the installed APK to
/data/misc/frame/kiosk-good.apk(root-only) each timehealthyequals the installed version. On the first boot with the script, the installed build is copied. - If a new version was installed and is still not healthy after about 15 minutes, root reinstalls that copy with
pm install -r -d, once. - The admin shows Update failed with "the new build did not start properly and was rolled back".
A crashing build therefore costs you roughly 15 minutes, then the frame is back on the last build that worked.
Root reads only the digits of healthy and the to= line of files/update/state from the app's folder, ignores symlinks and anything else, and never writes into the app's folder. The server can therefore at most cause a rollback to the last build that ran healthy on that frame.
Failure states
When a frame cannot install a release, the Kiosk version row says Update failed and gives the reason.
| Reason in the admin | What it means | What to do |
|---|---|---|
| the download failed | The frame could not fetch the file. | Check that the frame is online and var/kiosk/ is readable by the web user. Select Clear and retry. |
| the file was the wrong size | The size does not match the offer. | Delete the release and upload it again. |
| the file did not match its hash | The stored file differs from the offer. | Delete the release and upload it again. |
| the file is not a plain APK | The ZIP has extra data in it. | Rebuild and upload again. |
| the file is for another app | The package is not the kiosk. | Upload the right APK. |
| the file is not newer than the installed app | The version code is not higher. | Rebuild with a higher version code. |
| the file's version is not the one offered | The APK's version code differs from the release record. | Delete the release and upload again. |
| the signing certificate differs from the installed app | The build was signed with a different keystore. | Rebuild with the original keystore. If the original is lost, see Lost keystore. |
| the release signature did not verify | The signature does not match, or the frame has a different public key baked in. | Check RELEASE_KEY and RELEASE_PUBKEY. A frame built with another public key needs an adb install -r of a build with the right one. |
| the new build did not start properly and was rolled back | The watchdog reinstalled the old build. | Find out why the new build failed. Fix it, raise the version code, and release again. |
| the app is not the device owner | The kiosk lost its device owner status. | Redo Provisioning, step 7. |
| Android refused the install (status N) | Android returned an install error. | Read adb logcat on the frame. |
When you have fixed the cause, select Clear and retry on the Kiosk version row. It asks for your password and says "Cleared. The frame will try again." Until then the frame does not try the same file again.
A failure belongs to one file. A new release with a different file is offered afresh.
Delete a release
In Library, under Kiosk app, select Delete on the release. It asks for your password, then removes the record and the file.
The admin refuses while a frame still targets the release: "a frame still targets this release; clear its target first". Set that frame's Kiosk version to No update first.
Roll back by rebuilding
Android and the kiosk both refuse a lower version code, so you cannot send an old build as it was. To go back, build the old code again with a higher number and release that:
git checkout <old commit> -- kiosk/src kiosk/res
cd kiosk && lando build-kiosk -- --version-code 21 --version-name 21-rollback
(KIOSK_VERSION_CODE and KIOSK_VERSION_NAME in the environment work too when you build without Lando.) The new APK has a new SHA-256 and its own signature file. Upload it, target one frame, and roll it out as usual.
Keystore and release key hygiene
- Back up both keys somewhere private. Keep the release key off the server, out of the repository and out of any container image. Do not give either key to the server, to CI, or to anyone else.
- Never sign a throwaway or test build with the real keys, and never upload one as a release. The only exception is the
--test-crashbuild, used for the watchdog test on your own frame. - Lost the release key: every frame needs one more
adb install -rof a build with a new public key. - Lost the keystore: you cannot update in place. On every frame you must undo device owner, uninstall the kiosk, and start again. See Provisioning.
After a watchdog test
A --test-crash build (lando build-kiosk -- --test-crash --version-code <N>) crashes on purpose, but only while root has run setprop debug.frame.testcrash 1. That property is cleared at reboot. With SELinux off, any app on the frame could set it, so the gate means only that a compromised server alone cannot trigger the crash.
The build is signed with your real keys, so retire it as soon as the test is done:
- Delete that release in the admin.
- Remove the build:
rm -rf kiosk/build, and any copy of the crash APK and its.sigthat you saved. - Ship a real release with a higher version code to every frame. The crash build is then refused as
not_newereverywhere. Do this before any frame leaves your control.
What the server logs
Uploads, targets, roll-outs and cleared failures go to the PHP error log, for example:
frame admin: kiosk release uploaded user=yourname ip=203.0.113.9 version_code=15 sha256=...
frame admin: kiosk target set user=yourname ip=203.0.113.9 frame=1a2b3c4d version_code=15 sha256=...
frame admin: kiosk rollout user=yourname ip=203.0.113.9 version_code=15 sha256=... frames=3
frame admin: kiosk failure cleared user=yourname ip=203.0.113.9 frame=1a2b3c4d sha256=... reason=hash
Other events: release deleted, target cleared, target set on claim, and upload rejected (with the reason).
Related
Next: Troubleshooting and recovery.