diff --git a/.github/workflows/add_identifiers.yml b/.github/workflows/add_identifiers.yml index 62a2b543f5..868ca857d1 100644 --- a/.github/workflows/add_identifiers.yml +++ b/.github/workflows/add_identifiers.yml @@ -1,15 +1,17 @@ name: 2. Add Identifiers -run-name: Add Identifiers +run-name: Add Identifiers (${{ github.ref_name }}) on: workflow_dispatch: jobs: - secrets: + validate: + name: Validate uses: ./.github/workflows/validate_secrets.yml secrets: inherit - + identifiers: - needs: secrets + name: Add Identifiers + needs: validate runs-on: macos-12 steps: # Uncomment to manually select latest Xcode if needed diff --git a/.github/workflows/build_loop.yml b/.github/workflows/build_loop.yml index bce7ef5d0c..fdc86f4e0c 100644 --- a/.github/workflows/build_loop.yml +++ b/.github/workflows/build_loop.yml @@ -5,10 +5,10 @@ on: ## Remove the "#" sign from the beginning of the line below to get automated builds on push (code changes in your repository) #push: - + schedule: - - cron: '0 8 * * 3' # Checks for updates at 08:00 am UTC every Wednesday - - cron: '0 8 1 * 6' # Builds the app on the 1st Saturday every month at 08:00 am UTC + - cron: '0 8 * * 3' # Checks for updates at 08:00 UTC every Wednesday + - cron: '0 8 1 * 6' # Builds the app on the 1st Saturday every month at 08:00 UTC env: UPSTREAM_REPO: LoopKit/LoopWorkspace @@ -18,21 +18,22 @@ env: WORKFLOW_PERMISSIONS: false jobs: - secrets: + validate: + name: Validate uses: ./.github/workflows/validate_secrets.yml secrets: inherit - + # Checks if GH_PAT holds workflow permissions # Checks for existence of alive branch; if non-existent creates it check_alive_and_permissions: - needs: secrets + needs: validate runs-on: ubuntu-latest name: Check alive branch and permissions permissions: contents: write outputs: WORKFLOW_PERMISSION: ${{ steps.workflow-permission.outputs.has_permission }} - + steps: - name: Check for workflow permissions id: workflow-permission @@ -49,7 +50,7 @@ jobs: echo "Automated build features will be skipped!" echo "has_permission=false" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false. fi - + - name: Check for alive branch if: steps.workflow-permission.outputs.has_permission == 'true' env: @@ -62,7 +63,7 @@ jobs: echo "Branch 'alive' does not exist." echo "ALIVE_BRANCH_EXISTS=false" >> $GITHUB_ENV # Set ALIVE_BRANCH_EXISTS to false fi - + - name: Create alive branch if: env.ALIVE_BRANCH_EXISTS != 'true' env: @@ -82,7 +83,7 @@ jobs: /repos/${{ github.repository_owner }}/LoopWorkspace/git/refs \ -f ref='refs/heads/alive' \ -f sha=$SHA - + # Checks for changes in upstream repository; if changes exist prompts sync for build # Performs keepalive to avoid stale fork check_latest_from_upstream: @@ -91,7 +92,7 @@ jobs: name: Check upstream and keep alive outputs: NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }} - + steps: - name: Checkout target repo if: | @@ -101,7 +102,7 @@ jobs: with: token: ${{ secrets.GH_PAT }} ref: alive - + - name: Sync upstream changes if: | # do not run the upstream sync action on the upstream repository needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && @@ -114,7 +115,7 @@ jobs: target_repo_token: ${{ secrets.GH_PAT }} upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }} upstream_sync_repo: ${{ env.UPSTREAM_REPO }} - + # Display a sample message based on the sync output var 'has_new_commits' - name: New commits found if: | @@ -127,7 +128,7 @@ jobs: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false' run: echo "There were no new commits." - + - name: Show value of 'has_new_commits' if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && vars.SCHEDULED_SYNC != 'false' run: | @@ -142,7 +143,7 @@ jobs: uses: gautamkrishnar/keepalive-workflow@v1 # using the workflow with default settings with: time_elapsed: 20 # Time elapsed from the previous commit to trigger a new automated commit (in days) - + - name: Show scheduled build configuration message if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION != 'true' run: | @@ -152,7 +153,6 @@ jobs: echo "If you want to enable automatic builds and updates for your Loop, please follow the instructions \ under the following path LoopWorkspace/fastlane/testflight.md." >> $GITHUB_STEP_SUMMARY - # Builds Loop build: name: Build @@ -169,7 +169,7 @@ jobs: steps: - name: Select Xcode version run: "sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer" - + - name: Checkout Repo for syncing if: | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && @@ -178,7 +178,7 @@ jobs: with: token: ${{ secrets.GH_PAT }} ref: ${{ env.TARGET_BRANCH }} - + - name: Sync upstream changes if: | # do not run the upstream sync action on the upstream repository needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && diff --git a/.github/workflows/create_certs.yml b/.github/workflows/create_certs.yml index 82961983c0..a5e076ffaa 100644 --- a/.github/workflows/create_certs.yml +++ b/.github/workflows/create_certs.yml @@ -1,15 +1,17 @@ name: 3. Create Certificates -run-name: Create Certificates +run-name: Create Certificates (${{ github.ref_name }}) on: workflow_dispatch: jobs: - secrets: + validate: + name: Validate uses: ./.github/workflows/validate_secrets.yml secrets: inherit - + certificates: - needs: secrets + name: Create Certificates + needs: validate runs-on: macos-12 steps: # Uncomment to manually select latest Xcode if needed diff --git a/.github/workflows/validate_secrets.yml b/.github/workflows/validate_secrets.yml index 70401888c6..2b03713011 100644 --- a/.github/workflows/validate_secrets.yml +++ b/.github/workflows/validate_secrets.yml @@ -1,70 +1,136 @@ name: 1. Validate Secrets -run-name: Validate Secrets +run-name: Validate Secrets (${{ github.ref_name }}) on: [workflow_call, workflow_dispatch] - + jobs: - validate: - runs-on: macos-12 + validate-access-token: + name: Access + runs-on: macos-13 + env: + GH_PAT: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ secrets.GH_PAT }} + steps: + - name: Validate Access Token + run: | + # Validate Fastlane Access Token (GH_PAT) + if [ -z "$GH_PAT" ]; then + failed=true + echo "::error::The GH_PAT secret is unset or empty. Set it and try again." + elif [ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/LoopWorkspace | jq --raw-output '.permissions.push')" != "true" ]; then + failed=true + echo "::error::The GH_PAT secret is set but invalid or lacking at least 'repo' permission scope ('repo, workflow' is okay too).\ + Verify that token permissions are set correctly (or update them) at https://github.com/settings/tokens and try again." + fi + + # Exit unsuccessfully if secret validation failed. + if [ $failed ]; then + exit 2 + fi + + validate-match-secrets: + name: Match-Secrets + needs: validate-access-token + runs-on: macos-13 + env: + GH_TOKEN: ${{ secrets.GH_PAT }} + steps: + - name: Validate Match-Secrets + run: | + # Validate Match-Secrets + if [ "$(gh repo list --json name | jq --raw-output 'any(.name=="Match-Secrets")')" != "true" ]; then + echo "A 'Match-Secrets' repository could not be found. Attempting to create one..."; + + if gh repo create Match-Secrets --private >/dev/null && [ "$(gh repo list --json name,visibility | jq --raw-output '.[] | select(.name=="Match-Secrets") | .visibility == "PRIVATE"')" == "true" ]; then + echo "Created a private 'Match-Secrets' repository." + else + failed=true + echo "::error::Cannot access or create a private 'Match-Secrets' repository. The GH_PAT secret is lacking at least the 'repo' permission scope required to access or create the repository.\ + Verify that token permissions are set correctly (or update them) at https://github.com/settings/tokens and try again." + fi + elif [ "$(gh repo list --json name,visibility | jq --raw-output '.[] | select(.name=="Match-Secrets") | .visibility == "PUBLIC"')" == "true" ]; then + failed=true + echo "::error::A 'Match-Secrets' repository was found, but it is is public. Delete it and try again (a private repository will be created for you)." + fi + + # Exit unsuccessfully if secret validation failed. + if [ $failed ]; then + exit 2 + fi + + validate-fastlane-secrets: + name: Fastlane + needs: validate-match-secrets + runs-on: macos-13 + env: + GH_PAT: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ secrets.GH_PAT }} + FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} + FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} + FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + TEAMID: ${{ secrets.TEAMID }} steps: - # Checks-out the repo - name: Checkout Repo uses: actions/checkout@v3 - - # Validates the repo secrets - - name: Validate Secrets + + - name: Validate Fastlane Secrets run: | - # Validate Secrets - echo Validating Repository Secrets... + # Validate Fastlane Secrets # Validate TEAMID if [ -z "$TEAMID" ]; then failed=true - echo "::error::TEAMID secret is unset or empty. Set it and try again." + echo "::error::The TEAMID secret is unset or empty. Set it and try again." elif [ ${#TEAMID} -ne 10 ]; then failed=true - echo "::error::TEAMID secret is set but has wrong length. Verify that it is set correctly and try again." + echo "::error::The TEAMID secret is set but has wrong length. Verify that it is set correctly and try again." + elif ! [[ $TEAMID =~ ^[A-Z0-9]+$ ]]; then + failed=true + echo "::error::The TEAMID secret is set but invalid. Verify that it is set correctly (only uppercase letters and numbers) and try again." fi - # Validate GH_PAT - if [ -z "$GH_PAT" ]; then - failed=true - echo "::error::GH_PAT secret is unset or empty. Set it and try again." - elif [ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/Match-Secrets | jq --raw-output '.permissions.push')" != "true" ]; then + # Validate MATCH_PASSWORD + if [ -z "$MATCH_PASSWORD" ]; then failed=true - echo "::error::GH_PAT secret is set but invalid or lacking appropriate privileges on the ${{ github.repository_owner }}/Match-Secrets repository. Verify that it is set correctly and try again." + echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again." fi + # Ensure that fastlane exit codes are handled when output is piped. + set -o pipefail + # Validate FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY + FASTLANE_KEY_ID_PATTERN='^[A-Z0-9]+$' + FASTLANE_ISSUER_ID_PATTERN='^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$' + if [ -z "$FASTLANE_ISSUER_ID" ] || [ -z "$FASTLANE_KEY_ID" ] || [ -z "$FASTLANE_KEY" ]; then failed=true [ -z "$FASTLANE_ISSUER_ID" ] && echo "::error::The FASTLANE_ISSUER_ID secret is unset or empty. Set it and try again." [ -z "$FASTLANE_KEY_ID" ] && echo "::error::The FASTLANE_KEY_ID secret is unset or empty. Set it and try again." [ -z "$FASTLANE_KEY" ] && echo "::error::The FASTLANE_KEY secret is unset or empty. Set it and try again." - elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then + elif [ ${#FASTLANE_KEY_ID} -ne 10 ]; then failed=true - echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that it is set correctly and try again." - elif ! fastlane validate_secrets; then + echo "::error::The FASTLANE_KEY_ID secret is set but has wrong length. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." + elif ! [[ $FASTLANE_KEY_ID =~ $FASTLANE_KEY_ID_PATTERN ]]; then failed=true - echo "::error::Unable to create a valid authorization token for the App Store Connect API.\ - Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again." - fi - - # Validate MATCH_PASSWORD - if [ -z "$MATCH_PASSWORD" ]; then + echo "::error::The FASTLANE_KEY_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." + elif ! [[ $FASTLANE_ISSUER_ID =~ $FASTLANE_ISSUER_ID_PATTERN ]]; then failed=true - echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again." + echo "::error::The FASTLANE_ISSUER_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again." + elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then + failed=true + echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that you copied it correctly from the API Key file (*.p8) you downloaded and try again." + elif ! fastlane validate_secrets 2>&1 | tee fastlane.log; then + if grep -q "bad decrypt" fastlane.log; then + failed=true + echo "::error::Unable to decrypt the Match-Secrets repository using the MATCH_PASSWORD secret. Verify that it is set correctly and try again." + elif ! grep -q "No code signing identity found" fastlane.log; then + failed=true + echo "::error::Unable to create a valid authorization token for the App Store Connect API.\ + Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again." + fi fi # Exit unsuccessfully if secret validation failed. if [ $failed ]; then exit 2 fi - shell: bash - env: - TEAMID: ${{ secrets.TEAMID }} - GH_PAT: ${{ secrets.GH_PAT }} - FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }} - FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }} - FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }} - MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - GH_TOKEN: ${{ secrets.GH_PAT }} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 6b83bcaee9..f918b3aa41 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -249,6 +249,13 @@ platform :ios do end find_bundle_id("com.#{TEAMID}.loopkit.Loop") + + match( + type: "appstore", + git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), + app_identifier: [], + ) + end desc "Nuke Certs"