Progressive Rollout and Geo-Targeting
The tk-ota-push CLI and the OTA server provide three complementary levers to control patch delivery: percentage-based progressive rollout (Canary Rollout), ISO-3166-1 alpha-2 country targeting (Geo-Fencing), and network/urgency update strategies (Wi-Fi required vs. critical forced update).
1. Percentage-Based Progressive Rollout (--rollout)
On the tk-ota-push patch command, the --rollout option sets the initial percentage of devices eligible to receive the patch:
# Initially roll out to 10% of the fleet
tk-ota-push patch 1.0.0+3 -p android --rollout 10 -m "10% Canary new checkout flow"
Validation Rules in runner.dart
- Default value:
'100'(100%). - Allowed values: any integer between
0and100inclusive. - If the value is not a valid integer or falls outside
0..100, the CLI exits immediately with code64:--rollout doit être compris entre 0 et 100. - Setting
--rollout 0lets you publish and sign a patch on the server without exposing it to the general public, while still delivering it to devices enrolled in the QA Canary Whitelist (allowed_client_ids).
Dynamic Adjustment Without Recompilation
After publishing a patch, you can increase or decrease its rollout percentage (10% -> 50% -> 100%) from the Web Console or via the Admin REST endpoint:
curl -X POST "$OTA_SERVER_URL/api/admin/v1/patches/$PATCH_ID/rollout?rollout_percentage=50" \
-H "Authorization: Bearer $OTA_API_KEY"
2. Geographic Country Targeting (--countries)
The --countries option on tk-ota-push patch restricts patch distribution to a comma-separated list of ISO-3166-1 alpha-2 (2-letter) country codes:
# Target only Côte d'Ivoire, Senegal, and Cameroon
tk-ota-push patch 1.0.0+3 -p android --countries "CI,SN,CM" -m "Enable regional payment provider"
# Force worldwide deployment (overrides any default configured in shorebird.yaml)
tk-ota-push patch 1.0.0+3 -p android --countries "all" -m "Global hotfix"
Exact Resolution Logic (Publisher.parseCountries & FlutterProject.defaultCountries)
- Explicit CLI Flag:
--countriesflag passed to the command (highest priority). - Local Configuration: Defined in
shorebird.yamlundergeo.default_countries. - Global Default: Worldwide distribution (
null= unrestricted geo delivery).
You can define default target countries in shorebird.yaml using either of the two formats supported by FlutterProject.defaultCountries:
# Format 1: YAML list under geo.default_countries
geo:
default_countries:
- CI
- SN
- CM
# Format 2: CSV string under rollout.countries
rollout:
countries: "BJ, TG, FR"
When running patch, the CLI explicitly prints the effective geographic scope:
Ciblage géographique : 🌍 Mondial (Tous pays)Ciblage géographique : 🎯 Pays cibles : CI, SN, CM
To update the allowed countries of an existing patch without recompiling:
curl -X POST "$OTA_SERVER_URL/api/admin/v1/patches/$PATCH_ID/countries" \
-H "Authorization: Bearer $OTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"countries": ["CI", "SN", "CM", "BF"]}'
3. Update Strategies: Silent (Wi-Fi) vs. Critical Forced
The client-side update behavior combines OTA server strategy flags, the RemoteConfigGatekeeper payload, and TkOtaPush.init SDK parameters:
| Mode / Setting | Server Side (/patches/{id}/strategy) | Remote Config Payload | Flutter SDK (TkOtaPush.init) | Device Behavior |
|---|---|---|---|---|
| Silent Data-Saver (default) | forceCriticalUpdate: false | "force_update": false | requireWifiForSilentUpdate: false, minBatteryPercentageForSilentUpdate: 20 | Downloads in the background (silentBackgroundUpdate) when battery >= 20% (or charging). Applied on next cold start. |
| Silent Wi-Fi Only | requireWifi: true | "force_update": false | requireWifiForSilentUpdate: true | If on cellular data (3G/4G/5G), defers the download via workmanager (deferredToBackgroundTask) until connected to unmetered Wi-Fi. |
| Critical Forced (Emergency) | forceCriticalUpdate: true | "force_update": true | onCriticalUpdateRequired callback | Bypasses Wi-Fi restrictions, immediately invokes the blocking UI modal (criticalForcedUpdate), downloads the patch, and requires a restart (restartRequired: true). |
Updating a Patch Strategy via the Control Plane API
During initial submission (POST /api/admin/v1/patches), the server accepts the multipart field force_critical_update (false by default). Afterward, you can toggle Wi-Fi requirements and critical urgency from the Web Console or via POST /api/admin/v1/patches/{patchId}/strategy:
curl -X POST "$OTA_SERVER_URL/api/admin/v1/patches/$PATCH_ID/strategy" \
-H "Authorization: Bearer $OTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"forceCriticalUpdate": true,
"requireWifi": false
}'
If a patch is in ROLLED_BACK status, any attempt to modify its strategy (/strategy) or geographic targeting (/countries) is rejected by the server with HTTP 409 Conflict (VERROUILLAGE DE SÉCURITÉ).