OpenAIExpo2026/02/20 14:15

Automating OTA Updates: How Onespot deploys to 200+ apps without touching a laptop

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

openaijamodel: gpt-5-mini-2025-08-07

OTA更新自動化 — Onespotが200+アプリをラップトップ不要でデプロイ

Key Points

  • apps.jsonでアプリ定義を一元化
  • onescript.pyで設定とコマンドを自動化
  • GitHub ActionsとAPIでワンタップ実行

Summary

Onespotは単一のExpo/React Nativeコードベースから200以上のホワイトラベルアプリに対して、スマホからワンタップでOTA更新・ビルド・提出を行える自動化パイプラインを構築しました。キーは「どのアプリをデプロイするか」をデータ化して自動的に各種設定ファイルを生成し、CI(GitHub Actions)経由で実行することです。

Key Points

  • アプリの差分はJSONレジストリで管理:apps.jsonにslug、bundle ID、easProjectID、バージョンなどを定義しソースオブトゥルースにする。
  • 設定ファイル自動生成:onescript.pystandalone/config.jseas.jsongoogle-services.jsonstandalone/appImages.js.easignore等を生成して環境を切り替える。
  • Expo設定はコードでマッピング:app.config.jsが生成済み設定を読み込み、updates.urlruntimeVersionextra.publishedVersion等を埋めることで切替を完結させる。
  • OTA公開/ビルド呼び出しは簡易コマンド:例 npx eas-cli update --branch=main --auto、ビルドはnpx eas-cli build --platform <platform> --no-wait、自動提出は--auto-submitを付与。
  • CI化してラップトップ依存を排除:GitHub Actionsのrepository_dispatchonescript.pyをリモート実行。ワークフロー内で資格情報を設定し、実行後にクリーンアップするガードレールを必須にする。
  • モバイルからのトリガー:自社APIでGitHubの/dispatchesを叩くと、スマホアプリから安全にワークフローを起動でき、非開発者でもデプロイが可能になる。

Practical implementation notes

  • .easignoreで他アプリのアセットを除外してアップロード時間を削減するのは必須。
  • CI実行時は対話的フラグを外して--no-wait等で非対話モードにし、環境変数とシークレットで鍵・トークンを管理する。
  • onescriptにpublish/build/submitなどのコマンドを実装して、GitHubイベントのclient_payload.commandで振り分ける。
  • 将来的にはapps.jsonをソース管理からDBに移行して、運用画面から動的にアプリを作成・更新できるようにするとさらに楽になる。

Useful reminders

  • テスト環境でまずワークフローと生成ファイルの整合性を検証すること。
  • CI上での資格情報ファイルは実行後に必ず削除する(ワークフローのcleanupステップ)。
  • 大量デプロイ時は段階的ロールアウトと監視を組み合わせて巻き戻し手順を用意する。

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

openaijamodel: gpt-5-mini-2025-08-07

OTAアップデートの自動化:Onespotがノートパソコンに触れずに200以上のアプリをデプロイする方法

概要

Deploy OTA updates to 200+ React Native apps with one tap. という体験を、Onespot がどのように実現したかを解説します。この記事は Onespot の共同創業者兼CEO、Sean Cann によるゲスト投稿で、Expo の OTA Updates、GitHub Actions、そしてスマートフォンからのトリガーでマルチアプリ公開を自動化した具体的なアプローチを紹介します。


昨日、私は iPhone でボタンを1回タップして200以上のウェブとモバイルアプリにアップデートをデプロイしました。3年前なら笑っていたでしょう。以前はほとんどの Expo 開発者と同じように、ローカルでリポジトリに移動して、expo publish(現在は eas update)を実行し、数分間進行を監視していました。これをアプリごとに繰り返すのは非現実的です。

オンスポット(Onespot)は学校向けのカスタムブランドモバイルアプリを構築するプラットフォームで、各顧客に対してストアに載る独立したアプリ(アイコン、名前、スプラッシュ、bundle identifier、ストア説明など)を提供します。内部的には単一の React Native + Expo コードベースと単一の Firebase バックエンドを共有します。この設計はイテレーション速度に強力ですが、何百ものアプリへ効率よく配信するという新たなデプロイ課題を生みます。

