Copied from its existing master — this art was already drawn to the current brief, it simply had nowhere to live outside a designer's folder. The pair is the point: the icon is a reduction of the splash's world. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kiosk
Build, sign and install Officer's iOS and Android apps on your own registered devices. What EAS does, self-hosted.
- A catalogue of builds, newest per app per platform, with an Install button that hands
itms-services://to iOS or a plain URL to Android. - A per-user key that authorises artefact downloads. Held in memory by the app and re-read on demand, so rotating one costs nothing.
- Device records — who owns which iPhone or iPad, and whether Apple knows about it yet.
Status — read before installing
Nothing here has run. Ported on 2026-08-18 from the standalone service that was called OAS. The authenticated API and the two panels exist and typecheck; no endpoint has answered a request.
What works, in principle
GET /apps, GET /apps/:appName, GET /key, POST /key/rotate,
GET /install/:appName/:platform/:version, POST /installs, and the device and build verbs. All under
/api/kiosk, all requiring a platform session.
What is missing, and why
Installing will not work yet. Four requests in the flow arrive with no session — iOS installd fetching
the manifest and the .ipa, the device posting its UDID, and Safari downloading a .mobileconfig — and a
plugin cannot serve an unauthenticated route today. UNPROTECTED_API_PREFIXES is hardcoded in the platform
and plugin routers mount only behind the account gate.
So GET /install/... returns a correct-looking URL pointing at a route that does not exist. That is
deliberate: the URL shape is what the mobile app builds against, and it stops being a dead end the day the
platform gains unauthenticatedPaths. Those routes then land in api/public.ts, kept separate from
api/router.ts so the two surfaces cannot be confused by anyone reading either.
Enrolment is not ported for the same reason — it is entirely public routes.
Artefacts arrive by rsync, into $OFFICER_ROOT/data/kiosk/. The plugin records metadata about files
already on disk; it does not accept uploads, because the platform's shared upload route caps at 5MB and no
.ipa will ever satisfy that.
Notes
PUBLIC_URLmust be HTTPS with a certificate iOS already trusts. It fetches the manifest and binary itself and declines silently otherwise, which looks exactly like a broken build.- Table names are prefixed
kiosk_. There are no migrations — see the note at the top ofdb/schema.ts. - The plugin is currently
appName: kiosk. If the plugin storefront takes that name, this one is renamed; seedocs/kiosk-ecosystem.mdin the platform.