Aller au contenu principal
CI/CD iOS Tutoriel

Compiler et déployer des apps iOS avec Gitea Actions

Compilez, signez et envoyez vos apps iOS vers TestFlight depuis Gitea Actions : un runner Mac auto-hébergé avec act_runner, ou des builds cloud depuis un runner Linux.

Workflow Gitea Actions compilant une app iOS signée pour TestFlight

Gitea Actions peut compiler des apps iOS, mais uniquement sur un Mac que vous enregistrez vous-même, car Gitea ne propose pas de runners hébergés et Xcode ne fonctionne que sur macOS. Les deux configurations pratiques consistent à faire tourner un Mac act_runner en mode hôte, ou à utiliser un runner Linux classique qui envoie le build iOS vers un service de build cloud. Ce guide détaille les deux approches pour une app Capacitor, avec des fichiers de workflow que vous pouvez déposer dans .gitea/workflows/.

Les équipes choisissent Gitea parce qu’elles veulent garder leur code sur une infrastructure qu’elles contrôlent. Le fait que l’app nécessite Xcode ne devrait pas changer cela. Les deux configurations ci-dessous conservent votre code source sur votre serveur.

Comment Gitea Actions exécute les jobs

Gitea Actions utilise une syntaxe compatible avec GitHub Actions, et les jobs s’exécutent sur des act_runner instances que vous enregistrez auprès de votre serveur Gitea. Chaque runner publie des labels tels que ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest. La partie après les deux-points détermine l’exécution des jobs :

  • docker://image exécute le job dans un conteneur. C’est le comportement par défaut pour les runners Linux.
  • host exécute le job directement sur la machine, avec tout ce qui y est installé.

macOS ne peut pas s’exécuter dans un conteneur Docker, donc un runner Mac doit utiliser un host label. Cela a des conséquences que nous verrons plus loin.

Deux notes de compatibilité avant de copier un workflow depuis GitHub :

  • Gitea lit .gitea/workflows/ et bascule sur .github/workflows/ quand le premier dossier n'existe pas.
  • uses: actions/checkout@v4 fonctionne car Gitea résout les actions depuis GitHub par défaut (le DEFAULT_ACTIONS_URL paramètre). Les instances air-gapped doivent mettre en miroir les actions qu'elles utilisent et les référencer par leur URL complète.

Option 1 : un runner Mac avec act_runner

Enregistrer le Mac

Téléchargez le act_runner le binaire pour macOS (arm64 pour Apple Silicon) depuis la page des releases de Gitea, puis récupérez un token d'enregistrement depuis Administration du site > Actions > Runners (au niveau de l'instance), ou depuis les paramètres de l'organisation ou du dépôt.

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

./act_runner daemon

Lancez le daemon comme launch agent sous un utilisateur dédié, afin qu'il redémarre après un reboot et ait son propre keychain de connexion.

Installer la toolchain sur l'hôte

Le mode Host signifie que le job utilise les outils du Mac. Installez :

  • Xcode 26 (requis pour les uploads sur App Store Connect depuis le 28 avril 2026) et exécutez sudo xcodebuild -license accept
  • Node.js 22 ou plus récent et Bun (Capacitor 8 nécessite Node 22)
  • Ruby et fastlane, ou utilisez un Gemfile par bundle exec
  • CocoaPods, uniquement si votre projet iOS l’utilise encore. Les nouveaux projets Capacitor 8 utilisent Swift Package Manager.

Le workflow

# .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

Le beta lane importe le certificat dans un keychain temporaire, passe le projet en signature manuelle, incrémente le numéro de build depuis TestFlight, archive et envoie le binaire. Un Fastfile complet pour un projet Capacitor se trouve dans Compiler et déployer des applications iOS avec Bitbucket Pipelines; il fonctionne sans modification sur Gitea. Pour le contexte sur fastlane, voir la guide iOS GitHub Actions.

Le coût du mode hôte

Le act_runner la documentation est claire à ce sujet : les jobs hôte ne sont pas isolés. En pratique :

  • Chaque job partage le même utilisateur, le même disque et le même keychain. Un job qui échoue en cours de route peut laisser un keychain temporaire ou un profil de provisionnement obsolète qui casse le suivant.
  • Tout workflow ciblant ce label exécute code sur ce Mac avec les accès de cet utilisateur. N’enregistrez des runners hôte que pour des dépôts de confiance, et évitez de les exécuter sur des pull requests provenant de forks.
  • DerivedData, les archives et les runtimes de simulateur grossissent jusqu’à saturer le disque.
  • Les mises à niveau d’Xcode vous incombent, et Apple augmente le minimum SDK chaque printemps.
  • One Mac builds one job at a time. Release day queues.

