Files
FTStore/manager/README.md
T
Felitendo 86cf31bb2b feat: split the admin console out of the manager into its own app
The web/ dashboard used to ride along inside FTStore Manager as a second
tab, hidden behind a developer-mode switch. Hiding was the wrong tool:
the Manager is what a normal user installs to get FTStore onto a phone,
and the worker controls were one gesture away from it.

FTStore Admin (admin/, app.ftstore.admin) is now its own app:

- admin/FTStoreAdmin/Console/* is the moved console, unchanged in
  behaviour — ManagerConfig became AdminConfig, ManagerHomeView.brand
  became AdminTheme.brand.
- It links no Swift package at all, only system frameworks. The three
  helpers it borrowed from NimbleKit and the engine (User-Agent, share
  sheet, image resize, AnyCodable) are inlined in Support/, so the IPA
  stays what it claims to be: a login form and nothing else.
- Its Keychain service is app.ftstore.admin and its User-Agent is
  FTStore-Admin/…, deliberately outside web/'s APP_UA gate — the console
  only calls session-authenticated routes.
- The optional biometric gate now re-arms whenever the app leaves the
  screen, not just at launch.
- Sideloaded on purpose: admin-release.yml cuts a draft release from an
  admin-v* tag and admin-build.yml compiles every push, but there is no
  AltStore source and no self-update channel.

The Manager loses the console, the developer-mode gate and therefore the
tab bar — FTStoreManagerApp shows ManagerHomeView directly. Its camera
and Face ID usage strings go with it, and ManagerLegacyConsole.purge()
wipes the session an updated install would otherwise still be holding.
2026-08-04 08:53:22 +02:00

6.5 KiB

FTStore Manager

A tiny companion app that installs and updates FTStore from a private FTStore Admin — after you import a signing certificate.


FTStore Manager is a stripped-down fork of FTStore (itself a fork of Feather, GPL-3.0). It keeps only the parts needed to:

  1. Import a signing certificate (.p12 + .mobileprovision).
  2. Download FTStore from a private FTStore Admin, sign it with your certificate, and install it on-device (OTA) — no computer, no pairing file.
  3. Keep FTStore up to date on a Stable or Developer channel.

Everything else from FTStore (the store browser, the app library, tweaks, advanced signing options, the device-pairing install method) has been removed.

It has one screen and no account. The operator console that used to ride along as a second tab is its own app now: admin/ — FTStore Admin. Nothing in here needs a dashboard login, and nothing in here should ever grow one.

The PAT never ships in the app

FTStore lives in a private GitHub repo (FTStuff/FTStore Admin), so downloading it needs a GitHub token. Embedding a token in an iOS app is not secure — anyone with the .ipa can recover an embedded secret at runtime, no matter how it's obfuscated.

So the Manager never contains the token. Instead, FTStore Admin (the Next.js server you already run) exposes a small proxy that holds the token server-side and streams FTStore back to the app:

FTStore Manager  ──HTTPS──▶  FTStore Admin proxy  ──token (server-side)──▶  GitHub (private repo)
   (no token)                /api/ftstore-app/*                       FTStuff/FTStore Admin

The only thing the app needs is your FTStore Admin address. The GitHub token stays server-side in FTStore Admin and is never exposed to the Manager.

Part 1 — FTStore Admin proxy (server)

Two endpoints were added to FTStore Admin (src/app/api/ftstore-app/). They reuse FTStore Admin's existing server-side GitHub token (GITHUB_TOKEN / DB setting):

Endpoint Purpose
GET /api/ftstore-app/manifest?channel=stable|dev Returns { version, updatedAt, size, downloadURL } for the channel.
GET /api/ftstore-app/download?channel=stable|dev Streams the FTStore IPA bytes (the only path that touches the token).

Channels

  • stable → the newest non-dev release of FTStuff/FTStore Admin that has an FTStore*.ipa asset. Version = the release tag (e.g. v2.8.2).
  • dev → the release tagged dev. The version string never changes, so freshness is tracked by the asset's updated_at timestamp.

Prerequisites: FTStore Admin must be reachable from your device, have GITHUB_TOKEN / GITHUB_OWNER / GITHUB_REPO configured, and contain an FTStore*.ipa asset on a stable release and/or on the dev release.

Part 2 — the app

  • Identity: display name FTStore Manager, bundle id app.ftstore.manager (distinct from app.ftstore, so installing FTStore is never a self-install — that's why OTA works without a pairing file).
  • Config: inject the FTStore Admin address at build time via the FTSTORE_ADMIN_BASE_URL user-defined setting in FTStore.xcconfig (it flows into Info.plist as FTStoreAdminBaseURL), or use the built-in default. Nothing secret is committed.
  • Flow: the home screen shows FTStore's state (Not installed / Up to date / Update available) and one button. Tapping it downloads via the proxy, auto-signs with your certificate (prompting you to import one the first time), and installs over the air.

New / changed source (everything else is unmodified FTStore engine)

FTStore/Manager/
  ManagerConfig.swift          FTStore Admin base URL (Info.plist + override)
  FTStoreAdminClient.swift           Calls the FTStore Admin manifest endpoint
  FTStoreUpdateManager.swift   Decides install vs update (stable=version, dev=updatedAt)
  ManagerHomeView.swift        The installer screen — the whole app
  ManagerSettingsView.swift    Channel, install method, notifications, certificates
  ManagerLegacyConsole.swift   Wipes what the old in-app console left behind
FTStore/FTStoreManagerApp.swift  @main entry (replaces FTStoreApp.swift)

Small edits: AppInstallManager gained an install(InstallRequest) entry point and DownloadManager gained an optional auth-header parameter; ServerPackModel moved into Backend/Server/Models/.

Where the console went

Up to manager-v1.0 this app carried the /admin dashboard as a second tab, hidden behind a developer-mode switch (tap the version ten times). That surface now lives in FTStore Admin, a separate app you sideload yourself — so the Manager is one screen again and a normal user is never one gesture away from the worker controls.

If you used the in-app console, sign in once more in the new app: its session lives under its own Keychain service and cannot be read across apps. The Manager wipes the leftover one at launch (ManagerLegacyConsole), and you can revoke it server-side from Settings → Users.

The installer deliberately stays account-free, so a fresh device is never locked out of getting FTStore onto it.

Building

macOS + Xcode required (this is a Swift/Xcode project — it can't build on Linux/Windows). Dependencies are vendored (no submodule init needed):

git clone --recurse-submodules https://github.com/FTStuff/FTStore.git
cd FTStore/manager
make            # builds packages/FTStore-Manager.ipa (the Manager)

Or open FTStore.xcworkspace in Xcode, or run the Manager Release GitHub Action (workflow_dispatch or push a v* tag).

The optional LicensePlist / AltSourceKit / IDeviceKitten Swift packages remain linked (the OTA installer reuses a couple of helpers from them). They can be pruned in Xcode if you want a leaner build.

License & credits

GPL-3.0, inherited from FTStore / Feather. The original Feather copyright notices are preserved (LICENSE). Huge thanks to: