diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml index 30fcc2739..fea9bee88 100644 --- a/.github/workflows/debug.yml +++ b/.github/workflows/debug.yml @@ -62,7 +62,7 @@ jobs: echo NAVER_MAPS_CLIENT_ID=$NAVER_MAPS_CLIENT_ID >> local.properties echo POSTHOG_API_KEY=$POSTHOG_API_KEY >> local.properties echo POSTHOG_HOST=$POSTHOG_HOST >> local.properties - + - name: Generate google-services.json run: | echo "$GOOGLE_SERVICE" > app/google-services.json.b64 @@ -76,14 +76,14 @@ jobs: # - name: Assemble Debug APK # if: > # github.event_name == 'pull_request' && - # startsWith(github.event.pull_request.head.ref, 'release/') && + # startsWith(github.event.pull_request.head.ref, 'release/') && # (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') # run: ./gradlew assembleDebug # - name: Upload Debug APK artifact # if: > # github.event_name == 'pull_request' && - # startsWith(github.event.pull_request.head.ref, 'release/') && + # startsWith(github.event.pull_request.head.ref, 'release/') && # (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') # uses: actions/upload-artifact@v4 # with: @@ -91,6 +91,22 @@ jobs: # path: app/build/outputs/apk/debug/*.apk # retention-days: 1 + - name: Decode Keystore for Release Signing + if: > + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.head.ref, 'release/') + env: + KEYSTORE_CONTENT: ${{ secrets.KEYSTORE_CONTENT }} + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + echo "$KEYSTORE_CONTENT" | base64 -d > $GITHUB_WORKSPACE/release.keystore + echo "KEYSTORE_FILE=$GITHUB_WORKSPACE/release.keystore" >> $GITHUB_ENV + echo "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> $GITHUB_ENV + echo "KEY_ALIAS=$KEY_ALIAS" >> $GITHUB_ENV + echo "KEY_PASSWORD=$KEY_PASSWORD" >> $GITHUB_ENV + - name: Assemble Release APK if: > github.event_name == 'pull_request' && @@ -98,6 +114,37 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') run: ./gradlew assembleRelease + - name: Inspect AndroidManifest (NAVER_MAPS_CLIENT_ID injected?) + if: > + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.head.ref, 'release/') && + (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') + run: | + APK=$(ls app/build/outputs/apk/release/*.apk | head -n 1) + echo "APK=$APK" + + BUILD_TOOLS_VERSION=$(ls "$ANDROID_SDK_ROOT/build-tools" | sort -V | tail -n 1) + AAPT2="$ANDROID_SDK_ROOT/build-tools/$BUILD_TOOLS_VERSION/aapt2" + echo "AAPT2=$AAPT2" + + # 네이버 meta-data 존재 여부 확인 + "$AAPT2" dump xmltree --file AndroidManifest.xml "$APK" \ + | sed -n '/com.naver.maps.map.NCP_KEY_ID/,+20p' || true + + # placeholder가 그대로 남아있는지 확인(치환 실패 징후) + "$AAPT2" dump xmltree --file AndroidManifest.xml "$APK" \ + | grep -n '\${NAVER_MAPS_CLIENT_ID}' || true + + # Release APK 서명 여부 검증 + - name: Verify Release APK signature + if: > + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.head.ref, 'release/') && + (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') + run: | + BUILD_TOOLS_VERSION=$(ls "$ANDROID_SDK_ROOT/build-tools" | sort -V | tail -n 1) + "$ANDROID_SDK_ROOT/build-tools/$BUILD_TOOLS_VERSION/apksigner" verify --print-certs app/build/outputs/apk/release/*.apk + - name: Upload Release APK artifact if: > github.event_name == 'pull_request' && @@ -128,9 +175,17 @@ jobs: # Release만 Firebase 배포 - name: Install Firebase CLI + if: > + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.head.ref, 'release/') && + (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') run: npm i -g firebase-tools - name: Distribute Release APK to Firebase App Distribution + if: > + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.head.ref, 'release/') && + (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} @@ -140,16 +195,20 @@ jobs: --groups "eat-ssu-android-qa" \ --release-notes "Release | PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}" \ --token "$FIREBASE_TOKEN" - + # PR 코멘트 - name: Comment PR + if: > + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.head.ref, 'release/') && + (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened') uses: actions/github-script@v7 with: script: | const body = - [ - `✅ Firebase App Distribution으로 Release APK 배포됨 (그룹: eat-ssu-android-qa)`, - ].join('\n'); + [ + `✅ Firebase App Distribution으로 Release APK 배포됨 (그룹: eat-ssu-android-qa)`, + ].join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 342e27808..122917a52 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,6 +53,17 @@ android { keyPassword = System.getenv("KEY_PASSWORD") } } + + // CI 환경에서 사용하는 서명 설정 + create("ciRelease") { + val keystoreFile = System.getenv("KEYSTORE_FILE") + if (!keystoreFile.isNullOrBlank()) { + storeFile = file(keystoreFile) + storePassword = System.getenv("KEYSTORE_PASSWORD") + keyAlias = System.getenv("KEY_ALIAS") + keyPassword = System.getenv("KEY_PASSWORD") + } + } } buildTypes { @@ -85,6 +96,9 @@ android { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + if (!System.getenv("KEYSTORE_FILE").isNullOrBlank()) { + signingConfig = signingConfigs.getByName("ciRelease") + } } getByName("debug") {