From 415e74a1c4339428b666c0df93b5d3f54368e3c1 Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Fri, 3 Jul 2026 00:05:06 +0900 Subject: [PATCH 01/15] =?UTF-8?q?chore:=20Android/iOS=20=EB=A6=B4=EB=A6=AC?= =?UTF-8?q?=EC=A6=88=20=EB=B9=8C=EB=93=9C=20CI=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=ED=99=94=20(AAB/IPA=20=EC=82=B0=EC=B6=9C=EB=AC=BC=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C)(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release-build.yml | 188 ++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 .github/workflows/release-build.yml diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 00000000..fce037c1 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,188 @@ +name: Release Build (AAB / IPA) + +on: + push: + branches: [main] + +concurrency: + group: release-build-${{ github.ref }} + cancel-in-progress: true + +jobs: + android-build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Enable Corepack (pnpm) + run: | + corepack enable + pnpm -v + + - name: Setup Node (with pnpm cache) + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install + + - name: Build web assets + run: pnpm build + + - name: Sync Capacitor (android) + run: npx cap sync android + + - name: Setup JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Decode keystore + run: | + echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore + + - name: Write keystore.properties + run: | + cat < android/keystore.properties + storeFile=app/release.keystore + storePassword=${{ secrets.ANDROID_KEYSTORE_STORE_PASSWORD }} + keyAlias=${{ secrets.ANDROID_KEYSTORE_KEY_ALIAS }} + keyPassword=${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }} + EOF + + - name: Build release AAB + working-directory: android + run: | + chmod +x gradlew + ./gradlew bundleRelease --no-daemon + + - name: Upload AAB artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-aab + path: android/app/build/outputs/bundle/release/*.aab + retention-days: 14 + + ios-build: + runs-on: macos-latest + env: + TEAM_ID: YC7R5A6449 + # 심사 통과 전이므로 development로 기본 설정. 심사 통과 후 배포용으로 전환 시 app-store로 변경. + EXPORT_METHOD: development + BUNDLE_ID: com.finders.app + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Enable Corepack (pnpm) + run: | + corepack enable + pnpm -v + + - name: Setup Node (with pnpm cache) + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install + + - name: Build web assets + run: pnpm build + + - name: Sync Capacitor (ios) + run: npx cap sync ios + + - name: Import signing certificate & provisioning profile + env: + CERTIFICATE_BASE64: ${{ secrets.IOS_DIST_CERTIFICATE_P12_BASE64 }} + CERTIFICATE_PASSWORD: ${{ secrets.IOS_DIST_CERTIFICATE_PASSWORD }} + PROFILE_BASE64: ${{ secrets.IOS_PROVISION_PROFILE_BASE64 }} + run: | + KEYCHAIN_PASSWORD=$(openssl rand -base64 24) + KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" + CERT_PATH="$RUNNER_TEMP/certificate.p12" + PROFILE_PATH="$RUNNER_TEMP/profile.mobileprovision" + + echo "$CERTIFICATE_BASE64" | base64 -d -o "$CERT_PATH" + echo "$PROFILE_BASE64" | base64 -d -o "$PROFILE_PATH" + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERT_PATH" -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"//g') + + mkdir -p ~/Library/MobileDevice/"Provisioning Profiles" + cp "$PROFILE_PATH" ~/Library/MobileDevice/"Provisioning Profiles"/profile.mobileprovision + + PROFILE_UUID=$(security cms -D -i "$PROFILE_PATH" | /usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin) + PROFILE_NAME=$(security cms -D -i "$PROFILE_PATH" | /usr/libexec/PlistBuddy -c "Print Name" /dev/stdin) + + echo "PROFILE_UUID=$PROFILE_UUID" >> "$GITHUB_ENV" + echo "PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV" + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" + + - name: Archive + working-directory: ios/App + run: | + xcodebuild -workspace App.xcworkspace \ + -scheme App \ + -configuration Release \ + -archivePath "$RUNNER_TEMP/App.xcarchive" \ + -destination "generic/platform=iOS" \ + CODE_SIGN_STYLE=Manual \ + CODE_SIGN_IDENTITY="Apple Distribution" \ + DEVELOPMENT_TEAM="$TEAM_ID" \ + PROVISIONING_PROFILE_SPECIFIER="$PROFILE_NAME" \ + OTHER_CODE_SIGN_FLAGS="--keychain $KEYCHAIN_PATH" \ + archive + + - name: Create ExportOptions.plist + run: | + cat < "$RUNNER_TEMP/ExportOptions.plist" + + + + + method + $EXPORT_METHOD + teamID + $TEAM_ID + signingStyle + manual + provisioningProfiles + + $BUNDLE_ID + $PROFILE_NAME + + + + EOF + + - name: Export IPA + run: | + xcodebuild -exportArchive \ + -archivePath "$RUNNER_TEMP/App.xcarchive" \ + -exportPath "$RUNNER_TEMP/build" \ + -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" + + - name: Upload IPA artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-ipa + path: ${{ runner.temp }}/build/*.ipa + retention-days: 14 + + - name: Cleanup keychain + if: always() + run: security delete-keychain "$KEYCHAIN_PATH" || true From f6a13a6a847ffc683e9939f8bdcb6814c5b37a3e Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sat, 4 Jul 2026 15:21:07 +0900 Subject: [PATCH 02/15] =?UTF-8?q?chore:=20=EB=A6=AC=EB=B7=B0=20=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release-build.yml | 20 +++++++++++++------- android/app/build.gradle | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index fce037c1..12db09dc 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -45,23 +45,29 @@ jobs: uses: android-actions/setup-android@v3 - name: Decode keystore + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} run: | - echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore + echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/release.keystore - name: Write keystore.properties + env: + ANDROID_KEYSTORE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_STORE_PASSWORD }} + ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_KEYSTORE_KEY_ALIAS }} + ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }} run: | cat < android/keystore.properties storeFile=app/release.keystore - storePassword=${{ secrets.ANDROID_KEYSTORE_STORE_PASSWORD }} - keyAlias=${{ secrets.ANDROID_KEYSTORE_KEY_ALIAS }} - keyPassword=${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }} + storePassword=$ANDROID_KEYSTORE_STORE_PASSWORD + keyAlias=$ANDROID_KEYSTORE_KEY_ALIAS + keyPassword=$ANDROID_KEYSTORE_KEY_PASSWORD EOF - name: Build release AAB working-directory: android run: | chmod +x gradlew - ./gradlew bundleRelease --no-daemon + ./gradlew bundleRelease --no-daemon -PversionCode=${{ github.run_number }} - name: Upload AAB artifact uses: actions/upload-artifact@v4 @@ -74,8 +80,8 @@ jobs: runs-on: macos-latest env: TEAM_ID: YC7R5A6449 - # 심사 통과 전이므로 development로 기본 설정. 심사 통과 후 배포용으로 전환 시 app-store로 변경. - EXPORT_METHOD: development + # TestFlight 내부 테스트 배포용. TestFlight는 app-store 방식 IPA를 사용하며, 내부 테스터 배포는 App Review 없이 가능. + EXPORT_METHOD: app-store BUNDLE_ID: com.finders.app steps: - name: Checkout diff --git a/android/app/build.gradle b/android/app/build.gradle index ef5da9a8..279fa197 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -14,7 +14,7 @@ android { applicationId "com.finders" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 2 + versionCode project.hasProperty('versionCode') ? (project.property('versionCode') as Integer) : 2 versionName "0.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { From 730164867ecb0c48100ec654211d226ae64e2e82 Mon Sep 17 00:00:00 2001 From: MinSeokKim Date: Sat, 4 Jul 2026 20:20:55 +0900 Subject: [PATCH 03/15] =?UTF-8?q?fix:=20CI=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - env 주입 - keystore 경로 - 빌드번호 --- .github/workflows/release-build.yml | 34 +++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 12db09dc..4e71f5ff 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -3,11 +3,16 @@ name: Release Build (AAB / IPA) on: push: branches: [main] + workflow_dispatch: concurrency: group: release-build-${{ github.ref }} cancel-in-progress: true +env: + # 빌드 번호 = VERSION_CODE_BASE + github.run_number (Android versionCode / iOS CFBundleVersion 공용). + VERSION_CODE_BASE: 2 + jobs: android-build: runs-on: ubuntu-latest @@ -30,6 +35,16 @@ jobs: run: pnpm install - name: Build web assets + env: + VITE_PUBLIC_API_URL: ${{ secrets.VITE_PUBLIC_API_URL }} + VITE_PUBLIC_KAKAO_REDIRECT_URI: ${{ secrets.VITE_PUBLIC_KAKAO_REDIRECT_URI }} + VITE_PUBLIC_KAKAO_JS_KEY: ${{ secrets.VITE_PUBLIC_KAKAO_JS_KEY }} + VITE_PUBLIC_KAKAO_REST_API_KEY: ${{ secrets.VITE_PUBLIC_KAKAO_REST_API_KEY }} + VITE_PUBLIC_KAKAO_NATIVE_APP_KEY: ${{ secrets.VITE_PUBLIC_KAKAO_NATIVE_APP_KEY }} + VITE_PUBLIC_APPLE_CLIENT_ID: ${{ secrets.VITE_PUBLIC_APPLE_CLIENT_ID }} + VITE_PUBLIC_APPLE_REDIRECT_URI: ${{ secrets.VITE_PUBLIC_APPLE_REDIRECT_URI }} + VITE_PUBLIC_PORTONE_STORE_ID: ${{ secrets.VITE_PUBLIC_PORTONE_STORE_ID }} + VITE_PUBLIC_PORTONE_CHANNEL_KEY: ${{ secrets.VITE_PUBLIC_PORTONE_CHANNEL_KEY }} run: pnpm build - name: Sync Capacitor (android) @@ -57,7 +72,7 @@ jobs: ANDROID_KEYSTORE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_KEY_PASSWORD }} run: | cat < android/keystore.properties - storeFile=app/release.keystore + storeFile=release.keystore storePassword=$ANDROID_KEYSTORE_STORE_PASSWORD keyAlias=$ANDROID_KEYSTORE_KEY_ALIAS keyPassword=$ANDROID_KEYSTORE_KEY_PASSWORD @@ -67,7 +82,9 @@ jobs: working-directory: android run: | chmod +x gradlew - ./gradlew bundleRelease --no-daemon -PversionCode=${{ github.run_number }} + VERSION_CODE=$((VERSION_CODE_BASE + GITHUB_RUN_NUMBER)) + echo "versionCode: $VERSION_CODE" + ./gradlew bundleRelease --no-daemon -PversionCode=$VERSION_CODE - name: Upload AAB artifact uses: actions/upload-artifact@v4 @@ -102,6 +119,16 @@ jobs: run: pnpm install - name: Build web assets + env: + VITE_PUBLIC_API_URL: ${{ secrets.VITE_PUBLIC_API_URL }} + VITE_PUBLIC_KAKAO_REDIRECT_URI: ${{ secrets.VITE_PUBLIC_KAKAO_REDIRECT_URI }} + VITE_PUBLIC_KAKAO_JS_KEY: ${{ secrets.VITE_PUBLIC_KAKAO_JS_KEY }} + VITE_PUBLIC_KAKAO_REST_API_KEY: ${{ secrets.VITE_PUBLIC_KAKAO_REST_API_KEY }} + VITE_PUBLIC_KAKAO_NATIVE_APP_KEY: ${{ secrets.VITE_PUBLIC_KAKAO_NATIVE_APP_KEY }} + VITE_PUBLIC_APPLE_CLIENT_ID: ${{ secrets.VITE_PUBLIC_APPLE_CLIENT_ID }} + VITE_PUBLIC_APPLE_REDIRECT_URI: ${{ secrets.VITE_PUBLIC_APPLE_REDIRECT_URI }} + VITE_PUBLIC_PORTONE_STORE_ID: ${{ secrets.VITE_PUBLIC_PORTONE_STORE_ID }} + VITE_PUBLIC_PORTONE_CHANNEL_KEY: ${{ secrets.VITE_PUBLIC_PORTONE_CHANNEL_KEY }} run: pnpm build - name: Sync Capacitor (ios) @@ -141,6 +168,8 @@ jobs: - name: Archive working-directory: ios/App run: | + BUILD_NUMBER=$((VERSION_CODE_BASE + GITHUB_RUN_NUMBER)) + echo "build number: $BUILD_NUMBER" xcodebuild -workspace App.xcworkspace \ -scheme App \ -configuration Release \ @@ -151,6 +180,7 @@ jobs: DEVELOPMENT_TEAM="$TEAM_ID" \ PROVISIONING_PROFILE_SPECIFIER="$PROFILE_NAME" \ OTHER_CODE_SIGN_FLAGS="--keychain $KEYCHAIN_PATH" \ + CURRENT_PROJECT_VERSION=$BUILD_NUMBER \ archive - name: Create ExportOptions.plist From 567b0f66f411bce841c163126b5b0a6e1febd57e Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Mon, 13 Jul 2026 01:43:30 +0900 Subject: [PATCH 04/15] =?UTF-8?q?chore:=20release=20CI=EC=97=90=20google-s?= =?UTF-8?q?ervices.json=20=EC=A3=BC=EC=9E=85=20=EC=8A=A4=ED=85=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FCM 푸시 알림 도입으로 Android 릴리즈 빌드에 google-services.json이 필요해져, 기존 keystore와 동일한 방식(Secret base64 디코딩)으로 android/app/google-services.json을 생성하는 스텝을 추가. 자격증명 파일이라 android/.gitignore에도 추가해 커밋되지 않도록 처리. --- .github/workflows/release-build.yml | 6 ++++++ android/.gitignore | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 4e71f5ff..2787b666 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -59,6 +59,12 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 + - name: Write google-services.json + env: + ANDROID_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON_BASE64 }} + run: | + echo "$ANDROID_GOOGLE_SERVICES_JSON_BASE64" | base64 -d > android/app/google-services.json + - name: Decode keystore env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} diff --git a/android/.gitignore b/android/.gitignore index f9e9dd76..30b8e41a 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -66,7 +66,7 @@ keystore.properties .cxx/ # Google Services (e.g. APIs or Firebase) -# google-services.json +google-services.json # Freeline freeline.py From d7a64412c0ed63c5b67c06c55eebc5ba106fc09a Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sat, 8 Aug 2026 21:44:28 +0900 Subject: [PATCH 05/15] =?UTF-8?q?chore:=20release=20CI=EC=97=90=20GoogleSe?= =?UTF-8?q?rvice-Info.plist=20=EC=A3=BC=EC=9E=85=20=EC=8A=A4=ED=85=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Xcode 프로젝트에 리소스로 등록되어 있어 파일이 없으면 Archive가 즉시 실패한다 (Android와 달리 존재 여부를 체크하는 경로가 없음). public 레포이므로 파일 자체는 gitignore하고 Secret에서 복원한다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 8 ++++++++ ios/.gitignore | 3 +++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 2787b666..8aaea646 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -140,6 +140,14 @@ jobs: - name: Sync Capacitor (ios) run: npx cap sync ios + # Xcode 프로젝트에 리소스로 등록되어 있어 파일이 없으면 Archive가 바로 실패한다 + # (Android와 달리 존재 여부를 체크하는 경로가 없음). cap sync 뒤에 써야 덮이지 않는다 + - name: Write GoogleService-Info.plist + env: + IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64: ${{ secrets.IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64 }} + run: | + echo "$IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64" | base64 -d -o ios/App/App/GoogleService-Info.plist + - name: Import signing certificate & provisioning profile env: CERTIFICATE_BASE64: ${{ secrets.IOS_DIST_CERTIFICATE_P12_BASE64 }} diff --git a/ios/.gitignore b/ios/.gitignore index f4702997..593b94ee 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -11,3 +11,6 @@ capacitor-cordova-ios-plugins # Generated Config files App/App/capacitor.config.json App/App/config.xml + +# Google Services (e.g. APIs or Firebase) +App/App/GoogleService-Info.plist From f461d434323fcfe7a8176bff454805c66c581ca8 Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sat, 8 Aug 2026 21:44:51 +0900 Subject: [PATCH 06/15] =?UTF-8?q?fix:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EB=B2=88=ED=98=B8=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20=EB=B0=B0=ED=8F=AC=EB=90=9C=20versionCode?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=A4(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit develop이 versionCode 4까지 올려둔 상태라 base 2로는 run_number 1~2에서 3, 4가 생성되어 Play Console 업로드가 거부된다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 8aaea646..640318a4 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -11,7 +11,8 @@ concurrency: env: # 빌드 번호 = VERSION_CODE_BASE + github.run_number (Android versionCode / iOS CFBundleVersion 공용). - VERSION_CODE_BASE: 2 + # 이미 배포된 versionCode(4) 이상이어야 Play Console이 업로드를 받는다. + VERSION_CODE_BASE: 4 jobs: android-build: From 5a3bf817e30c01603f042cac573a5c2bf9a9c5e1 Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sat, 8 Aug 2026 21:45:23 +0900 Subject: [PATCH 07/15] =?UTF-8?q?chore:=20=EB=A8=B8=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=20CI=20=EA=B2=80=EC=A6=9D=EC=9A=A9=20push=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0=20=EC=9E=84=EC=8B=9C=20=EC=B6=94=EA=B0=80(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit workflow_dispatch는 기본 브랜치에 있는 워크플로만 노출되어 머지 전에는 실행할 수 없다. 검증 후 되돌릴 커밋. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 640318a4..7aa92ded 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -2,7 +2,9 @@ name: Release Build (AAB / IPA) on: push: - branches: [main] + # TEMP: main 머지 전 CI 검증용. 검증 끝나면 이 브랜치 항목을 제거할 것 + # (workflow_dispatch는 기본 브랜치에 있는 워크플로만 노출되어 머지 전엔 쓸 수 없다) + branches: [main, "chore/android-ios-build-ci(#312)"] workflow_dispatch: concurrency: From 481221184c6a0e43f52ab6730efb81e5123ce45e Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sat, 8 Aug 2026 21:49:35 +0900 Subject: [PATCH 08/15] =?UTF-8?q?fix:=20release=20CI=EC=9D=98=20Node=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EC=9D=84=2022=EB=A1=9C=20=EC=98=AC=EB=A6=BC(?= =?UTF-8?q?#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capacitor 8 CLI가 NodeJS >=22를 요구해 cap sync가 두 job 모두에서 실패한다. main.yml은 cap sync를 돌리지 않아 20에서도 통과하던 상태였다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 7aa92ded..229fb540 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -31,7 +31,7 @@ jobs: - name: Setup Node (with pnpm cache) uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies @@ -121,7 +121,7 @@ jobs: - name: Setup Node (with pnpm cache) uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies From d19ac4709ae8ce8ca2fc93cf5130cb1116589835 Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sun, 9 Aug 2026 16:32:32 +0900 Subject: [PATCH 09/15] =?UTF-8?q?chore:=20=EC=84=9C=EB=AA=85=20Secret=20?= =?UTF-8?q?=EB=94=94=EC=BD=94=EB=94=A9=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=EA=B0=80=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Secret이 비어 있으면 security cms가 "Cannot parse a NULL or zero-length data" 로만 죽어 어느 Secret이 문제인지 로그로 알 수 없다. 바이트 수를 찍고 비정상이면 어떤 Secret을 다시 등록해야 하는지 명시하고 중단한다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 229fb540..fea58ad5 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -165,6 +165,17 @@ jobs: echo "$CERTIFICATE_BASE64" | base64 -d -o "$CERT_PATH" echo "$PROFILE_BASE64" | base64 -d -o "$PROFILE_PATH" + # Secret이 비었거나 base64가 아니면 여기서 잡는다. + # (안 잡으면 뒤의 security cms가 "Cannot parse a NULL or zero-length data"로만 죽어 원인이 안 보인다) + for f in "$CERT_PATH" "$PROFILE_PATH"; do + SIZE=$(stat -f%z "$f" 2>/dev/null || echo 0) + echo "$(basename "$f"): ${SIZE} bytes" + if [ "$SIZE" -lt 100 ]; then + echo "::error::$(basename "$f") 디코딩 결과가 ${SIZE} bytes다. 해당 Secret 값을 다시 등록할 것" + exit 1 + fi + done + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" From 64abcf07d7c4b2308cd4c46a57bc1ba7c35af80f Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sun, 9 Aug 2026 17:03:54 +0900 Subject: [PATCH 10/15] =?UTF-8?q?chore:=20iOS=20=EC=84=9C=EB=AA=85=20Secre?= =?UTF-8?q?t=20=EC=9E=AC=EA=B2=80=EC=A6=9D=EC=9A=A9=20=EB=B9=88=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로비저닝 프로파일 Secret 교체 후 검증. 임시 push 트리거와 함께 정리할 커밋. Co-Authored-By: Claude Opus 5 From 1e789dda2ca2738e0337a87713717815e3f67651 Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sun, 9 Aug 2026 17:09:38 +0900 Subject: [PATCH 11/15] =?UTF-8?q?chore:=20=EC=84=9C=EB=AA=85=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=A7=A4=EC=A7=81=20=EB=B0=94=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EB=A1=9C=EA=B7=B8=EC=97=90=20=EB=82=A8=EA=B9=80(#3?= =?UTF-8?q?12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 크기만으로는 base64 이중 인코딩·잘못된 파일을 구분할 수 없다. 앞 4바이트로 DER 여부를 판별한다. 컨테이너 헤더라 비밀값이 아니다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index fea58ad5..4a8d2418 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -169,7 +169,9 @@ jobs: # (안 잡으면 뒤의 security cms가 "Cannot parse a NULL or zero-length data"로만 죽어 원인이 안 보인다) for f in "$CERT_PATH" "$PROFILE_PATH"; do SIZE=$(stat -f%z "$f" 2>/dev/null || echo 0) - echo "$(basename "$f"): ${SIZE} bytes" + # 컨테이너 형식 판별용 매직 바이트. DER(30 82…)가 아니면 인코딩이 잘못된 것이다 + # (3c3f786d = " Date: Sun, 9 Aug 2026 17:16:41 +0900 Subject: [PATCH 12/15] =?UTF-8?q?chore:=20=EC=84=9C=EB=AA=85=20=EC=8A=A4?= =?UTF-8?q?=ED=85=9D=EC=97=90=20=EB=AA=85=EB=A0=B9=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=EB=A7=88=EC=BB=A4=20=EC=B6=94=EA=B0=80(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 키체인 명령들이 한 스텝에 묶여 있어 어느 명령이 실패했는지 로그로 구분되지 않는다. 원인 특정 후 마커는 제거할 것. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 4a8d2418..090e4370 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -178,12 +178,19 @@ jobs: fi done + echo "--- create-keychain" security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + echo "--- set-keychain-settings" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + echo "--- unlock-keychain" security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + echo "--- import" security import "$CERT_PATH" -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" -T /usr/bin/codesign + echo "--- set-key-partition-list" security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + echo "--- list-keychain" security list-keychain -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"//g') + echo "--- security cms" mkdir -p ~/Library/MobileDevice/"Provisioning Profiles" cp "$PROFILE_PATH" ~/Library/MobileDevice/"Provisioning Profiles"/profile.mobileprovision From 65626f1c1edbd5e79f8b1abb7305c676c4085b53 Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sun, 9 Aug 2026 17:24:47 +0900 Subject: [PATCH 13/15] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=EB=B9=84?= =?UTF-8?q?=EC=A0=80=EB=8B=9D=20=ED=94=84=EB=A1=9C=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=94=94=EC=BD=94=EB=94=A9=EC=9D=84=20openssl=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit security cms가 정상 DER 프로파일(head=3082, 길이 일치)에도 "Cannot parse a NULL or zero-length data"로 실패해 iOS 빌드가 막혔다. 키체인 명령은 모두 통과한 상태이고 이 명령만 실패한다. openssl smime으로 CMS 페이로드를 꺼내 도구 의존을 제거한다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 090e4370..afae461f 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -195,8 +195,15 @@ jobs: mkdir -p ~/Library/MobileDevice/"Provisioning Profiles" cp "$PROFILE_PATH" ~/Library/MobileDevice/"Provisioning Profiles"/profile.mobileprovision - PROFILE_UUID=$(security cms -D -i "$PROFILE_PATH" | /usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin) - PROFILE_NAME=$(security cms -D -i "$PROFILE_PATH" | /usr/libexec/PlistBuddy -c "Print Name" /dev/stdin) + # security cms는 정상 DER 프로파일에도 "Cannot parse a NULL or zero-length data"로 + # 실패한다(macos-latest 기준). openssl로 CMS 페이로드만 꺼낸다. + # -noverify: 서명 체인 검증은 불필요하고 payload plist만 있으면 된다 + PROFILE_PLIST="$RUNNER_TEMP/profile.plist" + openssl smime -inform der -verify -noverify -in "$PROFILE_PATH" -out "$PROFILE_PLIST" + + PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" "$PROFILE_PLIST") + PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print Name" "$PROFILE_PLIST") + echo "profile: $PROFILE_NAME ($PROFILE_UUID)" echo "PROFILE_UUID=$PROFILE_UUID" >> "$GITHUB_ENV" echo "PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV" From d378730cdc3452d4b6d115f1c45ec888dca3d3ae Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sun, 9 Aug 2026 17:34:48 +0900 Subject: [PATCH 14/15] =?UTF-8?q?fix:=20=EC=84=9C=EB=AA=85=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EC=9D=84=20App=20=ED=83=80=EA=B9=83=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=98=AE=EA=B2=A8=20Pods=20=EC=95=84=EC=B9=B4=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=20=EC=8B=A4=ED=8C=A8=20=ED=95=B4=EA=B2=B0(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xcodebuild 커맨드라인 빌드 설정은 워크스페이스 전체 타깃에 적용된다. PROVISIONING_PROFILE_SPECIFIER가 Pods 타깃(Alamofire, KakaoSDK 등)에도 걸려 "does not support provisioning profiles"로 아카이브가 실패했다. 커맨드라인 설정은 프로젝트 설정을 덮어쓰므로 Podfile 훅으로는 막을 수 없다. App 타깃 Release 설정에만 Manual 서명을 지정하고 커맨드라인에서 제거한다. Debug는 Automatic 그대로라 로컬 개발 빌드는 영향받지 않는다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 4 ---- ios/App/App.xcodeproj/project.pbxproj | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index afae461f..cc8a4f46 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -219,10 +219,6 @@ jobs: -configuration Release \ -archivePath "$RUNNER_TEMP/App.xcarchive" \ -destination "generic/platform=iOS" \ - CODE_SIGN_STYLE=Manual \ - CODE_SIGN_IDENTITY="Apple Distribution" \ - DEVELOPMENT_TEAM="$TEAM_ID" \ - PROVISIONING_PROFILE_SPECIFIER="$PROFILE_NAME" \ OTHER_CODE_SIGN_FLAGS="--keychain $KEYCHAIN_PATH" \ CURRENT_PROJECT_VERSION=$BUILD_NUMBER \ archive diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 4cd1da1e..bf5a09ab 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -393,7 +393,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = App/App.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = YC7R5A6449; INFOPLIST_FILE = App/Info.plist; @@ -401,6 +402,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.1.6; PRODUCT_BUNDLE_IDENTIFIER = com.finders.app; + PROVISIONING_PROFILE_SPECIFIER = "Finders App Store"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_VERSION = 5.0; From 858364c77b6eb0949892c5df1229cdee6ceb4a2e Mon Sep 17 00:00:00 2001 From: jeonbinggu Date: Sun, 9 Aug 2026 17:41:35 +0900 Subject: [PATCH 15/15] =?UTF-8?q?chore:=20CI=20=EA=B2=80=EC=A6=9D=EC=9A=A9?= =?UTF-8?q?=20=EC=9E=84=EC=8B=9C=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=EC=99=80=20?= =?UTF-8?q?=EB=94=94=EB=B2=84=EA=B7=B8=20=EB=A7=88=EC=BB=A4=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release 빌드가 Android/iOS 양쪽 통과(AAB/IPA 산출물 생성)를 확인해 제거한다. Secret 크기·매직바이트 가드는 실패 시 원인을 즉시 알려주므로 남긴다. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-build.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index cc8a4f46..bf84e3bf 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -2,9 +2,7 @@ name: Release Build (AAB / IPA) on: push: - # TEMP: main 머지 전 CI 검증용. 검증 끝나면 이 브랜치 항목을 제거할 것 - # (workflow_dispatch는 기본 브랜치에 있는 워크플로만 노출되어 머지 전엔 쓸 수 없다) - branches: [main, "chore/android-ios-build-ci(#312)"] + branches: [main] workflow_dispatch: concurrency: @@ -178,19 +176,12 @@ jobs: fi done - echo "--- create-keychain" security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - echo "--- set-keychain-settings" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" - echo "--- unlock-keychain" security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - echo "--- import" security import "$CERT_PATH" -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" -T /usr/bin/codesign - echo "--- set-key-partition-list" security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - echo "--- list-keychain" security list-keychain -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"//g') - echo "--- security cms" mkdir -p ~/Library/MobileDevice/"Provisioning Profiles" cp "$PROFILE_PATH" ~/Library/MobileDevice/"Provisioning Profiles"/profile.mobileprovision