Zum Hauptinhalt springen
CI/CD

Automatischer Capacitor IOS-Build mit GitHub Aktionen mit Zertifikat

Wie Sie in 5 Minuten (2024) eine CI/CD-Pipeline für Ihre IOS-Ionic-App mit fastlane und GitHub Actions einrichten

Martin Donadieu

Martin Donadieu

Content-Marketing-Spezialist

Automatischer Capacitor IOS-Build mit GitHub Aktionen mit Zertifikat

Die Einrichtung einer CI/CD-Pipeline für Capacitor-Apps kann komplex und zeitaufwändig sein. Hier sind die wichtigsten Informationen:

Wir empfehlen nun die Verwendung von Capgo Build mit der Capgo CLI für native Capacitor-Bauten. Dieses Fastlane-Leitfaden wird für Teams aufrechterhalten, die bestehende GitHub-Actions-Pipelines verwalten, aber neue iOS-Builds sollten die Capgo CLI verwenden, damit Sie Fastlane, Xcode-Runner, Zertifikate und Upload-Skripte nicht selbst verwalten müssen.

Voraussetzungen

Bevor Sie beginnen, müssen Sie Folgendes einrichten:

  • Ein GitHub-Konto mit Administrator-Zugriff
  • iOS-Entwicklerprogramm-Mitgliedschaft
  • App Store Connect API-Zugriff mit den richtigen Berechtigungen
  • Verständnis von GitHub-Actions-Workflows
  • Kenntnisse in der Fastlane-Konfiguration
  • Zeit für die Verwaltung und Fehlersuche im Pipeline
  • Ordentliche Zertifikate und Bereitstellungsprofile

Professionelle CI/CD-Einrichtung durch Capgo

Vermeiden Sie die Komplexität. Capgo direkt in Ihrer bevorzugten Plattform Ihre CI/CD-Pipeline einrichtet:

  • Plattformunabhängigkeit: Funktioniert mit GitHub-Actions, GitLab CI oder anderen
  • Einfache Integration: Keine Plattformwechsel erforderlich, funktioniert mit Ihrem aktuellen Prozess
  • Angepasste Konfiguration: Individualisierte Einrichtung, die Ihren Projektbedürfnissen entspricht
  • Experteinsatz: Wir haben bereits CI/CD für 50+ Apps eingerichtet

Preise

  • Einmaliger Einrichtungsbetrag: 2.600 $
  • Ihre laufenden Kosten: ~$300/Jahr
  • Vergleichen Sie dies mit einer anderen proprietären Lösung: $6.000/Jahr
  • Sparen Sie 26.100 $ über 5 Jahre

Setup CI/CD Jetzt

Manueller Setup Guide

Wenn Sie immer noch alles selbst einrichten möchten, hier ist, was Sie tun müssen:

Kontinuierliche Lieferung für iOS mit Fastlane und GitHub Aktionen und Zertifikat

Voraussetzungen

Bevor Sie mit der Anleitung fortfahren:

  • Stellen Sie sicher, dass Sie Fastlane installiert auf Ihrem Entwicklungsgerät.
  • Stellen Sie sicher, dass Sie Mitglied des iOS-Entwicklerprogramms sind.

Wichtige Informationen zum Preis

Preis GitHub Aktion

https://github.com/features/actions

Die Dienstleistung ist ‘kostenlos’ bis zur Grenze, je nach gewählter Maschine.
Wir werden eine macOS Maschine verwenden, Sie können im Screenshot ihren Preis und Grenzen (Preise Stand der Erstellung des Tutorials, sie könnten sich in Zukunft ändern) sehen.

Nachdem Sie über Anforderungen und Preise gewarnt wurden, können wir fortfahren.

Hinweis: In dem Beitrag gehe ich davon aus, dass Sie die App in App Store Connect erstellt haben. Die wichtigen Informationen werden von Fastlane kopiert!

Was werden Sie in der Anleitung lernen

