Gitea Actions può fare build di app iOS, ma solo su un Mac che registri tu, perché Gitea non ha runner ospitati e Xcode gira solo su macOS. Le due configurazioni pratiche sono un Mac che esegue act_runner in modalità host, oppure un normale runner Linux che invia la build iOS a un servizio di build cloud. Questa guida copre entrambi gli approcci per un'app Capacitor, con file di workflow che puoi inserire in .gitea/workflows/.
Le team scelgono Gitea perché vogliono il loro code su un'infrastruttura che controllano. Questo non dovrebbe cambiare solo perché l'app richiede Xcode. Entrambe le configurazioni qui sotto mantengono il codice sorgente sul vostro server.
Come Gitea Actions esegue i job
Gitea Actions usa una sintassi compatibile con GitHub Actions, e i job vengono eseguiti su act_runner istanze che registrate sul vostro server Gitea. Ogni runner pubblica etichette come ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest. La parte dopo i due punti determina come vengono eseguiti i job:
docker://imageesegue il job in un container. È il comportamento predefinito per i runner Linux.hostesegue il job direttamente sulla macchina, con quello che c'è installato.
macOS non può girare in un container Docker, quindi un runner Mac deve usare una host label. Questo ha conseguenze che vedremo più avanti.
Due note di compatibilità prima di copiare un workflow da GitHub:
- Gitea legge
.gitea/workflows/e fa fallback su.github/workflows/se la prima cartella non esiste. uses: actions/checkout@v4funziona perché Gitea risolve le action da GitHub per impostazione predefinita (l'impostazioneDEFAULT_ACTIONS_URL). Le istanze air-gapped devono fare il mirror delle action che usano e referenziarle tramite URL completo.
Opzione 1: un runner Mac con act_runner
Registra il Mac
Scarica l'app act_runner binario per macOS (arm64 per Apple Silicon) dalla pagina di release di Gitea, quindi ottieni un token di registrazione da Amministrazione sito > Actions > Runners (a livello di istanza), oppure dalle impostazioni dell'organizzazione o del repository.
./act_runner register --no-interactive \
--instance https://gitea.example.com \
--token <registration_token> \
--name mac-mini-01 \
--labels macos-arm64:host
./act_runner daemon
Esegui il daemon come launch agent sotto un utente dedicato, così che si riavvii dopo i riavvii e abbia la propria keychain di login.
Installa la toolchain sull'host
La modalità host significa che il job usa gli strumenti del Mac stesso. Installa:
- Xcode 26 (obbligatorio per gli upload su App Store Connect dal 28 aprile 2026) ed esegui
sudo xcodebuild -license accept - Node.js 22 o superiore e Bun (Capacitor 8 richiede Node 22)
- Ruby e fastlane, oppure usa un
Gemfileconbundle exec - CocoaPods, solo se il tuo progetto iOS lo usa ancora. I nuovi progetti Capacitor 8 usano Swift Package Manager.
Il 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
Il beta lane importa il certificato in un keychain temporaneo, passa il progetto alla firma manuale, incrementa il numero di build da TestFlight, archivia e carica. Un Fastfile completo per un progetto Capacitor è in Build e deploy di app iOS con Bitbucket Pipelines; funziona senza modifiche su Gitea. Per il contesto di fastlane, consulta guida alle GitHub Actions per iOS.
Il costo della modalità host
Il act_runner la documentazione è chiara a riguardo: i job sull'host non sono isolati. In pratica:
- Ogni job condivide lo stesso utente, disco e keychain. Un job che si interrompe a metà può lasciare un keychain temporaneo o un provisioning profile obsoleto che fa fallire il successivo.
- Qualsiasi workflow che punta a quel label esegue code su quel Mac con gli accessi di quell'utente. Registra runner host solo per i repository di cui ti fidi ed evita di eseguirli su pull request da fork.
- DerivedData, archivi e simulator runtimes crescono finché il disco non è pieno.
- Gli aggiornamenti di Xcode sono a tuo carico, e Apple alza il minimo SDK ogni primavera.
- Un Mac builda un job alla volta. Code il giorno della release.
Se il tuo team ha già dei Mac e qualcuno se ne occupa, questa soluzione funziona. Altrimenti, l'opzione successiva rimuove il Mac dalla tua infrastruttura.
Opzione 2: Build iOS da un runner Linux con Capgo Build
Build Capgo compila e firma app Capacitor su Mac gestiti da Capgo. Il tuo job Gitea resta sul runner Linux che hai già. Fa il checkout del code, builda il layer web, esegue cap sync ios, e chiama build request. Il Capgo CLI carica il progetto nativo preparato, trasmette i log di Xcode nel log del job di Gitea ed esce con codice diverso da zero in caso di errore.
Questo modello si adatta bene a Gitea self-hosted:
- Nessun accesso in entrata. Capgo non clona il tuo repository. Il tuo runner esegue il push del progetto preparato via HTTPS, quindi un server Gitea dietro firewall o VPN funziona senza modifiche.
- Nessun token del repository. Non c'è nessuna connessione Git da configurare lato Capgo.
- Il tuo codice sorgente web resta dov'è. Vengono caricati solo la cartella
ios/nativa, la config di Capacitor e le parti native dei plugin.src/,.git/, e.envi file non vengono caricati.
Configurazione una tantum
Su una macchina di sviluppo:
bunx @capgo/cli@latest login
bunx @capgo/cli@latest app add
bunx @capgo/cli@latest build init --platform ios
build init crea o riutilizza il tuo certificato di distribuzione e il provisioning profile dell'App Store e salva la chiave API di App Store Connect. Esegui una build locale per verificare che la firma funzioni:
bun run build && bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
Poi esporta le credenziali:
bunx @capgo/cli@latest build credentials manage --appId com.example.app --platform ios
# select "Export to .env"
Aggiungi i secret a Gitea
Apri Impostazioni repository > Actions > Secrets e aggiungi ogni chiave del file esportato, più la tua chiave Capgo API:
| Segreto | Scopo |
|---|---|
CAPGO_TOKEN |
Chiave Capgo API con permesso di upload |
BUILD_CERTIFICATE_BASE64 |
Certificato di distribuzione (.p12) |
P12_PASSWORD |
Password del certificato |
CAPGO_IOS_PROVISIONING_MAP_BASE64 |
Mappatura del provisioning profile |
APPLE_KEY_ID |
ID della chiave API di App Store Connect |
APPLE_ISSUER_ID |
Issuer ID di App Store Connect |
APPLE_KEY_CONTENT |
Base64 .p8 chiave |
APP_STORE_CONNECT_TEAM_ID |
ID team Apple |
Gitea rifiuta nomi di secret e variabili che iniziano con GITEA_ oppure GITHUB_, quindi mantieni questi nomi invariati. Inserisci i valori non segreti come l'app ID in Variabili e leggili tramite ${{ vars.NAME }}. I secret impostati a livello di organizzazione sono condivisi da ogni repository, il che è comodo se distribuisci diverse app con lo stesso team Apple. Elimina il file esportato una volta terminato.
Rilascio al push del 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 }}"
Note:
${{ gitea.ref_name }}è il nome del tag. Ilgithub.*context funziona anche come alias, utile durante il porting dei workflow.- Gitea imposta
CI=truein ogni job, così Capgo CLI non aspetta mai input interattivo. - Capgo incrementa il build number dall'ultima build su TestFlight, così due tag di fila non collidono mai.
- Senza
--submit-to-store-review, la build arriva su TestFlight e si ferma. Aggiungi il flag se vuoi che la CI invii anche per l'App Review.
Il rilascio ora è git tag v2.3.0 && git push origin v2.3.0. Chiunque possa fare il push di un v* tag can ship, so add a protected tag rule for v* sotto Impostazioni > Tag e limitalo ai release manager.
Build ad hoc per i tester
Per le build QA da installare direttamente su dispositivi registrati, compila in modalità ad hoc e ottieni un link di download:
# .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
Le build ad hoc richiedono un profilo di provisioning ad hoc che elenchi i dispositivi di test. Conserva quel profilo codificato in base64 come segreto (ADHOC_PROFILE_BASE64 sopra), decodificalo nel job e passalo con --ios-provisioning-profile. I flag CLI hanno la precedenza sulle variabili d'ambiente, quindi questo sovrascrive il profilo App Store solo per questa build. Raccogli gli UDID con Trova UDID iOS. Per la maggior parte dei tester, il testing interno su TestFlight è più semplice, perché non richiede UDID.
Approvazione manuale del rilascio
GitHub ti consente di richiedere dei revisori su un ambiente. Gitea Actions non implementa le regole di protezione degli ambienti, quindi un job che specifica un ambiente non si fermerà in attesa di approvazione. In alternativa funzionano due pattern:
- Tag protetti. Solo i release manager possono fare il push
v*dei tag, e il workflow dei tag parte. - Build, poi promuovi. Build ad ogni push su
maincon--ios-distribution ad_hoco fai upload su TestFlight solo per i test interni, poi usa unworkflow_dispatchworkflow (disponibile nelle release Gitea attuali) per ricostruire il commit approvato con--submit-to-store-review.
Distribuisci modifiche web senza un nuovo binario
La maggior parte dei commit a un'app Capacitor cambia solo JavaScript, CSS e HTML. Ricompilare iOS per quelli spreca minuti e tempo di App Review. Con Live updates di Capgo, un solo job può scegliere il percorso giusto:
- 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 esce con 0 quando le dipendenze native corrispondono a quelle attive sul canale, e con 1 quando serve un nuovo binario. Forza inoltre il percorso nativo quando i file sotto ios/ oppure capacitor.config.* cambiano, poiché non rientrano nel confronto delle dipendenze. Vedi Scegli automaticamente live update o Native Build.
Runner Mac vs cloud build
| act_runner sul tuo Mac | Build di Capgo da Linux | |
|---|---|---|
| Hardware | Lo acquisti e lo ospiti tu | Nessuna |
| Aggiornamenti di Xcode | Manuale, per singola macchina | Gestito da Capgo |
| Isolamento tra i job | Nessuno in modalità host | Ambiente di build fresco per ogni job |
| Build parallele | Uno per Mac | Multipli, da qualsiasi runner |
| File di firma | Nei segreti Gitea, decodificati sul Mac | Nei segreti Gitea, inviati per build |
| Funziona dietro un firewall | Sì | Sì, solo HTTPS in uscita |
| App native Swift (senza Capacitor) | Sì | No, solo app Capacitor |
| Tempo massimo di build | Configurabile da te | 10 minuti per build |
Risoluzione problemi
| Sintomo | Soluzione |
|---|---|
| Il job resta in coda all'infinito | Nessun runner online ha la label runs-on. Controlla l'elenco dei runner e l'ortografia delle etichette. |
uses: actions/checkout@v4 il download non riesce |
Il runner non riesce a raggiungere GitHub. Replica l'action sul tuo Gitea e richiamala tramite URL completo. |
| Il valore del segreto è vuoto nel job | Il nome inizia con GITEA_/GITHUB_, oppure il segreto è definito su un altro repository. |
security: SecKeychainItemImport: MAC verification failed |
Errato .p12 password, oppure un .p12 creato da OpenSSL 3 senza -legacy. Riesportalo. |
No profiles for 'com.example.app' were found |
Il profilo nei secrets non corrisponde al bundle ID o al certificato. Riesegui build init e riesporta. |
| Upload rifiutato per la versione SDK | Il runner Mac ha ancora un Xcode precedente alla versione 26. |
cap sync ios avvisa riguardo a CocoaPods su Linux |
Atteso. La macchina di build di Capgo installa i pod. |
Per ulteriori modalità di errore, consulta CI/CD per Capacitor: errori comuni e il Capgo Build troubleshooting guide.
Riassunto
Gitea Actions gestisce iOS senza problemi una volta deciso dove eseguire Xcode. Un runner Mac in modalità host offre pieno controllo e piena responsabilità. Un job Linux che chiama Capgo Build mantiene invariata la configurazione di Gitea, non richiede accesso in ingresso e trasforma un rilascio TestFlight in un push di tag. Indipendentemente dalla scelta, conserva i materiali di firma nei segreti di Gitea, proteggi i tag di rilascio e pubblica le modifiche solo web come live updates, in modo che la pipeline nativa si esegua solo quando necessario.