Files
Felitendo b383c8e948 chore: drop two retired names from prose, and write down what admin means
The old server name and the account the repositories live under were still
scattered through comments, doc prose and one line of dashboard copy. None of
those readings told anyone anything: neither name is what anything is called
now, and a comment that explains a value in terms of a name nobody uses
explains nothing.

Values are untouched, and that distinction is the whole rule. The legacy backup
type string, the pre-rename UserDefaults key, the pre-rename host in the
User-Agent allowlist and the registry namespaces are all things somebody else
already stored; rewriting one breaks an install rather than tidying it. So the
sentences changed and the strings did not, and AGENTS.md now lists exactly
which hits a sweep is supposed to keep finding.

The feed's blocklist grows the second name, so a future message cannot put it
back on a page anyone can load. Its fixtures name both on purpose: that test is
the proof the feed never publishes them, and it cannot show that without
saying them once.

Also states the thing that had been left implicit: FTStore Admin is the
dashboard and the console app together, so an operator-facing control that
lands in only one of them is half-built.
2026-08-05 14:56:07 +02:00

131 lines
6.4 KiB
Markdown

<h1 align="center">FTStore Manager</h1>
<p align="center"><strong>A tiny companion app that installs and updates <a href="https://github.com/FTStuff/FTStore">FTStore</a> from a private FTStore Admin — after you import a signing certificate.</strong></p>
---
FTStore Manager is a stripped-down fork of [FTStore](https://github.com/FTStuff/FTStore)
(itself a fork of [Feather](https://github.com/claration/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/`](../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, 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/*
```
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 the app repo 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**](../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):
```sh
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:
- [Samara / claration](https://github.com/claration) — Feather, which FTStore (and this) builds on.
- [Zsign](https://github.com/zhlynn/zsign), [idevice](https://github.com/jkcoxson/idevice),
[Vapor](https://github.com/vapor/vapor), [Nuke](https://github.com/kean/Nuke),
[*.backloop.dev](https://backloop.dev/), [plistserver](https://github.com/nekohaxx/plistserver).