メインコンテンツへスキップ

Gitea Actions で iOS アプリをビルド・デプロイ

Gitea Actions から iOS アプリをビルド、署名、TestFlight にアップロード: act_runner を使ったセルフホスト型 Mac ランナー、または Linux ランナーからのクラウドビルド。

TestFlight用の署名済みiOSアプリをビルドするGitea Actionsワークフロー

Gitea ActionsはiOSアプリのビルドに対応していますが、Giteaにはホスト型ランナーがなく、XcodeはmacOSでのみ動作するため、自分で登録したMacでのみ実行できます。現実的な構成は2つあり、1つはMacを act_runner ホストモードで実行する構成、もう1つは通常のリナックスランナーからiOSビルドをクラウドビルドサービスに送信する構成です。本ガイドでは、Capacitorアプリの両方の構成について、そのまま .gitea/workflows/.

チームがGiteaを選ぶのは、自分たちで管理するインフラ上に code を置きたいからです。アプリにXcodeが必要になったからといって、そこが変わるべきではありません。以下の2つの構成はどちらも、ソースコードを自社サーバーに保持します。

Gitea Actionsによるジョブの実行方法

Gitea Actions は GitHub Actions 互換の構文を使い、ジョブは act_runner Giteaサーバーに登録したインスタンス上で実行されます。各ランナーは次のようなラベルを公開します: ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest。コロンより後で、ジョブの実行方法が決まります:

  • docker://image はコンテナ内でジョブを実行します。Linuxランナーではこれがデフォルトです。
  • host はマシンにインストールされているものを使って、ジョブを直接実行します。

macOS は Docker コンテナ内で実行できないため、Mac ランナーは host ラベルを使う必要があります。これには後述する注意点があります。

ワークフローを GitHub からコピーする前に、互換性の注意点が2つあります:

  • Giteaは .gitea/workflows/ を読み取り、なければ次にフォールバックします: .github/workflows/ 最初のフォルダが存在しない場合。
  • uses: actions/checkout@v4 デフォルトで GitHub からアクションを解決するため動作します( DEFAULT_ACTIONS_URL 設定)。エアギャップ環境では、使用するアクションをミラーリングし、完全なURLで参照する必要があります。

オプション1: act_runner を備えた Mac ランナー

Mac の登録

ダウンロード act_runner Gitea のリリースページから macOS 用バイナリ(Apple Silicon なら arm64)をダウンロードし、 サイト管理 > Actions > Runners (インスタンス全体)または、組織またはリポジトリの設定から登録トークンを取得します。

./act_runner register --no-interactive \
  --instance https://gitea.example.com \
  --token <registration_token> \
  --name mac-mini-01 \
  --labels macos-arm64:host

./act_runner daemon

デーモンを専用ユーザーの launch agent として実行し、再起動後に自動で再起動されるようにし、独自のログインキーチェーンを持たせます。

ホストにツールチェーンをインストール

ホストモードでは、ジョブが Mac 自身のツールを使用します。以下をインストールします:

  • Xcode 26(2026 年 4 月 28 日以降の App Store Connect アップロードに必須)をインストールし、 sudo xcodebuild -license accept
  • Node.js 22 以降と Bun(Capacitor 8 は Node 22 が必要)
  • Ruby と fastlane、または Gemfile を使用して bundle exec
  • CocoaPods は、iOS プロジェクトがまだ使っている場合のみ。新しい Capacitor 8 プロジェクトは Swift Package Manager を使います。

ワークフロー

# .gitea/workflows/ios-mac.yaml
name: iOS release (Mac runner)

on:
  push:
    tags:
      - 'v*'

