diff --git a/.github/workflows/build_LoopFollow.yml b/.github/workflows/build_LoopFollow.yml index 1dbab1f47..c8281b0d0 100644 --- a/.github/workflows/build_LoopFollow.yml +++ b/.github/workflows/build_LoopFollow.yml @@ -2,14 +2,10 @@ name: 4. Build Loop Follow run-name: Build Loop Follow (${{ github.ref_name }}) on: workflow_dispatch: - - ## Remove the "#" sign from the beginning of the line below to get automated builds on push (code changes in your repository) - #push: - - ## Remove the "#" sign from the beginning of the two lines below to get automated builds every two months - #schedule: - #- cron: '0 17 1 */2 *' # Runs at 17:00 UTC on the 1st in Jan, Mar, May, Jul, Sep and Nov. - + push: + schedule: + - cron: "0 9 * * 3" # Weekly trigger: every Wednesday at 09:00 UTC + - cron: "0 7 1 * *" # Monthly trigger: on the 1st of every month at 07:00 UTC jobs: validate: @@ -22,25 +18,20 @@ jobs: needs: validate runs-on: macos-15 steps: - # Uncomment to manually select latest Xcode if needed - name: Select Latest Xcode run: "sudo xcode-select --switch /Applications/Xcode_16.2.app/Contents/Developer" - - # Checks-out the repo + - name: Checkout Repo uses: actions/checkout@v4 with: submodules: recursive - # Patch Fastlane Match to not print tables - name: Patch Match Tables run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d" - - # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996) + - name: Sync clock run: sudo sntp -sS time.windows.com - # Build signed Loop Follow IPA file - name: Fastlane Build & Archive run: fastlane build_LoopFollow env: @@ -51,7 +42,6 @@ jobs: FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - # Upload to TestFlight - name: Fastlane upload to TestFlight run: fastlane release env: @@ -61,8 +51,7 @@ jobs: FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - - # Upload IPA and Symbols + - name: Upload IPA and Symbol artifacts uses: actions/upload-artifact@v4 with: @@ -70,3 +59,54 @@ jobs: path: | artifacts buildlog + + check_certs: + name: Check Certificates + uses: ./.github/workflows/create_certs.yml + secrets: inherit + + nuke_certs: + name: Nuke Certificates + needs: [validate, check_certs] + runs-on: macos-14 + if: ${{ (needs.check_certs.outputs.new_certificate_needed == 'true' && vars.ENABLE_NUKE_CERTS == 'true') || vars.FORCE_NUKE_CERTS == 'true' }} + steps: + - name: Show certificate check output + run: echo "new_certificate_needed=${{ needs.check_certs.outputs.new_certificate_needed }}" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: bundle install + + - name: Run Fastlane nuke_certs + run: | + set -e + bundle exec fastlane nuke_certs + + - name: Recreate Distribution certificate after nuking + run: | + set -e + bundle exec fastlane certs + + - name: Add success annotations + if: ${{ success() }} + run: | + echo "::warning::⚠️ All Distribution certificates and TestFlight profiles have been revoked and recreated." + echo "::warning::❗️ If you have other apps that do not auto-renew certificates, run their 'Create Certificates' workflow." + echo "::warning::✅ Your existing TestFlight builds will keep working!" + + keep_alive: + name: Keep Alive + needs: [validate] + runs-on: ubuntu-latest + if: ${{ github.event_name == 'schedule' }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Keep alive branch + run: | + git checkout -B alive-main + git commit --allow-empty -m 'Keep alive commit' + git push origin alive-main \ No newline at end of file diff --git a/.github/workflows/create_certs.yml b/.github/workflows/create_certs.yml index 719a88792..898ca1714 100644 --- a/.github/workflows/create_certs.yml +++ b/.github/workflows/create_certs.yml @@ -1,7 +1,16 @@ name: 3. Create Certificates run-name: Create Certificates (${{ github.ref_name }}) -on: - workflow_dispatch: + +on: [workflow_call, workflow_dispatch] + +env: + TEAMID: ${{ secrets.TEAMID }} + GH_PAT: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ secrets.GH_PAT }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} + FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} + FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} jobs: validate: @@ -9,30 +18,94 @@ jobs: uses: ./.github/workflows/validate_secrets.yml secrets: inherit - certificates: - name: Create Certificates + create_certs: + name: Certificates needs: validate runs-on: macos-15 + outputs: + new_certificate_needed: ${{ steps.set_output.outputs.new_certificate_needed }} steps: - # Checks-out the repo - name: Checkout Repo uses: actions/checkout@v4 - - # Patch Fastlane Match to not print tables + - name: Patch Match Tables - run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d" - - # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996) - - name: Sync clock - run: sudo sntp -sS time.windows.com - - # Create or update certificates for app - - name: Create Certificates - run: fastlane certs - env: - TEAMID: ${{ secrets.TEAMID }} - GH_PAT: ${{ secrets.GH_PAT }} - MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} - FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} - FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} + run: | + TABLE_PRINTER_PATH=$(ruby -e 'puts Gem::Specification.find_by_name("fastlane").gem_dir')/match/lib/match/table_printer.rb + if [ -f "$TABLE_PRINTER_PATH" ]; then + sed -i "" "/puts(Terminal::Table.new(params))/d" "$TABLE_PRINTER_PATH" + else + echo "table_printer.rb not found" + exit 1 + fi + + - name: Install Project Dependencies + run: bundle install + + - name: Run Fastlane certs lane + run: | + echo "Running Fastlane certs lane..." + bundle exec fastlane certs || true + + - name: Check Distribution certificate and renew if needed + run: bundle exec fastlane check_and_renew_certificates + id: check_certs + + - name: Set output and annotations based on Fastlane result + id: set_output + run: | + CERT_STATUS_FILE="${{ github.workspace }}/fastlane/new_certificate_needed.txt" + ENABLE_NUKE_CERTS=${{ vars.ENABLE_NUKE_CERTS }} + + if [ -f "$CERT_STATUS_FILE" ]; then + CERT_STATUS=$(cat "$CERT_STATUS_FILE" | tr -d '\n' | tr -d '\r') + echo "new_certificate_needed: $CERT_STATUS" + echo "new_certificate_needed=$CERT_STATUS" >> $GITHUB_OUTPUT + else + echo "Certificate status file not found. Defaulting to false." + echo "new_certificate_needed=false" >> $GITHUB_OUTPUT + fi + + if [ "$CERT_STATUS" != "true" ] && [ "$ENABLE_NUKE_CERTS" != "true" ]; then + echo "::notice::🔔 Automated renewal of certificates is disabled because ENABLE_NUKE_CERTS is not set to 'true'." + fi + + if [ "$CERT_STATUS" = "true" ] && [ "$ENABLE_NUKE_CERTS" != "true" ]; then + echo "::error::❌ No valid distribution certificate found. Automated renewal of certificates was skipped because ENABLE_NUKE_CERTS is not set to 'true'." + exit 1 + fi + + if [ "${{ vars.FORCE_NUKE_CERTS }}" = "true" ]; then + echo "::warning::‼️ Nuking of certificates was forced because FORCE_NUKE_CERTS is set to 'true'." + fi + + nuke_certs: + name: Nuke certificates + needs: [validate, create_certs] + runs-on: macos-14 + if: ${{ (needs.create_certs.outputs.new_certificate_needed == 'true' && vars.ENABLE_NUKE_CERTS == 'true') || vars.FORCE_NUKE_CERTS == 'true' }} + steps: + - name: Output from step id 'check_certs' + run: echo "new_certificate_needed=${{ needs.create_certs.outputs.new_certificate_needed }}" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: bundle install + + - name: Run Fastlane nuke_certs + run: | + set -e + bundle exec fastlane nuke_certs + + - name: Recreate Distribution certificate after nuking + run: | + set -e + bundle exec fastlane certs + + - name: Add success annotations for nuke and certificate recreation + if: ${{ success() }} + run: | + echo "::warning::⚠️ All Distribution certificates and TestFlight profiles have been revoked and recreated." + echo "::warning::❗️ If you have other apps that do not auto-renew certificates, please run the '3. Create Certificates' workflow for each." + echo "::warning::✅ Your existing TestFlight builds will keep working!" \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 9086d1b1c..35bd3985e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -199,4 +199,60 @@ platform :ios do git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}") ) end -end + + ##################################################################### + # Additional lanes to support scheduled building and certificate nuking + ##################################################################### + + desc "Check Certificates and Trigger Workflow for Expired or Missing Certificates" + lane :check_and_renew_certificates do + setup_ci if ENV['CI'] + ENV["MATCH_READONLY"] = false.to_s + + api_key = app_store_connect_api_key( + key_id: ENV["FASTLANE_KEY_ID"], + issuer_id: ENV["FASTLANE_ISSUER_ID"], + key_content: ENV["FASTLANE_KEY"] # Ensure valid key content + ) + + new_certificate_needed = false + + require 'time' + certificates = Spaceship::ConnectAPI::Certificate.all + + distribution_certs = certificates.select { |cert| cert.certificate_type == "DISTRIBUTION" } + + if distribution_certs.empty? + puts "No Distribution certificates found! Triggering action to create certificate." + new_certificate_needed = true + else + distribution_certs.each do |cert| + expiration_date = Time.parse(cert.expiration_date) + + puts "Current Distribution Certificate: #{cert.id}, Expiration date: #{expiration_date}" + + if expiration_date < Time.now + puts "Distribution Certificate #{cert.id} is expired! Triggering action to renew certificate." + new_certificate_needed = true + else + puts "Distribution certificate #{cert.id} is valid. No action required." + end + end + end + + file_path = File.expand_path('new_certificate_needed.txt') + File.write(file_path, new_certificate_needed ? 'true' : 'false') + + puts "" + puts "Absolute path of new_certificate_needed.txt: #{file_path}" + new_certificate_needed_content = File.read(file_path) + puts "Certificate creation or renewal needed: #{new_certificate_needed_content}" + end + + desc "Keep Alive: Create an empty commit to keep the branch active" + lane :keep_alive do + sh "git checkout -B alive-main" + sh "git commit --allow-empty -m 'Keep alive commit'" + sh "git push origin alive-main" + end +end \ No newline at end of file