Schritte, die Sie im Beitrag befolgen müssen

  1. Verwendung von App Store Connect API mit Fastlane
    • Anforderungen:
      • Erstellung eines App Store Connect API-Schlüssels
      • Verwendung eines App Store Connect API-Schlüssels
  2. Kopieren von Fastlane-Dateien
  3. Konfiguration von GitHub-Aktionen

1. Verwendung von App Store Connect API mit Fastlane

Ab Februar 2021 ist für alle Benutzer die Zwei-Faktor-Authentifizierung oder die Zwei-Schritt-Verifizierung erforderlich, um sich bei App Store Connect anzumelden. Diese zusätzliche Sicherheitsschicht für Ihren Apple-Konto hilft sicherzustellen, dass Sie der einzige Person sind, die Zugriff auf Ihr Konto hat.
Von Apple Support

Anforderungen

Um sicherzustellen, dass Fastlane App Store Connect API zur Veröffentlichung Ihres Apps verwenden kann, müssen Sie die folgenden Informationen bereitstellen: drei Dinge:

  1. Aussteller-ID
  2. Schlüssel-ID
  3. Schlüsseldatei oder Schlüsselinhalt

Erwerb eines App Store Connect API-Schlüssels

Um Schlüssel zu generieren, müssen Sie in App Store Connect die Administratorrechte haben. Wenn Sie diese Berechtigung nicht haben, können Sie den relevanten Personen diesen Artikel empfehlen.

  1. Anmeldung bei App Store Connect.

  2. Auswahl Benutzer und Zugriff.

Zugriff auf App Store Connect

3 — Wählen Sie die Integrationseinstellungen.

App Store Connect API-Integration

  1. Klicken Sie auf Erstellen von API-Schlüssel oder auf die (+) Taste.

App Store Connect API-Schlüssel erstellen

  1. Geben Sie einen Namen für den Schlüssel ein. Der Name dient nur Ihrer Referenz und ist nicht Teil des Schlüssels selbst.

App Store Connect API-Schlüssel erstellen: Name

6 — Unter Zugriff wählen Sie die Rolle für den Schlüssel aus. Die Rollen, die auf Schlüssel angewendet werden, sind dieselben Rollen, die auf Benutzer auf Ihrem Team angewendet werden. Siehe Benutzerrechte. Wir empfehlen, App-Manager.

  1. Klicken Sie auf Generieren.

Der Zugriff auf einen API-Schlüssel kann nicht auf bestimmte Apps beschränkt werden.

Der Name des neuen Schlüssels, die Schlüssel-ID, ein Download-Link und weitere Informationen erscheinen auf der Seite.

App Store Connect-Download-Schlüssel

Sie können hier alle drei notwendigen Informationen finden.
<1> Issue-ID. (APPLE_ISSUER_ID geheim)
<2> Schlüssel-ID. (APPLE_KEY_ID geheim)
<3> Klicken Sie auf "Download API-Schlüssel" zum Herunterladen Ihres API-privaten Schlüssels. Der Download-Link erscheint nur, wenn der private Schlüssel noch nicht heruntergeladen wurde. Apple hält keinen Kopie des privaten Schlüssels. Daher können Sie ihn nur einmal herunterladen.

🔴 Speichern Sie Ihren privaten Schlüssel an einem sicheren Ort. Sie sollten Ihre Schlüssel niemals teilen, Schlüssel in einem code-Repository speichern oder Schlüssel in Client-Seiten code einbeziehen.

Mit einem App Store Connect API-Schlüssel

Die API-Schlüsseldatei (p8-Datei, die Sie herunterladen), die Schlüssel-ID und die Aussteller-ID sind erforderlich, um den JWT-Token für die Autorisierung zu erstellen. app_store_connect_api_keyEs gibt mehrere Möglichkeiten, diese Informationen in Fastlane einzugeben. Ich habe mich dafür entschieden, die neue Aktion von Fastlane zu verwenden Sie können andere Methoden in derFastlane-Dokumentation

