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://imageexécute le job dans un conteneur. C’est le comportement par défaut pour les runners Linux.hostexé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@v4fonctionne car Gitea résout les actions depuis GitHub par défaut (leDEFAULT_ACTIONS_URLparamè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
Gemfileparbundle 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.envLes 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. Legithub.*contexte fonctionne également comme un alias, ce qui facilite le portage des workflows.- Gitea configure
CI=truedans 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 :
- Tags protégés. Seuls les responsables de release peuvent pousser des
v*tags, et le workflow de tag déploie. - Compiler, puis promouvoir. Compiler à chaque push sur
mainpar--ios-distribution ad_hocou envoyer uniquement au test interne TestFlight, puis utiliser unworkflow_dispatchworkflow (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.