jobs:
  ios:
    runs-on: macos-arm64
    env:
      APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
      APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
      APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
      P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
      APP_STORE_CONNECT_TEAM_ID: ${{ vars.APP_STORE_CONNECT_TEAM_ID }}
      PROFILE_NAME: ${{ vars.IOS_PROFILE_NAME }}
    steps:
      - uses: actions/checkout@v4
      - run: bun install --frozen-lockfile
      - run: bun run build
      - run: bunx cap sync ios
      - name: Decode signing files
        run: |
          echo "${{ secrets.IOS_CERTIFICATE_BASE64 }}" | base64 --decode > dist.p12
          echo "${{ secrets.IOS_PROFILE_BASE64 }}" | base64 --decode > profile.mobileprovision
      - run: bundle exec fastlane ios beta
      - name: Clean up
        if: always()
        run: rm -f dist.p12 profile.mobileprovision

この beta lane は証明書を一時キーチェーンにインポートし、プロジェクトを手動署名に切り替え、TestFlight のビルド番号をインクリメントしてアーカイブし、アップロードします。Capacitor プロジェクト向けの完全な Fastfile は Bitbucket Pipelines で iOS アプリをビルド・デプロイにあります。Gitea でもそのまま動作します。fastlane の背景については、 GitHub Actions iOS ガイド.

ホストモードのコスト

この act_runner ドキュメントはこの点について率直に述べています:ホストジョブは分離されていません。実際には:

  • すべてのジョブが同じユーザー、ディスク、キーチェーンを共有します。途中で失敗したジョブが一時キーチェーンや古いプロビジョニングプロファイルを残し、次のジョブを壊すことがあります。
  • そのラベルを対象とするワークフローは、その Mac 上でそのユーザーの権限で code を実行します。ホストランナーは信頼できるリポジトリにのみ登録し、フォークからのプルリクエストでは実行しないでください。
  • DerivedData、アーカイブ、シミュレータランタイムが肥大化し、ディスクがいっぱいになります。
  • Xcodeのアップグレードは自分で行う必要があり、Appleは毎年春に最低限のSDKを引き上げます。
  • 1台のMacは一度に1つのジョブしかビルドできません。リリース日はキューが溜まります。

チームがすでにMacを運用し、管理者がいるならこれで大丈夫です。そうでなければ、次のオプションでインフラからMacを排除できます。

オプション2: Capgo BuildでLinuxランナーからiOSをビルド

Capgo のビルド はCapgoが管理するMac上でCapacitorアプリをコンパイル・署名します。Giteaジョブは既存のLinuxランナー上で実行されます。codeをチェックアウトし、Webレイヤーをビルドして cap sync iosを実行し、 build requestを呼び出します。Capgo CLIは準備されたネイティブプロジェクトをアップロードし、XcodeログをGiteaジョブログにストリーミングし、失敗時は非ゼロで終了します。

このモデルはセルフホストGiteaに最適です:

  • 入站アクセス不要。 Capgoはリポジトリをクローンしません。ランナーがHTTPS経由で準備されたプロジェクトをプッシュするため、ファイアウォールやVPNの背後にあるGiteaサーバーも変更なしで動作します。
  • リポジトリトークンなし。 Capgo 側で設定する Git 接続はありません。
  • Web ソースはローカルに留まります。 ネイティブの ios/ フォルダ、Capacitor 設定、およびプラグインのネイティブ部分のみがアップロードされます。 src/, .git/や .env ファイルはアップロードされません。

ワンタイムセットアップ

開発者マシン上で:

bunx @capgo/cli@latest login
bunx @capgo/cli@latest app add
bunx @capgo/cli@latest build init --platform ios

build init 配布証明書と App Store プロビジョニングプロファイルを新規作成または再利用し、App Store Connect の API キーを保存します。すべてが署名されることを確認するために、ローカルで一度ビルドを実行してください:

bun run build && bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release

その後、認証情報をエクスポートします:

bunx @capgo/cli@latest build credentials manage --appId com.example.app --platform ios
# select "Export to .env"

Gitea にシークレットを追加

開く リポジトリ設定 > Actions > Secrets エクスポートしたファイルの各キーと、Capgo API キーを追加します:

