Skip to content

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):

AppNAS
Ariadne Androidsecrets/ariadne/android/ (upload-keystore.jks, key.properties)
Ariadne iOSsecrets/ariadne/iOS/
Theseus Androidsecrets/theseus/android/
Theseus iOSsecrets/theseus/iOS/

Exact portal clicks: Ariadne operator close-out. Lab host: lab-server.md.

Commands ​

CommandDescription
bun buildBuild everything (server binaries + Ariadne)
bun build:ariadneBuild just Ariadne (AAB + IPA)
bun build:serverBuild just the server binaries
bun release:ariadneFail-closed Internal pipeline (test, bump, signed AAB + IPA, commit, push)
bun release:theseusFail-closed Theseus Internal pipeline (NAS --dart-define required)
bun test:contractsRun contract tests only (server ↔ ariadne)
bun run lab:deployLab 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:ariadne if ariadne/pubspec.yaml is already 1.0.1+2 from 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.properties

Fill 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-connect
  • teamID: 8P59575P2K
  • manageAppVersionAndBuildNumber: 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-interactive

Pipeline:

  1. Server tests
  2. Contract tests
  3. Flutter analyze
  4. Flutter tests
  5. Version bump in pubspec.yaml
  6. Signed Android AAB → releases/ariadne.aab
  7. Signed iOS IPA (macOS) → releases/Ariadne.ipa
  8. Git commit, tag (ariadne-v{version}), push
  9. 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.

VariableWhere it isLooks like
THESEUS_UNLOCK_HASHNAS secrets/theseus/unlock.hash (File Browser http://192.168.178.213:8080)One line, $argon2id$v=19$m=…
THESEUS_OPS_SERVER_URLNot a secret: the company notify host. Also in NAS secrets/theseus/ops/token_label_notes.txthttps://notify.wantok.cz
THESEUS_OPS_SUBSCRIBE_AUTHApple 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 itemtk_ 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.

  1. Start on an up-to-date, clean master. The script runs git add ., so it commits anything left in the tree:

    bash
    cd /Users/tmin/Code/active/safecall
    git pull
    git status --untracked-files=all
  2. In 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.hash in File Browser. Run this, paste the line, press Enter:

    bash
    read -rs 'THESEUS_UNLOCK_HASH?Unlock hash: ' && export THESEUS_UNLOCK_HASH && echo
    bash
    export THESEUS_OPS_SERVER_URL=https://notify.wantok.cz

    In Apple Passwords, open NTFY / safecall-ops-subscriber and copy the tk_… token from the Notes field, not the password field. Run this, paste it, press Enter:

    bash
    read -rs 'THESEUS_OPS_SUBSCRIBE_AUTH?Ops token: ' && export THESEUS_OPS_SUBSCRIBE_AUTH && echo

    To download unlock.hash instead, save it outside the repo (for example ~/Downloads), load it with export THESEUS_UNLOCK_HASH="$(cat ~/Downloads/unlock.hash)", and delete it after the release.

  3. 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, and 200 from curl. The notify host also answers 200 to an empty token (it lets anonymous readers in), so curl alone does not prove the token. 401 means 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).

  4. Run the release with patch, minor or major and a message:

    bash
    bun 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, tags theseus-v{version} and pushes. Do not edit files while it runs. Signing comes from the gitignored theseus/android/key.properties and upload-keystore.jks (NAS secrets/theseus/android/) and the Apple Distribution certificate in Keychain.

  5. Check that both files have today's date:

    bash
    ls -l releases/theseus.aab releases/Theseus.ipa
  6. Upload 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.
  7. 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:

    bash
    unset 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
  1. Phase 1 (Bun): in-memory test server, seed navigation data, write ariadne/test/fixtures/*.json
  2. Phase 2 (Dart): deserialize with Dart models

Files reference ​

FilePurpose
scripts/release/ariadne.tsAriadne Internal pipeline
scripts/release/theseus.tsTheseus Internal pipeline
scripts/release/at-signing.tsFail-closed keystore / ExportOptions / Theseus defines
scripts/build/ariadne.shAriadne build only (AAB + IPA)
scripts/deploy.tsLab deploy (web + rsync)
ariadne/android/key.properties.exampleGitignored key.properties template
ariadne/ios/ExportOptions.plistApp Store Connect export (team 8P59575P2K)