Si votre équipe possède déjà des Mac et que quelqu’un s’en occupe, ça fonctionne. Sinon, l’option suivante supprime le Mac de votre infrastructure.

Option 2 : compiler iOS depuis un runner Linux avec Capgo Build

Build Capgo compile et signe les apps Capacitor sur des Mac gérés par Capgo. Votre job Gitea reste sur le runner Linux que vous avez déjà. Il fait le checkout du code, compile la couche web, exécute cap sync ios, et appelle build request. Le Capgo CLI envoie le projet natif préparé, renvoie les logs Xcode dans le log du job Gitea, et sort avec un code non nul en cas d’échec.

Ce modèle s’adapte bien à Gitea auto-hébergé :

  • Aucun accès entrant. Capgo ne clone pas votre dépôt. Votre runner pousse le projet préparé via HTTPS, donc un serveur Gitea derrière un pare-feu ou un VPN fonctionne sans modification.
  • Aucun token de dépôt. Aucune connexion Git à configurer côté Capgo.
  • Votre source web reste chez vous. Seuls le dossier natif ios/ , la config Capacitor et les parties natives des plugins sont envoyés. src/, .git/, et .env Les fichiers ne le sont pas.

Configuration unique

Sur une machine de dev :

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

build init crée ou réutilise votre certificat de distribution et votre profil de provisionnement App Store, et stocke la clé API App Store Connect. Lancez un build en local pour vérifier que tout se signe :

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

Puis exportez les identifiants :

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

Ajouter des secrets à Gitea

Ouvrez Paramètres du dépôt > Actions > Secrets et ajoutez chaque clé du fichier exporté, ainsi que votre clé Capgo API :

Secret Objectif
CAPGO_TOKEN Clé Capgo API avec permission d’upload
BUILD_CERTIFICATE_BASE64 Certificat de distribution (.p12)
P12_PASSWORD Mot de passe du certificat
CAPGO_IOS_PROVISIONING_MAP_BASE64 Mapping des profils de provisionnement
APPLE_KEY_ID ID de clé API d'App Store Connect
APPLE_ISSUER_ID ID de l'émetteur d'App Store Connect
APPLE_KEY_CONTENT Base64 .p8 clé
APP_STORE_CONNECT_TEAM_ID ID d'équipe Apple

Gitea refuse les noms de secrets et de variables commençant par GITEA_ ou GITHUB_, conservez donc ces noms tels quels. Placez les valeurs non confidentielles, comme l'ID de l'application, sous Variables et lisez-les avec ${{ vars.NAME }}. Les secrets définis au niveau de l'organisation sont partagés par tous les dépôts, ce qui est pratique lorsque vous déployez plusieurs applications depuis une même équipe Apple. Supprimez le fichier exporté une fois terminé.

Release lors d'un push de tag

# .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 }}"

Notes :

  • ${{ gitea.ref_name }} est le nom de la tag. Le github.* contexte fonctionne également comme un alias, ce qui facilite le portage des workflows.
  • Gitea configure CI=true dans chaque job, afin que Capgo CLI n’attende jamais une saisie interactive.
  • Capgo incrémente le numéro de build à partir de la dernière build TestFlight, pour que deux tags consécutifs ne se chevauchent jamais.
  • Sans --submit-to-store-review, la build est déposée sur TestFlight et s’arrête là. Ajoutez le flag si vous voulez que la CI soumette aussi pour l’App Review.

La publication est désormais git tag v2.3.0 && git push origin v2.3.0. Quiconque peut pousser un v* tag peut déployer, ajoutez donc une règle de tag protégé pour v* sous Paramètres > Tags et restreignez-l’aux responsables de release.

Builds ad hoc pour les testeurs

Pour les builds QA installés directement sur des appareils enregistrés, compilez en mode ad hoc et obtenez un lien de téléchargement :

# .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