Secret 目的
CAPGO_TOKEN アップロード権限を持つ Capgo API キー
BUILD_CERTIFICATE_BASE64 配布証明書 (.p12)
P12_PASSWORD 証明書パスワード
CAPGO_IOS_PROVISIONING_MAP_BASE64 プロビジョニングプロファイルの紐付け
APPLE_KEY_ID App Store Connect API キーID
APPLE_ISSUER_ID App Store Connect の発行者 ID
APPLE_KEY_CONTENT Base64 .p8 キー
APP_STORE_CONNECT_TEAM_ID AppleチームID

Gitea は、次の文字で始まるシークレット名と変数名を拒否します。 GITEA_ または GITHUB_そのため、これらの名前はそのままにしてください。app ID などのシークレットではない値は、 変数 に設定し、次のコードで読み取ります ${{ vars.NAME }}。組織レベルで設定されたシークレットは、その組織内のすべてのリポジトリで共有されるため、1つの Apple チームから複数のアプリをリリースする場合に便利です。作業が終わったら、エクスポートしたファイルを削除してください。

タグ push でリリース

# .gitea/workflows/ios-release.yaml
name: iOS release

on:
  push:
    tags:
      - 'v*'

jobs:
  release-ios:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - run: bun run build
      - run: bunx cap sync ios
      - name: Build, sign, and upload to TestFlight
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
        run: |
          bunx @capgo/cli@latest build request ${{ vars.APP_ID }} \
            --platform ios \
            --build-mode release \
            --ios-distribution app_store \
            --store-release-name "${{ gitea.ref_name }}"

注意:

  • ${{ gitea.ref_name }} がタグ名です。 github.* context もエイリアスとして使えるため、ワークフローを移植する際に便利です。
  • Gitea は CI=true すべてのジョブで設定するため、Capgo CLI は対話入力を待ちません。
  • Capgo は最新の TestFlight ビルドからビルド番号をインクリメントするため、連続する 2 つのタグが衝突することはありません。
  • がないと --submit-to-store-review、ビルドは TestFlight に届き、そこで停止します。CI で App Review にも提出したい場合は、フラグを追加してください。

リリースはこれで git tag v2.3.0 && git push origin v2.3.0。push できる人は誰でも v* tag を push してリリースできます。そのため、 v* 内の 設定 > タグ リリース管理者だけに制限できます。

テスター向け Ad hoc ビルド

登録済みデバイスに直接インストールする QA ビルドには、ad hoc モードでビルドしてダウンロードリンクを取得します:

# .gitea/workflows/ios-adhoc.yaml
name: iOS ad hoc build

on:
  workflow_dispatch:

jobs:
  adhoc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile && bun run build
      - run: bunx cap sync ios
      - name: Ad hoc build
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
        run: |
          echo "${{ secrets.ADHOC_PROFILE_BASE64 }}" | base64 --decode > adhoc.mobileprovision
          bunx @capgo/cli@latest build request ${{ vars.APP_ID }} \
            --platform ios \
            --ios-distribution ad_hoc \
            --ios-provisioning-profile ./adhoc.mobileprovision \
            --output-upload --output-retention 3d \
            --output-record /tmp/build.json
          bunx @capgo/cli@latest build last-output --path /tmp/build.json --field outputUrl

Ad hoc ビルドには、テストデバイスを記載した ad hoc プロビジョニングプロファイルが必要です。そのプロファイルを base64 でエンコードしてシークレット(ADHOC_PROFILE_BASE64 上記)に保存し、ジョブ内でデコードして次のように渡します: --ios-provisioning-profile。 CLI フラグは環境変数より優先されるため、このビルドに限り App Store 用プロファイルを上書きします。UDID は次で収集します: iOS UDID 検索ツール。ほとんどのテスターにとっては、UDID が不要な TestFlight の内部テストの方が簡単です。

手動リリース承認