課題:ホワイトラベル規模でのデプロイ

以前のワークフローは以下の通りでした:

  • アプリ固有の設定(bundle IDs, slug, credentials など)を更新
  • ローカルで動作確認
  • ターミナル(個人のラップトップ)で Expo の publish/build/submit を実行
  • アップデートの完了を待機
  • ストアビルドなら適切なストアにアップロードして審査申請
  • 次のアプリへ繰り返す

1アプリあたり2〜3分かかると、200アプリの更新に7〜10時間かかる計算です。小規模チームや個人開発者にはスケールしません。そこで、全アプリに対して1つのアプリにデプロイするのと同じ簡単さで修正を届けられる仕組みが必要になりました。

キーアイデア:「どのアプリをデプロイするか」はデータの問題にする

私たちの解決の基盤は、"どのアプリをデプロイするか" を手作業として扱うのをやめ、それをデータに変えることでした。apps.json という単一の JSON レジストリを作り、システム内のすべてのアプリを定義しました。これがビルドごとに変わる情報(名前、slug、bundle identifiers、EAS project IDs、ストアID、バックエンド識別子、バージョン番号など)の唯一のソースです。

apps.json の最終形は概ね次のようになります:

{
  "montessori_apps": {
    "amare": {
      "name": "Amare",
      "slug": "amaremontessori",
      "bundlePackageID": "com.seabirdapps.amaremontessori",
      "easProjectID": "9c3a7f8e-2b41-4d9e-a6c5-1234abcd9876",
      "databaseAppID": "MLvbKPmILkLvp8Cq1234",
      "appleAppID": "1234567890",
      "version": "20.0.0",
      "androidBuild": 2,
      "iosBuild": 3
    },
    "appleseed": {
      "name": "Appleseed",
      "slug": "appleseedmontessori",
      ...
    },
    ...
  }
}

設定ファイルの自動生成

このレジストリを元に、onescript.py という Python スクリプトを作成しました。apps.json の app ID(またはバッチ)を受け取り、そのアプリに必要なすべての設定ファイルを生成します。生成されるファイル例:

  • standalone/config.js:app.config.js が読み込む設定モジュール
  • eas.json:CI が適切なメタデータで build/submit できるように生成
  • google-services.json:Android 用に更新
  • standalone/appImages.js:正しいアイコン/スプラッシュ資産を指す
  • .easignore:他アプリの資産フォルダを除外して EAS のアップロードを高速化

特に .easignore は重要です。多数のアプリ資産が単一リポジトリにある場合、除外がないとビルド/アップデートが極端に遅くなります。.easignore の使い方については公式ドキュメントを参照してください。

アプリ設定の構造

Expo の設定がコードであることが、この仕組みを可能にしています。私たちの app.config.js は生成された standalone の設定を読み込み、標準の Expo フィールドにマッピングします。参考として私たちの app.config.js の例は次の通りです:

import { standaloneConfig } from "./standalone/config";
// Incremented each time we publish an OTA update
const PUBLISHED_VERSION = 692;
export default ({ config }) => ({
  ...config,
  name: standaloneConfig.name,
  version: standaloneConfig.version,
  slug: standaloneConfig.slug,
  scheme: standaloneConfig.scheme,
  ios: {
    ...config.ios,
    bundleIdentifier: standaloneConfig.bundlePackageID,
    buildNumber: `${standaloneConfig.iosBuild}`
  },
  android: {
    ...config.android,
    package: standaloneConfig.bundlePackageID
  },
  updates: {
    url: `https://u.expo.dev/${standaloneConfig.easProjectID}`
  },
  runtimeVersion: {
    policy: "sdkVersion"
  },
  extra: {
    publishedVersion: `${PUBLISHED_VERSION}`,
    databaseAppID: standaloneConfig.databaseAppID,
    eas: {
      projectId: standaloneConfig.easProjectID
    }
  }
});

この時点で「アプリを切り替える」ことは、単に standalone/config.js を更新するだけになっています。

Python での公開(publish)

最終ステップは、そのアプリを実際に publish、build、submit することです。EAS はワンライナーで簡単にしてくれます。onescript.py 内での公開は次のようになります:

def publish_app(app_id):
    app = all_apps[app_id]  # from apps.json
    write_all_files(app)   # generates all the config files
    os.system("npx eas-cli update --branch=main --auto")

ビルドやサブミットはほぼ同様ですが、最後のコマンドが異なります:

  • Build:

    os.system(f"npx eas-cli build --platform {platform} --no-wait{non_interactivity_flag_if_ci}")

  • Build + submit:

    os.system(f"npx eas-cli build --platform {platform} --auto-submit --no-wait{non_interactivity_flag_if_ci}")

また同じスクリプトで web ビルド(expo export --platform web とホスティングへのデプロイ)も行っています。Expo をウェブとして公開する方法の詳細は公式ドキュメントを参照してください。

デプロイをローカルからCIへ移動

設定生成とバッチ更新を自動化した後、次のボトルネックは「デプロイを開発者のローカル環境で実行すること」でした。ローカルでスクリプトを実行すると、作業ディレクトリが書き換えられてロックされたり、中断時に脆弱になったり、機密資格情報がローカルに残るリスクがあります。

解決策は CI 上で実行することです。我々は GitHub Actions を使っています(EAS Workflows という Expo 向けの CI/CD を使う選択肢もあります)。構成は repository_dispatch トリガーで起動される .github/workflows/onescript.yml です。これにより onescript.py の任意の関数をリモートで実行できます。簡略化した workflow の例:

name: API - Triggered Onescript Command
on:
  repository_dispatch:
    types: [onescript-command]
jobs:
  onescript:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Parse command
        run: |
          COMMAND="${{ github.event.client_payload.command || '' }}"
          echo "PARSED_COMMAND=$COMMAND" >> $GITHUB_ENV
      ( ... set up Node, Python, EAS, App Store & Google Play credentials for submissions, and other dependencies ... )
      - name: Clear build cache
        run: |
          rm -rf web-build/
          rm -rf .expo/
      - name: Execute onescript command
        run: |
          echo "Executing: python3 onescript.py ${{ env.PARSED_COMMAND }}"
          python3 onescript.py ${{ env.PARSED_COMMAND }}
        env:
          CI: true
      ( ... credentials )
      - name: Cleanup
        if: always()
        run: rm -f ( ... credentials files )

この構造により、onescript.py に関数を追加するだけで CI から任意の操作を呼び出せるようになりました。

スマホからのデプロイトリガー

GitHub リポジトリ内でクリックしてトリガーすることもできますが、さらに進めて社内 API に /trigger-onescript という認証付きエンドポイントを追加し、GitHub の REST API を呼ぶことでどこからでもワークフローを起動できるようにしました。サーバー側で安全に検証したうえで、command(例: "publish amare", "submit amare", "publish all_apps")を送ります。fetch の例:

fetch("https://api.github.com/repos/<org>/<repo>/dispatches", {
  method: "POST",
  headers: {
    Authorization: `token ${GITHUB_ACCESS_TOKEN}`,
    Accept: "application/vnd.github.v3+json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    event_type: "onescript-command",
    client_payload: { command }
  })
});

これがあれば、非開発者を含むチームメンバーが私の手を煩わせることなく、アプリ内から更新、ビルド、ストア申請を実行できます。私の仕事はその後、チーム全員がプロのハッカー気分になれるようなダッシュボードをアプリ内にデザインすることでした。

今後の展望

デプロイが API トリガーで実行できるようになると、改善の余地は一気に広がります。いくつか考えているアイデア:

  • apps.json をソース管理から外してデータベースに移す
    • これによりアプリの追加や更新がコードコミットではなく、動的なデータ操作で可能になります。バリデーション、監査ログ、権限管理も整えられます。
  • AI エージェント(例: Cursor の API)をデプロイプロセスやアプリに直接連携
    • チームが自然言語で変更を記述すると、コード生成や設定更新、デプロイ操作までつなげられる可能性があります。

この投稿では、Onespot がどのようにして複数アプリの OTA 配信を自動化し、最終的にスマートフォンのワンクリックで200以上のアプリにデプロイできるようにしたかを説明しました。興味があれば、apps.json の管理方法や CI セットアップの詳細、onescript.py の実装のベストプラクティスについてさらに深掘りします。