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.
131 lines
6.4 KiB
Markdown
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).
|