The app is FTStore Admin. Its own AGENTS.md already sets the vocabulary — "the FTStore Admin server" for web/, "the FTStore Admin app" for this one — and then most of the comments in it said "the console" anyway. Three user-visible strings went with it: the lock screen, the menu item that locks it, and the biometric prompt. The storage-notification footer named the console too. Their catalog keys move with them, in every locale the app ships. Not "the app": inside these files that already means FTStore on somebody's phone, so a blind swap produced lines like "Dashboard and app only, never the app". AdminConsoleView is AdminHomeView. The Console/ directory keeps its name: the history of Console/*.swift runs back through manager/FTStore/Admin/ and is documented as doing so, which is worth more than a path nobody reads as a name. Also fills a translation gap this turned up — the status-change footer in the feedback detail was untranslated.
136 lines
7.4 KiB
Markdown
136 lines
7.4 KiB
Markdown
<h1 align="center">FTStore Admin</h1>
|
|
|
|
<p align="center"><strong>The <a href="../web">FTStore Admin</a> dashboard as an iOS app — for the person who runs the server, and nobody else.</strong></p>
|
|
|
|
---
|
|
|
|
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
|
|
FTStore Admin 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 and tested flags, 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 FTStore Admin 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
|
|
FTStore Admin/ 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 FTStore Admin 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). FTStore Admin 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).
|