finden. Ich zeige Ihnen diese Methode, weil ich glaube, dass sie die einfachste Möglichkeit ist, mit den meisten CI-Tools zu arbeiten, bei denen Sie Umgebungsvariablen setzen können.APPLE_KEY_CONTENT).

base64 -i APPLE_KEY_CONTENT.p8 | pbcopy

Now we can manage the App Store Connect with Fastlane using the API key, great!

Jetzt können wir App Store Connect mit Fastlane über die __CAPGO_KEEP_0__-Schlüssel verwalten, großartig!

2. Zertifikate Öffnen Sie XCode und gehen Sie zu > Einstellungen > Accounts > Apple-ID und wählen Sie Ihr Team.

Code Signierungsidentitäten

Klicken Sie auf Zertifikate verwalten.

Wenn Sie noch kein Zertifikat erstellt haben, können Sie ein neues Zertifikat erstellen.

Klicken Sie auf + und wählen Sie Apple Distribution

Apple Distribution

Dann müssen Sie zu Keychain gehen, um das Zertifikat als Datei herunterzuladen. .p12 Um dies zu tun, müssen Sie zu Keychain gehen und auf die

Datei Anmeldung Dann die Taste "Schlüsselkette" und dann die Taste Meine Zertifikate.

Meine Zertifikate

Dann können Sie das Zertifikat auswählen, das Sie herunterladen möchten. (Suchen Sie nach dem Datum des Zertifikats)

Und dann klicken Sie mit der rechten Maustaste auf den privaten Schlüssel auf dem Zertifikat und wählen Sie Exportieren.

Wählen Sie die Dateiformat Persönliche Informationen austauschen (.p12).

Das wird das Zertifikat als .p12 Datei herunterladen.

Bitte öffnen Sie die Datei in einem Terminal und verwenden Sie den folgenden Befehl, um sie in Base64 umzuwandeln:

base64 -i BUILD_CERTIFICATE.p12 | pbcopy

Dies wird Ihr BUILD_CERTIFICATE_BASE64 __CAPGO_KEEP_0__ P12_PASSWORD Geheimnis. Bitte geben Sie auch bei der Aufforderung den Passwort des Zertifikats an. Dieses Passwort wird Ihr

__CAPGO_KEEP_0__

3. Provisionierungsprofile Öffnen Apple Developer

und wählen Sie das richtige Team. +

Erstellen Sie dann ein neues Profil, indem Sie auf

__CAPGO_KEEP_1__ und wählen Sie.

App Store Connect

Dann müssen Sie das richtige App auswählen, beachten Sie, dass Sie ein Wildcard nicht verwenden können, sonst wird die Signierung fehlschlagen.

Richtige App auswählen

Wählen Sie das richtige Zertifikat, das Sie vorher erstellt haben (suchen Sie nach dem Datum der Ablaufzeit, es sollte der gleiche Tag und Monat wie heute sein) und klicken Sie auf Richtiges Zertifikat auswählen.

Geben Sie schließlich den Namen des Profils ein und klicken Sie auf

Profil erstellen Der Name wird zum Identifizieren des Profils in Fastlane unter der Werte von.

Profil erstellen APPLE_PROFILE_NAME.

Sie können das Profil als

Datei herunterladen. .mobileprovision Profil herunterladen

Sie können das Profil als Datei herunterladen.

Bitte konvertieren Sie das Profil in Base64 und speichern Sie es als Geheimnis (BUILD_PROVISION_PROFILE_BASE64).

base64 -i BUILD_PROVISION_PROFILE.mobileprovision | pbcopy

4. Kopieren Sie die Fastlane-Dateien

Fastlane ist eine Ruby-Bibliothek, die zum Automatisieren von häufigen Aufgaben der mobilen Entwicklung erstellt wurde. Mit Fastlane können Sie benutzerdefinierte „Bahnen“ konfigurieren, die eine Reihe von „Aktionen“ enthalten, die Aufgaben ausführen, die Sie normalerweise mit Android Studio durchführen würden. Mit Fastlane können Sie viel erreichen, aber für die Zwecke dieses Tutorials werden wir nur eine Handvoll grundlegender Aktionen verwenden.

