Appearance
Build & Internal release
Ariadne is built with Flutter (Android + iOS). Store identity is com.sourcectl.ariadne on Android, iOS, macOS, and Linux. Launcher label Ariadne. Store title Ariadne Indoor Navigation. CZ-only Internal tracks. Never claim cz.wantok.ariadne. If Apple refuses the iOS App ID, fall back to com.sourcectl.ariadneapp in a follow-up — do not invent the suffix in source until that happens.
Do not start Play Production or App Review. First distribution is Play Internal + TestFlight Internal against https://safecall-lab.sourcectl.com.
Keystores, .p12, and key.properties stay on the NAS (secret store):
| App | NAS |
|---|---|
| Ariadne Android | secrets/ariadne/android/ (upload-keystore.jks, key.properties) |
| Ariadne iOS | secrets/ariadne/iOS/ |
| Theseus Android | secrets/theseus/android/ |
| Theseus iOS | secrets/theseus/iOS/ |
Exact portal clicks: Ariadne operator close-out. Lab host: lab-server.md.
Commands
| Command | Description |
|---|---|
bun build | Build everything (server binaries + Ariadne) |
bun build:ariadne | Build just Ariadne (AAB + IPA) |
bun build:server | Build just the server binaries |
bun release:ariadne | Fail-closed Internal pipeline (test, bump, signed AAB + IPA, commit, push) |
bun release:theseus | Fail-closed Theseus Internal pipeline (NAS --dart-define required) |
bun test:contracts | Run contract tests only (server ↔ ariadne) |
bun run lab:deploy | Lab web build + rsync to safecall-lab.sourcectl.com |
bun release:ariadne and bun release:theseus refuse missing android/key.properties, missing upload keystore, missing ios/ExportOptions.plist, and (Theseus) missing THESEUS_UNLOCK_HASH / ops URL / ops auth. They do not fall back to debug signing.
One-time setup (operator)
Prerequisites
- Flutter SDK (3.10+) on PATH
- Android SDK with build-tools
- macOS with Xcode that archives iOS 15.0–27.0 (current GLaDOS). Ariadne iOS deployment target is 15.0. Do not re-run
bun release:ariadneifariadne/pubspec.yamlis already1.0.1+2from a failed IPA — build the IPA only (see below).
Android upload keystore
Generate on GLaDOS, then copy into NAS. Do not generate a second keystore on MiniVan-3.
bash
cd ariadne/android
keytool -genkey -v \
-keystore upload-keystore.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias upload \
-dname "CN=Ariadne Indoor Navigation, OU=Ariadne, O=SOURCE CONTROL SINGLE MEMBER P.C., C=GR"
cd ../..
cp ariadne/android/key.properties.example ariadne/android/key.propertiesFill passwords in gitignored ariadne/android/key.properties. storeFile=../upload-keystore.jks is relative to ariadne/android/app/. Copy the .jks and key.properties to NAS secrets/ariadne/android/. Same pattern for Theseus under theseus/android/ → secrets/theseus/android/.
Gradle already has fail-closed signingConfigs.release. Do not paste a second signing block.
iOS
Committed ariadne/ios/ExportOptions.plist and theseus/ios/ExportOptions.plist:
method:app-store-connectteamID:8P59575P2KmanageAppVersionAndBuildNumber:false
Register App IDs com.sourcectl.ariadne and com.sourcectl.theseus in Apple Developer first (close-out OA-AT-ariadne-apple / OA-AT-theseus-apple). Create App Store Connect apps Ariadne Indoor Navigation / Theseus for SafeCall, CZ-only. Theseus has no public listing. iOS deployment target is 15.0 (Xcode archive will not accept 13.0).
Release process
Ariadne
bash
bun release:ariadne # interactive
bun release:ariadne patch # prompts for message only
bun release:ariadne minor "Add QR scanning" # non-interactivePipeline:
- Server tests
- Contract tests
- Flutter analyze
- Flutter tests
- Version bump in
pubspec.yaml - Signed Android AAB →
releases/ariadne.aab - Signed iOS IPA (macOS) →
releases/Ariadne.ipa - Git commit, tag (
ariadne-v{version}), push - Print Internal upload checklist (not Production)
Upload AAB to Play Internal testing and IPA with Transporter to TestFlight Internal only. See Ariadne operator close-out.
Theseus
bun release:theseus builds three values into the app. It reads them from the shell environment and refuses to build without them. None of them go into git.
| Variable | Where it is | Looks like |
|---|---|---|
THESEUS_UNLOCK_HASH | NAS secrets/theseus/unlock.hash (File Browser http://192.168.178.213:8080) | One line, $argon2id$v=19$m=… |
THESEUS_OPS_SERVER_URL | Not a secret: the company notify host. Also in NAS secrets/theseus/ops/token_label_notes.txt | https://notify.wantok.cz |
THESEUS_OPS_SUBSCRIBE_AUTH | Apple Passwords, item NTFY with username safecall-ops-subscriber, Notes field: the tk_… access token. The password field is that user's ntfy login password and gets a 401. Not the safecall-token-manager item | tk_ and letters and digits. Bearer in front is optional |
The hash is full of $. Typed inside double quotes (export THESEUS_UNLOCK_HASH="$argon2id$v=19…"), zsh replaces $argon2id, $v and the rest with nothing. The build still passes, and nobody can unlock the app. Load the values with read as below: the $ stay as they are, nothing shows on screen, and nothing goes into the shell history.
Start on an up-to-date, clean
master. The script runsgit add ., so it commits anything left in the tree:bashcd /Users/tmin/Code/active/safecall git pull git status --untracked-files=allIn the same terminal, load the three values. They live only in this terminal window. If you close it, load them again.
Copy the one line of
secrets/theseus/unlock.hashin File Browser. Run this, paste the line, press Enter:bashread -rs 'THESEUS_UNLOCK_HASH?Unlock hash: ' && export THESEUS_UNLOCK_HASH && echobashexport THESEUS_OPS_SERVER_URL=https://notify.wantok.czIn Apple Passwords, open NTFY /
safecall-ops-subscriberand copy thetk_…token from the Notes field, not the password field. Run this, paste it, press Enter:bashread -rs 'THESEUS_OPS_SUBSCRIBE_AUTH?Ops token: ' && export THESEUS_OPS_SUBSCRIBE_AUTH && echoTo download
unlock.hashinstead, save it outside the repo (for example~/Downloads), load it withexport THESEUS_UNLOCK_HASH="$(cat ~/Downloads/unlock.hash)", and delete it after the release.Check the values before the long build. Neither command prints a secret:
bash[[ $THESEUS_UNLOCK_HASH == '$argon2id$v=19$'* ]] && echo "hash ok" || echo "hash WRONG" t=${THESEUS_OPS_SUBSCRIBE_AUTH#Bearer }; [[ $t == tk_* && $t != *[^[:alnum:]_]* ]] && echo "token ok" || echo "token WRONG" curl -s -o /dev/null -w '%{http_code}\n' \ -H "Authorization: Bearer ${THESEUS_OPS_SUBSCRIBE_AUTH#Bearer }" \ "$THESEUS_OPS_SERVER_URL/safecall-ops/json?poll=1"All three must pass:
hash ok,token ok, and200fromcurl. The notify host also answers200to an empty token (it lets anonymous readers in), socurlalone does not prove the token.401means the host does not know the token: the password field instead of the Notes field, the wrong item, or a token revoked on hetest2 (sudo ntfy token list safecall-ops-subscriber).Run the release with
patch,minorormajorand a message:bashbun release:theseus minor "Internal test build for RTLS field test"It runs Flutter analyze and tests, bumps
theseus/pubspec.yaml, builds the signed AAB and IPA, then commits, tagstheseus-v{version}and pushes. Do not edit files while it runs. Signing comes from the gitignoredtheseus/android/key.propertiesandupload-keystore.jks(NASsecrets/theseus/android/) and the Apple Distribution certificate in Keychain.Check that both files have today's date:
bashls -l releases/theseus.aab releases/Theseus.ipaUpload by hand. The script uploads nothing. Internal tracks only:
- Android. Play Console → Theseus for SafeCall → Test and release → Testing → Internal testing → Create new release. Upload
releases/theseus.aab, Save, Roll out to Internal testing. Never Production. - iOS. Transporter: drag in
releases/Theseus.ipa, Deliver. App Store Connect → Theseus for SafeCall → TestFlight: wait until the build is no longer Processing, then add it to the Internal Testing group App Store Connect Users. If it asks about compliance: encryption Yes, exempt Yes, other cryptography No. No External Testing, no Submit for Review.
- Android. Play Console → Theseus for SafeCall → Test and release → Testing → Internal testing → Create new release. Upload
Install from Play or TestFlight and unlock with the Apple Passwords item Wantok staff passphrase @ Theseus (not
theseus-dev). The ops inbox must not say Ops not configured. Then close the terminal, or:bashunset THESEUS_UNLOCK_HASH THESEUS_OPS_SUBSCRIBE_AUTH
First-time setup (App IDs, keystore, Play app) is in the Theseus operator close-out.
Contract tests
bash
bun test:contracts- Phase 1 (Bun): in-memory test server, seed navigation data, write
ariadne/test/fixtures/*.json - Phase 2 (Dart): deserialize with Dart models
Files reference
| File | Purpose |
|---|---|
scripts/release/ariadne.ts | Ariadne Internal pipeline |
scripts/release/theseus.ts | Theseus Internal pipeline |
scripts/release/at-signing.ts | Fail-closed keystore / ExportOptions / Theseus defines |
scripts/build/ariadne.sh | Ariadne build only (AAB + IPA) |
scripts/deploy.ts | Lab deploy (web + rsync) |
ariadne/android/key.properties.example | Gitignored key.properties template |
ariadne/ios/ExportOptions.plist | App Store Connect export (team 8P59575P2K) |