The app icon is flattened onto black because iOS rejects an alpha channel, and copying that straight over made every dock tile an opaque black square. The tile wants the frame's own rounded edge with the corners cut away — which is what the icon this replaces already had, and what tile: bare means. 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.