Erstellen Sie das Fastlane-Verzeichnis am Root Ihres Capacitor/Ionic-Projekts und fügen Sie das Fastfile dorthin hinzu:

  • Ordner: <project-root>/fastlane/
  • Datei: <project-root>/fastlane/Fastfile

Dies ist derselbe Level wie package.json, capacitor.config.*, und das ios/ Ordner. Erstellen Sie es nicht innerhalb von ios/App/.

platform :ios do
  desc 'Export ipa and submit to TestFlight'
  lane :beta do
    keychain_info = { keychain_name: "ios-build-#{Time.now.to_i}.keychain", keychain_password: SecureRandom.uuid }
    
    begin
      setup_signing(keychain_info)
      bump_build_number
      build_app_with_signing(keychain_info)
      submit_to_testflight
    ensure
      cleanup_keychain(keychain_info)
    end
  end

  private_lane :setup_signing do |options|
    create_keychain(
      name: options[:keychain_name],
      password: options[:keychain_password],
      unlock: true,
      timeout: 0,
      lock_when_sleeps: false, 
      add_to_search_list: true
    )
    import_cert(options)
    install_profile
    update_project_settings
  end

  lane :bump_build_number do
		file = File.read('../package.json')
		data_hash = JSON.parse(file)
		api_key = app_store_connect_api_key(
      key_id: ENV['APPLE_KEY_ID'],
      issuer_id: ENV['APPLE_ISSUER_ID'],
      key_content: ENV['APPLE_KEY_CONTENT'],
      is_key_content_base64: true,
      duration: 1200,
      in_house: false
    )
		build_num = app_store_build_number(
      api_key: api_key,
			app_identifier: ENV['BUNDLE_IDENTIFIER'],
			live: false
    )
		build_num = build_num + 1
		UI.message("Bumped build number to #{build_num}")
		increment_build_number(
			build_number: build_num,
			xcodeproj: "./ios/App/App.xcodeproj",
			skip_info_plist: true
		)
	end

  private_lane :import_cert do |options|
    cert_path = "#{Dir.tmpdir}/build_certificate.p12"
    File.write(cert_path, Base64.decode64(ENV['BUILD_CERTIFICATE_BASE64']))
    import_certificate(
      certificate_path: cert_path,
      certificate_password: ENV['P12_PASSWORD'] || "",
      keychain_name: options[:keychain_name],
      keychain_password: options[:keychain_password],
      log_output: true
    )
    File.delete(cert_path)
  end  
  
  private_lane :cleanup_keychain do |options|
    delete_keychain(
      name: options[:keychain_name]
    )
  end  

  private_lane :install_profile do
    profile_path = "#{Dir.tmpdir}/build_pp.mobileprovision"
    File.write(profile_path, Base64.decode64(ENV['BUILD_PROVISION_PROFILE_BASE64']))
    UI.user_error!("Failed to create provisioning profile at #{profile_path}") unless File.exist?(profile_path)
    ENV['PROVISIONING_PROFILE_PATH'] = profile_path
    install_provisioning_profile(path: profile_path)
    File.delete(profile_path)
  end

  private_lane :update_project_settings do
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "./ios/App/App.xcodeproj",
      code_sign_identity: "iPhone Distribution",
      profile_name: ENV['APPLE_PROFILE_NAME'],
      bundle_identifier: ENV['BUNDLE_IDENTIFIER'],
      team_id: ENV['APP_STORE_CONNECT_TEAM_ID']
    )
    update_project_team(
      path: "./ios/App/App.xcodeproj",
      teamid: ENV['APP_STORE_CONNECT_TEAM_ID']
    )
  end

  private_lane :build_app_with_signing do |options|
    unlock_keychain(
      path: options[:keychain_name],
      password: options[:keychain_password],
      set_default: false
    )
    build_app(
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      configuration: "Release",
      export_method: "app-store",
      output_name: "App.ipa",
      export_options: {
        provisioningProfiles: {
          ENV['BUNDLE_IDENTIFIER'] => ENV['APPLE_PROFILE_NAME']
        }
      },
      xcargs: "-verbose",
      buildlog_path: "./build_logs",
      export_xcargs: "-allowProvisioningUpdates",
    )
  end   

  private_lane :submit_to_testflight do
    api_key = app_store_connect_api_key(
      key_id: ENV['APPLE_KEY_ID'],
      issuer_id: ENV['APPLE_ISSUER_ID'],
      key_content: ENV['APPLE_KEY_CONTENT'],
      is_key_content_base64: true,
      duration: 1200,
      in_house: false
    )
    pilot(
      api_key: api_key,
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )
  end