GitHub を使うと、環境にレビュアーを必須にできます。Gitea Actions は環境保護ルールを実装していないため、環境を指定したジョブでも承認待ちで停止しません。代わりに次の 2 つのパターンが使えます:

  1. 保護されたタグ。 リリースマネージャーだけがタグをpushでき、 v* タグのワークフローが配信します。
  2. ビルドして、プロモート。 pushのたびにビルドするか、 main を使用して --ios-distribution ad_hoc TestFlightの内部テストにのみアップロードし、その後 workflow_dispatch ワークフロー(現在のGiteaリリースで利用可能)を使って、承認されたコミットを再ビルドします。 --submit-to-store-review.

新しいバイナリなしでWebの変更を配信

Capacitor アプリへのコミットの多くは、JavaScript、CSS、HTMLだけを変更します。そのたびにiOSを再ビルドするのは、数分とApp Reviewの時間を無駄にします。これなら、 Capgo Live Updates1つのジョブで適切なパスを選べます:

      - name: Live update or native build
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
          # plus the iOS signing secrets from above
        run: |
          if bunx @capgo/cli@latest build needed ${{ vars.APP_ID }} --channel production; then
            bunx @capgo/cli@latest bundle upload ${{ vars.APP_ID }} --channel production
          else
            bunx cap sync ios
            bunx @capgo/cli@latest build request ${{ vars.APP_ID }} --platform ios --build-mode release
          fi

build needed ネイティブ依存関係がチャネル上のライブ状態と一致する場合に 0、新しいバイナリが必要な場合に 1 を返します。また、以下のファイルが変更された場合はネイティブパスを強制します。 ios/ または capacitor.config.* これらは依存関係の比較対象に含まれないためです。詳細は live update か Native Build を自動選択.

Macランナー vs クラウドビルド

Mac上のact_runner LinuxからのCapgo Build
ハードウェア 購入してホストする なし
Xcode アップグレード 手動、マシンごと Capgo が処理
ジョブ間の分離 ホストモードではなし ジョブごとに新しいビルド環境
並列ビルド Mac ごとに 1 つ 任意のランナーから複数
署名ファイル Gitea のシークレットに保存、Mac でデコード Giteaのシークレットに、ビルドごとに送信
ファイアウォールの背後でも動作 はい はい、アウトバウンドHTTPSのみ
ネイティブSwiftアプリ(Capacitorなし) はい いいえ、Capacitorアプリのみ
ビルド時間制限 設定はあなた次第 1ビルドあたり10分

トラブルシューティング

症状 解決策
ジョブが永遠にキューに留まる オンラインランナーにラベルがありません runs-on。ランナーリストとラベルのスペルを確認してください。
uses: actions/checkout@v4 ダウンロードに失敗します ランナーが GitHub に接続できません。Gitea上でアクションをミラーリングし、完全なURLで参照してください。
ジョブ内のシークレット値が空です 名前の先頭が GITEA_/GITHUB_、またはシークレットが別のリポジトリで定義されています。
security: SecKeychainItemImport: MAC verification failed 誤り .p12 パスワード、または .p12 OpenSSL 3 によって、なしで作成されました -legacy。再エクスポートしてください。
No profiles for 'com.example.app' were found シークレットのプロファイルがバンドル ID または証明書と一致しません。再実行してください build init 再エクスポートしてください。
SDK バージョンのアップロードが拒否されました Mac ランナーにはまだ 26 より古い Xcode がインストールされています。
cap sync ios Linux での CocoaPods について警告します 想定内の動作です。Capgo ビルドマシンが pods をインストールします。

その他の失敗モードについては、 Capacitor の CI/CD:よくある落とし穴 および Capgo Build トラブルシューティングガイドにあります.

まとめ

Gitea Actions は、Xcode をどこで実行するかを決めれば iOS にも対応します。ホストモードの Mac ランナーなら完全な制御と責任が得られます。Linux ジョブから Capgo Build を呼び出す場合、Gitea の構成はそのまま、入站アクセスも不要で、TestFlight リリースをタグプッシュに置き換えられます。どちらを選ぶ場合も、署名情報は Gitea のシークレットに保管し、リリースタグを保護し、Web 限定の変更はライブアップデートとして配信し、ネイティブパイプラインは必要なときだけ実行されるようにしてください。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。