FTStore Admin

The FTStore Admin dashboard as an iOS app — for the person who runs the server, and nobody else.

--- FTStore Admin signs in against your FTStore Admin server with a normal dashboard account and then drives the same API the web UI does: the worker, the library, the queue, the scraper, Telegram, users, settings, backups. It used to be a second tab inside [FTStore Manager](../manager), hidden behind a developer-mode switch. It is its own app now, because "hidden" was the wrong tool: the Manager is what a normal user installs to get FTStore onto their phone, and an operator console has no business being one gesture away from that. ## What it is not - **Not something to hand out.** It ships from the rolling `admin-dev` prerelease of a **private** repo, so its AltStore source is only reachable by someone who already has access. There is no `web/` proxy and no in-app updater — it never touches the `/api/ftstore-*` endpoints the app and the Manager use. - **Not an installer.** It signs nothing and installs nothing — no Zsign, no Vapor, no certificates, no OTA. Installing FTStore is the Manager's job. - **Not useful without an account.** See below. ## Screens | Screen | What it does | |--------|--------------| | Root | Worker start/stop, headline KPIs, navigation, sign out / lock | | Dashboard | KPIs, 30-day trends, builds per source, in-flight transfers, recent activity | | Library | Every build: search, filter, detail (tweaks, entitlements, privacy manifest), rename in the feed, flag corrupted, refresh App Store metadata, delete | | Upload IPA | Pick an `.ipa`, attribute it to a source, push it in 8 MiB chunks and run the ingest pipeline | | Queue | Per-status counts, storage-cap banner, paging, retry / skip / delete, bulk retry-failed and clear-completed | | Logs | The activity log — filter by type and status, search, paginate | | Channels & Sources | Telegram channels (add, enable, remove) and GitHub release sources (enable) | | Telegram | The worker's TDLib login: connect, verification code, 2FA password, disconnect, reset | | Known Tweaks | Edit `known_tweaks`: aliases, lock to a Telegram channel or GitHub repo, recommended flag, file-name filters, custom icon (Files, Photos or camera) | | Users | Admin only — approve, revoke, delete accounts | | Scraper Actions | Find IPA, scan, scan previous, process queue, publish feed, cleanup, cache, rebuild storage, nuke | | Server Settings | Source metadata, intervals, limits, feed/storage options; credentials shown read-only and masked | | Backup & Restore | Export the whole configuration as JSON (with or without secrets) and import one back | Two things stay in the browser, both because they are bulk work on a table rather than a phone task: multi-select delete/rename across the Library, and reordering channels. ## Why decrypting the IPA gets you nothing The security model is that the app is worthless without an account: - **No secret ships in the app.** The only baked-in value is the FTStore Admin base URL, which is the same public domain FTStore already uses as its default source. There is no API key, no token, no privileged URL to extract. - **Every request is authenticated server-side.** `web/src/middleware.ts` rejects any `/api/*` call without a session cookie, and each handler re-checks that session against the database through `withAuth()` / `withAdmin()`. A forged cookie gets a 401 from the handler even though it passes the middleware's presence check. - **The password is never stored.** It is used for one `POST /api/auth/login` and discarded. What persists is the server-issued session token. - **The token lives in the Keychain**, as `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` — so it is not in an iTunes/iCloud backup and does not travel to another device. The app's `URLSession` is ephemeral with cookie storage disabled, so nothing authenticating it is ever written to the app container in the clear. - **Sessions are revocable.** Signing out ends the session on the server too, and an admin can revoke any account from Settings → Users at any time. - **Nothing sensitive is cached locally.** GitHub PATs and the Telegram API hash arrive masked from the server and are read-only here; the Telegram login code and 2FA password are typed here but handled only by your own worker; a settings export is written to the temp directory for the share sheet, never to a browsable one. - **Optional local gate.** Face ID / Touch ID (falling back to the device passcode) can be required before the console opens, and re-arms whenever the app leaves the screen. That protects an unlocked, unattended device; the server credentials are what actually protect the data. ## Layout ``` FTStoreAdmin/ FTStoreAdminApp.swift @main — session, tint, the biometric re-lock Console/ One file per screen, plus the transport AdminAPI.swift HTTP, multipart upload, raw payloads, login AdminSession.swift The single door: token, 401 handling, lock AdminKeychain.swift Where the session token lives AdminModels.swift Mirrors web/'s handler responses + request bodies AdminRootView.swift Front door and table of contents Admin*View.swift The screens in the table above Support/ AdminConfig.swift Base URL, User-Agent — the only baked-in values AdminTheme.swift The brand colour AdminUIKit.swift Share sheet + image resize, inlined from NimbleKit AnyCodable.swift For the open-ended payloads Resources/ Info.plist, app icon ``` **The app links no Swift package at all** — only system frameworks (SwiftUI, Charts, Security, LocalAuthentication, PhotosUI). That is deliberate: the few helpers it needed from the FTStore engine are inlined in `Support/`, which keeps the binary small and keeps "a decrypted IPA is a login form" easy to verify. ## Configuration The server address is injected at build time via `FTSTORE_ADMIN_BASE_URL` in [`FTStoreAdmin.xcconfig`](./FTStoreAdmin.xcconfig) (it flows into `Info.plist` as `FTStoreAdminBaseURL`); leave it blank to use the built-in default, `https://ftstore.app`. **Never commit a token here** — there is no field for one, and there should never be. ## Building macOS + Xcode required. No submodules, no packages, no `deps` step: ```sh cd FTStore/admin make # builds packages/FTStore-Admin.ipa ``` Or open `FTStoreAdmin.xcodeproj` in Xcode. CI works like the app's and the Manager's: **Admin Dev Repo** builds every push that touches `admin/` and publishes the IPA plus a generated AltStore source (`admin-devrepo.json`) to the rolling **`admin-dev`** prerelease, so the console updates itself the same way the other two do. **Admin Release** cuts a draft release from an `admin-v*` tag when a stable one is wanted. ``` https://github.com/FTStuff/FTStore/releases/download/admin-dev/admin-devrepo.json ``` The IPA is unsigned — sign it on-device at install with your own certificate. ## License & credits GPL-3.0, like the rest of the repo — see the root [LICENSE](../LICENSE). The console was written for FTStore Admin's own API; the two UIKit helpers in `Support/AdminUIKit.swift` are adapted from [NimbleKit](../manager/NimbleKit) (MIT), and `AnyCodable.swift` comes from FTStore's engine, which descends from [Feather](https://github.com/claration/Feather).