end

5. Einstellungen für Geheimnisse

GitHub Actions verwendet die Repository-Geheimnisse, die Sie in der nächsten Schritt konfigurieren. Sie benötigen nur eine lokale .env Datei, wenn Sie Fastlane ausführen oder testen möchten, während Sie auf Ihrem eigenen Computer sind.

Für lokale Tests erstellen Sie <project-root>/fastlane/.env nächstens dem Fastfile. Kommt dies nicht in die Version. Fügt fastlane/.env zu Ihrem .gitignore ersten (oder überprüfen Sie, ob es bereits ignoriert wird). Hier ist ein Beispiel:

APP_STORE_CONNECT_TEAM_ID=UVTJ336J2D
BUNDLE_IDENTIFIER=ee.forgr.testfastlane
# See previous section for these secrets
BUILD_CERTIFICATE_BASE64=
BUILD_PROVISION_PROFILE_BASE64=
APPLE_KEY_ID=
APPLE_ISSUER_ID=
APPLE_KEY_CONTENT=
P12_PASSWORD=
APPLE_PROFILE_NAME=

Ermitteln Sie die APP_STORE_CONNECT_TEAM_ID

Gehen Sie zu Entwickler-Zentrum und scrollen Sie nach unten zu Membership details Abschnitt. Team ID Die APP_STORE_CONNECT_TEAM_ID ist der Wert, den Sie in der

app-store-connect-team-id

Die BUNDLE_IDENTIFIER erhalten

  1. Xcode öffnen
  2. Doppelklicken Sie auf den App in der Projektnavigator
  3. Klicken Sie dann auf die Registerkarte Signing and Capabilities
  4. Die Werte von kopieren Sie. Bundle identifierDies ist der Wert, den Sie in der BUNDLE_IDENTIFIER geheim.
bundle-identifier-xcode

6. Verarbeitung von Aufgaben

In GitHub Aufgaben, Sie werden auf der Grundlage der Minuten abgerechnet, die Sie für die Ausführung Ihres CI/CD-Workflows verwendet haben. Aus meiner Erfahrung dauert es etwa 10-15 Minuten, bevor ein Build in App Store Connect bearbeitet werden kann. Bei privaten Projekten kann die geschätzte Kosten pro Build bis zu

$0,08/min x 15 min = $1,2 , oder mehr, je nach Konfiguration und Abhängigkeiten Ihres Projekts abhängen.Wenn Sie sich um die Kosten für private Projekte Sorgen machen, können Sie

festlegen. skip_waiting_for_build_processing Dies wird Build-Minuten sparen, indem Sie nicht auf die Bearbeitung des Builds in App Store Connect warten. trueEs gibt jedoch einen Kompromiss - Sie müssen die Compliance-Informationen Ihres Apps in App Store Connect manuell aktualisieren, bevor Sie den Build an die Benutzer verteilen können.

Diese Optimierung ist vor allem für private Projekte nützlich, bei denen Build-Minuten Geld kosten. Bei öffentlichen/ kostenlosen Projekten sind die Build-Minuten kostenlos, daher ist es nicht notwendig, diese Einstellung zu aktivieren. Weitere Informationen finden Sie auf der

This optimization is mainly useful for private projects where build minutes cost money. For public/free projects, the build minutes are free so there’s no need to enable this setting. See GitHub’s See __CAPGO_KEEP_0__'s pricing page

