Signet's build pipeline is a set of GitHub Actions workflows under
.github/workflows/. All jobs run on GitHub-hosted runners; no
self-hosted infrastructure.
Triggers: every push to main, every pull request, manual dispatch.
Does: flutter analyze + flutter test --coverage. Uploads
coverage/lcov.info as a 14-day artifact. Fails on any analyzer
info/warning/error or any failing test.
Cost: ~3–5 minutes per run, Linux runner (1× multiplier). Runs on every PR — keep it cheap.
Triggers: push to main, version tags (v*.*.*), manual dispatch.
Does: flutter build appbundle --release + flutter build apk --release
with the default debug-signing fallback (see android/app/build.gradle.kts).
Uploads both artifacts + SHA-256 checksums as a 30-day artifact. Both formats
are produced because the release channels need different things:
AAB is required for Play Console (new-app submissions since Aug 2021),
APK is used by F-Droid + direct sideload + the owner's on-device smoke test.
Cost: ~8–12 minutes per run (first run is slower due to Gradle + NDK downloads; subsequent runs hit the cache). Linux runner.
Intentionally debug-signed so we can run this on every main-branch
push without exposing production signing secrets. Signed artifacts
only fire on version tags via release.yml.
Triggers: push to main, version tags, manual dispatch.
Does: flutter build ios --release --no-codesign on a macos-latest
runner. Uploads the resulting Runner.app.zip + SHA-256 as a 30-day
artifact. Does NOT produce an IPA (that needs Apple Developer
credentials + provisioning profile setup).
Cost: ~15–25 minutes per run on macOS (10× multiplier on the
runner-minute bill). Intentionally does NOT run on every PR to
conserve minutes — only main pushes and tags.
Does NOT run TestFlight uploads. Signed iOS distribution is a post-v1.0 concern; this workflow exists to prove the iOS toolchain compiles our source.
Triggers: nightly (05:00 UTC), manual dispatch, pull requests that
change integration_test/ or this workflow, and release.yml on every
version tag (workflow_call).
Does: flutter test integration_test on two emulators, API 28
(minSdk) and API 34, google_apis x86_64 images on ubuntu-22.04 via
reactivecircus/android-emulator-runner with KVM. These tests use the
real Android Keystore behind flutter_secure_storage, the real
clipboard channel and the whole app, which the unit and widget tests in
ci.yml replace with fakes:
secure_store_harness_test.dart: a relationship round-trips through the Keystore; a v1 single-slot record migrates to the keyed store; no label or secret appears in plaintext (raw, base64 or base64url) in any file of the app's data directory, checked against a planted copy so the scan cannot pass by reading nothing.secure_clipboard_test.dart: a secret copy pastes as plain text, is cleared after a minute and not before, a newer copy is left alone, and a leftover is cleared at the next start. The copy-preview screenshot and the background test need a person at the device; they run only with--dart-define=SIGNET_MANUAL_STEPS=true.restore_and_verify_test.dart: restores a bulk backup made by 0.3.6 through the UI, then verifies both contacts with words computed from the backup's known secrets, and a wrong set of words fails.
On failure the emulator's logcat is uploaded as a 14-day artifact.
Limits: debug builds only (flutter test cannot run an R8 release
build), and no upgrade from an older Signet; both stay manual
pre-release checks.
Cost: about 10 to 15 minutes per API level, Linux runner (1× multiplier); the clipboard tests alone wait out three one-minute timers. Not run on every PR for that reason.
Triggers: version tags matching v<major>.<minor>.<patch> or
v<major>.<minor>.<patch>-<prerelease> (e.g. v0.2.0-alpha.1).
Does:
- Android signed build — decodes the production keystore from
ANDROID_KEYSTORE_BASE64, writesandroid/key.propertiespointing at it, runsflutter build appbundle --release+flutter build apk --release. Verifies BOTH outputs were NOT signed with the Android debug key (safety check runs against the AAB and the APK independently). Wipes the keystore +key.propertiesfrom the workspace in analways()step before the runner terminates. - iOS unsigned build — same as
ios-build.yml. - Instrumented tests:
instrumented-tests.ymlas a reusable workflow. Publishing waits for it and is skipped if it fails. - Publish — creates / updates a GitHub Release at the tag with
both artifacts attached, auto-generated body (SHA-256s + verification
command + tag message), marked as prerelease if the tag contains
a hyphen (
-alpha,-beta,-rc).
Set these in Settings → Secrets and variables → Actions on GitHub:
| Secret | Value |
|---|---|
ANDROID_KEYSTORE_BASE64 |
base64 -w0 ~/keystores/signet-release.jks |
ANDROID_KEYSTORE_PASSWORD |
The -storepass from keytool. |
ANDROID_KEY_ALIAS |
The -alias from keytool (e.g. signet). |
ANDROID_KEY_PASSWORD |
The -keypass (often same as storepass). |
See docs/RELEASE.md (Phase 12.16, to come) for the full keystore
generation and release-tagging playbook.
The Flutter version is pinned in several places, and Renovate manages
none of them. The source of truth is the flutter-version: 'X.Y.Z'
literal on the Android build step of release.yml: F-Droid's prebuild
extracts it with a sed one-liner (see
metadata/dev.digitalgrease.signet.yml), which must match exactly one
line, so keep that literal format. To upgrade:
- Upgrade locally (
flutter upgrade, or check out the new tag in your Flutter SDK), run the tests, regeneratepubspec.lockwith the new Flutter. - Change every pin to the new version:
env.FLUTTER_VERSIONinci.yml,android-build.yml,ios-build.yml,instrumented-tests.yml,jdk-canary.ymlandrelease.yml;- the quoted
flutter-version:literal inrelease.yml(the one F-Droid reads) and incodeql.yml, pluscodeql.yml'scache-key; - the prose in
README.mdanddocs/IOS_VALIDATION.md.
scripts/check-flutter-version.sh(run byci.ymlon every PR) fails if any workflow disagrees withrelease.yml's literal.- The first CI run after the bump rebuilds the Flutter cache; later runs hit the new cache.
The workflows are not merged into one shared config: GitHub Actions has no clean include mechanism short of reusable workflows, and each workflow's triggers differ enough that sharing would leak concerns.
Java compiles with a JDK 21 toolchain (android/app/build.gradle.kts,
applied to the plugin modules in android/build.gradle.kts), the JDK on
F-Droid's buildserver; do not raise it without checking what F-Droid
ships. CI, release and CodeQL also run Gradle on JDK 21 (JAVA_VERSION).
jdk-canary.yml runs Gradle on the newest GA JDK weekly so a Gradle,
AGP or jlink break shows up before it reaches a developer.
scripts/check-apk-manifest.sh runs on the release APK in
android-build.yml and release.yml. It fails if backup is enabled,
INTERNET is requested, any permission is outside the allowlist
(CAMERA plus androidx's own receiver permission), or any hardware
feature is required. android-build.yml also runs on PRs that touch
android/, the pubspec files or the check itself, so Gradle and plugin
bumps are built and checked before they merge.
- iOS signed IPA — needs Apple Developer credentials, provisioning
profiles,
EXPORT_OPTIONS.plist. Post-v1.0. - TestFlight upload — same.
- Play Store internal-track upload — lands when Task 12.18 scaffolds the listing metadata.
- F-Droid verified builds: F-Droid builds every release from
source on its own buildserver and signs it with its own key. CI
builds with the same JDK (21) but does not compare against
F-Droid's output. Verified builds (F-Droid ships our signed APK when
its rebuild matches) would need
BinariesandAllowedAPKSigningKeysin the F-Droid recipe; not set up. - Codecov / Coveralls integration — we upload coverage as an artifact only. The project stance is no external-service network dependencies in the build path.