@@ -106,10 +106,17 @@ jobs:
106106 rm "$RUNNER_TEMP/dist.p12"
107107
108108 - name : Write App Store Connect API key
109+ id : write-api-key
109110 env :
110111 API_KEY : ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }}
111- API_KEY_ID : ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
112+ API_KEY_ID_RAW : ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
113+ API_ISSUER_RAW : ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
112114 run : |
115+ set -euo pipefail
116+ # Strip any accidental whitespace/newlines from ID values pasted into secrets.
117+ API_KEY_ID=$(printf '%s' "$API_KEY_ID_RAW" | tr -d '[:space:]')
118+ API_ISSUER=$(printf '%s' "$API_ISSUER_RAW" | tr -d '[:space:]')
119+
113120 if [[ -z "$API_KEY" ]]; then
114121 echo "::error::APP_STORE_CONNECT_PRIVATE_KEY secret is empty or not set"
115122 exit 1
@@ -118,53 +125,73 @@ jobs:
118125 echo "::error::APP_STORE_CONNECT_KEY_ID secret is empty or not set"
119126 exit 1
120127 fi
121- mkdir -p ~/.appstoreconnect/private_keys
122- KEY_FILE=~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8
128+
129+ mkdir -p "$HOME/.appstoreconnect/private_keys"
130+ RAW_FILE="$RUNNER_TEMP/api_key_raw.p8"
131+ KEY_FILE="$HOME/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8"
123132
124133 # Auto-detect: raw PEM (starts with -----BEGIN) or base64-encoded.
125134 if printf '%s' "$API_KEY" | head -c 20 | grep -q "^-----BEGIN"; then
126- # Raw PEM — strip carriage returns and trailing whitespace per line
127- # (CryptoKit rejects trailing spaces on -----END PRIVATE KEY-----).
128- printf '%s' "$API_KEY" | tr -d '\r' | sed 's/[[:space:]]*$//' > "$KEY_FILE"
135+ # Raw PEM: strip CR and trailing whitespace per line.
136+ printf '%s' "$API_KEY" | tr -d '\r' | sed 's/[[:space:]]*$//' > "$RAW_FILE"
129137 echo "Detected raw PEM format"
130138 else
131- # Base64-encoded .p8 — decode then strip carriage returns.
132- printf '%s' "$API_KEY" | tr -d '\r ' | base64 -D > "$KEY_FILE"
133- echo "Detected base64-encoded format, decoded successfully"
139+ # Base64-encoded: decode then strip CR.
140+ printf '%s' "$API_KEY" | tr -d '\r ' | base64 -D > "$RAW_FILE"
141+ echo "Detected base64-encoded format"
142+ fi
143+
144+ # Ensure file ends with exactly one newline.
145+ if [[ "$(tail -c 1 "$RAW_FILE" | xxd -p)" != "0a" ]]; then
146+ printf '\n' >> "$RAW_FILE"
134147 fi
135148
136- # Ensure file ends with a single newline (CryptoKit requires it)
137- if [[ "$(tail -c 1 "$KEY_FILE" | xxd -p)" != "0a" ]]; then
138- printf '\n' >> "$KEY_FILE"
149+ # Normalize via openssl so CryptoKit gets a canonical PEM structure
150+ # (correct line width, no stray bytes, clean header/footer).
151+ if openssl pkey -in "$RAW_FILE" -out "$KEY_FILE" 2>/dev/null; then
152+ echo "Key normalized via openssl pkey"
153+ else
154+ cp "$RAW_FILE" "$KEY_FILE"
155+ echo "Warning: openssl normalization skipped — using cleaned raw key"
139156 fi
157+ rm -f "$RAW_FILE"
158+ chmod 600 "$KEY_FILE"
140159
160+ # Validate written file
141161 if ! head -c 20 "$KEY_FILE" | grep -q "^-----BEGIN"; then
142- echo "::error::Written .p8 doesn't look like PEM. "
162+ echo "::error::Written .p8 doesn't look like PEM"
143163 exit 1
144164 fi
145165 SIZE=$(wc -c < "$KEY_FILE" | tr -d ' ')
146- FIRST=$(head -1 "$KEY_FILE")
147- LAST=$(tail -1 "$KEY_FILE")
148- echo "API key: ${SIZE} bytes | first: '${FIRST}' | last: '${LAST}'"
166+ echo "API key written: ${SIZE} bytes → $(basename "$KEY_FILE")"
167+
168+ # Emit trimmed values as step outputs so downstream steps use the exact same strings.
169+ echo "key_id=${API_KEY_ID}" >> "$GITHUB_OUTPUT"
170+ echo "issuer_id=${API_ISSUER}" >> "$GITHUB_OUTPUT"
171+ echo "key_path=${KEY_FILE}" >> "$GITHUB_OUTPUT"
149172
150173 - name : Archive (Release)
151174 working-directory : DoomCoderCompanion
152175 env :
153176 APPLE_TEAM_ID : ${{ secrets.APPLE_TEAM_ID }}
177+ ASC_KEY_ID : ${{ steps.write-api-key.outputs.key_id }}
178+ ASC_ISSUER_ID : ${{ steps.write-api-key.outputs.issuer_id }}
179+ ASC_KEY_PATH : ${{ steps.write-api-key.outputs.key_path }}
154180 run : |
155181 # Strip the agent's stale GIT_CONFIG vars so SwiftPM can resolve bare repos.
156182 unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0
183+ echo "Using key: $(basename "${ASC_KEY_PATH}")"
157184 xcodebuild \
158185 -project DoomCoderCompanion.xcodeproj \
159186 -scheme DoomCoderCompanion \
160187 -configuration Release \
161188 -destination 'generic/platform=iOS' \
162189 -archivePath "$RUNNER_TEMP/DoomCoderCompanion.xcarchive" \
163190 -allowProvisioningUpdates \
164- -authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APP_STORE_CONNECT_KEY_ID }}.p8 \
165- -authenticationKeyID ${{ secrets.APP_STORE_CONNECT_KEY_ID }} \
166- -authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} \
167- DEVELOPMENT_TEAM=${APPLE_TEAM_ID} \
191+ -authenticationKeyPath "${ASC_KEY_PATH}" \
192+ -authenticationKeyID "${ASC_KEY_ID}" \
193+ -authenticationKeyIssuerID "${ASC_ISSUER_ID}" \
194+ DEVELOPMENT_TEAM=" ${APPLE_TEAM_ID}" \
168195 archive
169196
170197 - name : Write ExportOptions.plist
@@ -186,6 +213,10 @@ jobs:
186213 EOF
187214
188215 - name : Export & upload to TestFlight
216+ env :
217+ ASC_KEY_ID : ${{ steps.write-api-key.outputs.key_id }}
218+ ASC_ISSUER_ID : ${{ steps.write-api-key.outputs.issuer_id }}
219+ ASC_KEY_PATH : ${{ steps.write-api-key.outputs.key_path }}
189220 run : |
190221 unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0
191222 xcodebuild \
@@ -194,9 +225,9 @@ jobs:
194225 -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \
195226 -exportPath "$RUNNER_TEMP/export" \
196227 -allowProvisioningUpdates \
197- -authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APP_STORE_CONNECT_KEY_ID }}.p8 \
198- -authenticationKeyID ${{ secrets.APP_STORE_CONNECT_KEY_ID }} \
199- -authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
228+ -authenticationKeyPath "${ASC_KEY_PATH}" \
229+ -authenticationKeyID "${ASC_KEY_ID}" \
230+ -authenticationKeyIssuerID "${ASC_ISSUER_ID}"
200231
201232 - name : Clean up API key
202233 if : always()
0 commit comments