Skip to main content

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 0 and 100 inclusive.
  • If the value is not a valid integer or falls outside 0..100, the CLI exits immediately with code 64:
    --rollout doit être compris entre 0 et 100.
  • Setting --rollout 0 lets 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)​

Country Targeting Precedence
  1. Explicit CLI Flag: --countries flag passed to the command (highest priority).
  2. Local Configuration: Defined in shorebird.yaml under geo.default_countries.
  3. 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 / SettingServer Side (/patches/{id}/strategy)Remote Config PayloadFlutter SDK (TkOtaPush.init)Device Behavior
Silent Data-Saver (default)forceCriticalUpdate: false"force_update": falserequireWifiForSilentUpdate: false, minBatteryPercentageForSilentUpdate: 20Downloads in the background (silentBackgroundUpdate) when battery >= 20% (or charging). Applied on next cold start.
Silent Wi-Fi OnlyrequireWifi: true"force_update": falserequireWifiForSilentUpdate: trueIf 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": trueonCriticalUpdateRequired callbackBypasses 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
}'
Security Lock on Rolled-Back Patches

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É).