Les builds ad hoc nécessitent un profil de provisionnement ad hoc listant les appareils de test. Stockez ce profil encodé en base64 en tant que secret (ADHOC_PROFILE_BASE64 ci-dessus), décodez-le dans le job et passez-l’avec --ios-provisioning-profile. Les flags CLI ont la priorité sur les variables d’environnement, ce qui remplace donc le profil App Store uniquement pour ce build. Récupérez les UDIDs avec Recherche d'UDID iOS. Pour la plupart des testeurs, le test interne TestFlight est plus simple, car il ne nécessite pas d’UDID.

Approbation manuelle de la release

GitHub permet d’exiger des approbateurs sur un environnement. Gitea Actions n’implémente pas de règles de protection d’environnement, donc un job qui référence un environnement ne se mettra pas en pause pour approbation. Deux approches fonctionnent alors :

  1. Tags protégés. Seuls les responsables de release peuvent pousser des v* tags, et le workflow de tag déploie.
  2. Compiler, puis promouvoir. Compiler à chaque push sur main par --ios-distribution ad_hoc ou envoyer uniquement au test interne TestFlight, puis utiliser un workflow_dispatch workflow (disponible dans les versions actuelles de Gitea) pour recompiler le commit approuvé avec --submit-to-store-review.

Déployer des changements web sans nouveau binaire

La plupart des commits sur une app Capacitor ne modifient que le JavaScript, le CSS et le HTML. Rebuild iOS pour ça, c’est du temps perdu en minutes et en App Review. Avec les live updates Capgo, un seul job peut choisir le bon chemin :

      - 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 renvoie 0 si les dépendances natives correspondent à ce qui est en ligne sur le canal, et 1 si un nouveau binaire est requis. Forcez également le chemin natif lorsque des fichiers sous ios/ ou capacitor.config.* changent, car ils ne font pas partie de la comparaison des dépendances. Voir Choisir automatiquement live update ou un build natif.

Mac runner vs build cloud

act_runner sur votre Mac Capgo Build depuis Linux
Matériel Vous l’achetez et l’hébergez Aucun
Mises à niveau Xcode Manuel, par machine Géré par Capgo
Isolation entre les jobs Aucune en mode hôte Environnement de build neuf par job
Builds parallèles Un par Mac Plusieurs, depuis n’importe quel runner
Fichiers de signature Dans les secrets Gitea, décodés sur le Mac Dans les secrets Gitea, envoyés à chaque build
Fonctionne derrière un pare-feu Oui Oui, HTTPS sortant uniquement
Applications natives en Swift (sans Capacitor) Oui Non, uniquement les apps Capacitor
Limite de temps de build À votre convenance 10 minutes par build

Troubleshooting

Symptôme Corriger
Le job reste en file d'attente indéfiniment Aucun runner en ligne n'a ce label runs-on. Vérifiez la liste des runners et l'orthographe des labels.
uses: actions/checkout@v4 échoue au téléchargement Le runner ne peut pas accéder à GitHub. Dupliquez l'action sur votre Gitea et référencez-la par son URL complète.
La valeur du secret est vide dans le job Le nom commence par GITEA_/GITHUB_, ou le secret est défini sur un autre dépôt.
security: SecKeychainItemImport: MAC verification failed Incorrect .p12 mot de passe, ou un .p12 créé par OpenSSL 3 sans -legacy. Réexportez-le.
No profiles for 'com.example.app' were found Le profil dans les secrets ne correspond ni au bundle ID ni au certificat. Relancez build init et réexportez.
Upload rejeté pour la version SDK Le runner Mac a encore une version d’Xcode antérieure à 26.
cap sync ios avertit à propos de CocoaPods sur Linux C’est normal. La machine de build Capgo installe les pods.

Pour plus de modes d’échec, voir CI/CD pour Capacitor : les pièges courants et le Guide de dépannage des builds Capgo.

Résumé

Gitea Actions gère bien iOS une fois que vous avez décidé où Xcode s’exécute. Un runner Mac en mode hôte vous donne un contrôle total et une responsabilité totale. Un job Linux qui appelle Capgo Build laisse votre configuration Gitea inchangée, ne nécessite aucun accès entrant et transforme une release TestFlight en un push de tag. Quelle que soit votre option, gardez les éléments de signature dans les secrets Gitea, protégez vos tags de release et déployez les changements purement web en live updates pour que le pipeline natif ne s’exécute que lorsque c’est nécessaire.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.