7. Setup GitHub Aktionen

Konfigurieren Sie GitHub-Geheimnisse

Bitte kopieren Sie die Geheimnisse aus dem .env Datei und fügen Sie sie in die GitHub-Repository-Geheimnisse ein.

Gehe zu Einstellungen > Geheimnisse und Variablen > Aktionen > Neues Repository-Geheimnis

github-Geheimnisse

2. BUILD_CERTIFICATE_BASE64 - Zertifikat in Base64-Code.

3. BUILD_PROVISION_PROFILE_BASE64 - Base64-codierte Provisionierungsprofil.

4. BUNDLE_IDENTIFIER - Ihre App-Bundle-Identifikationsnummer.

5. APPLE_KEY_ID — App Store Connect API Schlüssel 🔺Schlüssel-ID.

6. APPLE_ISSUER_ID — App Store Connect API Schlüssel 🔺Aussteller-ID.

7. APPLE_KEY_CONTENT — App Store Connect API Schlüssel 🔺Schlüsselinhalt. .p8, überprüfen Sie es

8. Konfigurieren Sie das GitHub Workflow-Datei.

Erstellen Sie ein GitHub Workflow-Verzeichnis.

cd .github/workflows

Im Ordner erstellen Sie eine Datei mit dem Namen workflow und fügen Sie folgendes hinzu. build-upload-ios.ymlDiese Workflow sollte nach jedem __CAPGO_KEEP_0__ ausgelöst werden.

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: Set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
          bundler-cache: true
      - uses: maierj/fastlane-action@v3.1.0
        env:
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          BUNDLE_IDENTIFIER: ${{ secrets.BUNDLE_IDENTIFIER }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
          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 }}
          APPLE_PROFILE_NAME: ${{ secrets.APPLE_PROFILE_NAME }}
        with:
          lane: ios beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v6
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 10

This workflow should be triggered after each GitHub Tag, wenn Sie die Automatisierung von Tags benötigen, beziehen Sie sich auf Automatisches Build- und Release-Verfahren mit GitHub-Aktionen Zuerst.

Dann wird diese Workflow Ihre NodeJS-Abhängigkeiten ziehen, sie installieren und Ihr JavaScript-Anwendungsprogramm bauen.

Jedes Mal, wenn Sie einen neuen Commit senden, wird ein Release in TestFlight erstellt.

Ihre App benötigt nicht zwingend Ionic, nur die Capacitor-Basis ist erforderlich. Sie kann alte Cordova-Module haben, aber die Capacitor-JS-Plugin wird bevorzugt.

8. Auslösen Sie den Workflow

Erstellen Sie einen Commit

Machen Sie einen Commit, Sie sollten das aktive Workflow im Repository sehen.

Auslösen des Workflows

Pushen Sie die neuen Commits in die Zweig main oder development um den Workflow auszulösen.

Gestartet mit Commit

Nach einigen Minuten sollte das Build in Ihrem App Store Connect-Dashboard verfügbar sein.

Testflight-Dashboard

9. Kann ich von der lokalen Maschine bereitstellen?

Ja, das geht, und es ist einfach.

Sie können Xcode verwenden, um Ihre App zu bauen und zu signieren, wie immer.

CI/CD-Einrichtungsleitfäden

Alternative CI/CD-Plattformen

Live Updates &amp; Bereitstellung

Vielen Dank

Dieser Blog basiert auf den folgenden Artikeln:

Live-Updates für Capacitor-Anwendungen

Wenn ein Web-Schicht-Bug live ist, liefern Sie die Reparatur über Capgo anstatt Tage auf die Genehmigung des App-Store zu warten. Die Benutzer erhalten die Aktualisierung im Hintergrund, während native Änderungen im normalen Review-Verfahren bleiben.

Los geht's!

Neueste Beiträge aus unserem Blog

Capgo bietet Ihnen die besten Einblicke, die Sie benötigen, um eine wirklich professionelle mobile App zu erstellen.