From c9e74988fc5f75b53768bf4d9c66702047d323cb Mon Sep 17 00:00:00 2001 From: junghoyu Date: Wed, 17 Jun 2026 17:56:53 +0900 Subject: [PATCH 1/3] ci: add macOS/Windows PR check workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 pr-check.yml은 Linux만 검증한다. 외부 컨트리뷰터가 본 미러 레포에 PR을 올릴 때 macOS/Windows 빌드 회귀를 함께 잡기 위해 별도 워크플로를 추가한다. 본 파일은 미러 단독 파일이며 upstream(JJs23/racemo)에서는 sync-public-whitelist.mjs의 MIRROR_ONLY_PRESERVE 목록으로 sync 시 보존된다. --- .github/workflows/pr-check-cross.yml | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/pr-check-cross.yml diff --git a/.github/workflows/pr-check-cross.yml b/.github/workflows/pr-check-cross.yml new file mode 100644 index 0000000..1f0ee24 --- /dev/null +++ b/.github/workflows/pr-check-cross.yml @@ -0,0 +1,52 @@ +name: PR Check (Cross-Platform) + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + verify: + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install Node Dependencies + run: npm ci + + - name: Setup Rust + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + with: + components: clippy + + - name: Clear stale sccache wrapper + shell: bash + run: unset RUSTC_WRAPPER 2>/dev/null; echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV" + + - name: Cache Rust + uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + workspaces: src-tauri + prefix-key: v1 + + - name: Rust Tests + shell: bash + run: cd src-tauri && cargo test + + - name: Rust Clippy + shell: bash + run: cd src-tauri && cargo clippy -- -D warnings + + - name: Frontend Build + run: npm run build From a2d5e490167f7f7d41895c756ae370b2f530e9ce Mon Sep 17 00:00:00 2001 From: junghoyu Date: Wed, 17 Jun 2026 18:21:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test(flow):=20macOS=20CI=20=EB=B3=80?= =?UTF-8?q?=EB=8F=99=EC=84=B1=20=EB=A7=88=EC=A7=84=20=E2=80=94=20Phase=202?= =?UTF-8?q?=20timeout=2060s=20=E2=86=92=20180s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pty_reader_pauses_without_acks_and_resumes_losslessly 테스트가 GitHub-hosted macos-latest 러너에서 60s 안에 688KB 출력을 끝까지 받지 못해 실패. 로컬 macOS arm64에서는 ~37s로 통과하므로 flow control 로직의 회귀가 아니라 러너 성능 차이. Co-Authored-By: Claude Opus 4.7 (1M context) --- src-tauri/src/tests/flow_test.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/tests/flow_test.rs b/src-tauri/src/tests/flow_test.rs index c8421d4..facc8c7 100644 --- a/src-tauri/src/tests/flow_test.rs +++ b/src-tauri/src/tests/flow_test.rs @@ -333,7 +333,8 @@ fn pty_reader_pauses_without_acks_and_resumes_losslessly() { // ── Phase 2: 수신분을 ack하며 소비 → 재개되어 끝까지 유실 없이 도착해야 함 flow.on_ack(CONN, &pty_id, received.len()); - let deadline = std::time::Instant::now() + Duration::from_secs(60); + // macOS CI 러너 변동성 마진. 데이터 유실이라면 어떤 timeout도 fail이라 검증 강도는 유지된다. + let deadline = std::time::Instant::now() + Duration::from_secs(180); while !contains_subslice(&received, b"\r\n100000\r\n") { assert!( std::time::Instant::now() < deadline, From 2643ba5d4bea5a2961ee0ee30718fb9d35b0a2ed Mon Sep 17 00:00:00 2001 From: junghoyu Date: Wed, 17 Jun 2026 20:35:38 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix(auth)+ci:=20Windows=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=ED=94=BD=EC=8A=A4=20=EB=B0=8F=20stale=20release-li?= =?UTF-8?q?nux.yml=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) src/auth.rs:246의 log::warn! 매크로에서 ?expr syntax (tracing crate 전용) 제거. log crate은 미지원이라 Windows 빌드가 syntax error로 실패했다. 2) main에서는 이미 sync-public-whitelist.mjs의 whitelist 갱신으로 .github/workflows/release-linux.yml이 제거됐다. 본 PR 브랜치에는 분기 시점의 구버전이 남아 있어 머지 시 main에 다시 들어올 위험이 있으므로 함께 제거한다. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release-linux.yml | 279 ---------------------------- src-tauri/src/auth.rs | 4 +- 2 files changed, 2 insertions(+), 281 deletions(-) delete mode 100644 .github/workflows/release-linux.yml diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml deleted file mode 100644 index 480fa43..0000000 --- a/.github/workflows/release-linux.yml +++ /dev/null @@ -1,279 +0,0 @@ -name: Release (Linux) - -on: - push: - tags: ['v*'] - workflow_dispatch: - inputs: - tag: - description: 'Tag to release (e.g. v0.0.4)' - required: true - -jobs: - # 매트릭스 빌드 전에 릴리스가 존재하는지 한 번만 보장해서 동시 create 레이스 방지 - prepare-release: - runs-on: ubuntu-latest - outputs: - tag: ${{ steps.meta.outputs.tag }} - version: ${{ steps.meta.outputs.version }} - steps: - - name: Resolve version & tag - id: meta - shell: bash - run: | - set -euo pipefail - if [ -n "${{ github.event.inputs.tag }}" ]; then - TAG="${{ github.event.inputs.tag }}" - else - TAG="${GITHUB_REF_NAME}" - fi - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - - - name: Ensure draft release exists - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - shell: bash - run: | - set -euo pipefail - TAG="${{ steps.meta.outputs.tag }}" - REPO="racemo-dev/racemo" - gh release view "$TAG" --repo "$REPO" > /dev/null 2>&1 || \ - gh release create "$TAG" --repo "$REPO" --title "Racemo ${TAG}" --notes "See the assets to download and install this version." --draft - - build: - needs: prepare-release - strategy: - fail-fast: false - matrix: - include: - - runner: ubuntu-latest - arch: x64 # 파일명에 쓰이는 라벨 - - runner: ubuntu-24.04-arm - arch: aarch64 - runs-on: ${{ matrix.runner }} - permissions: - contents: write - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ github.event.inputs.tag || github.ref }} - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '20' - cache: 'npm' - - - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - - - name: Clear stale sccache wrapper - run: echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV" - - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - workspaces: src-tauri - prefix-key: v1 - key: ${{ matrix.arch }} # 아키텍처별 캐시 분리 - - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libappindicator3-dev \ - librsvg2-dev \ - patchelf \ - libdbus-1-dev \ - pkg-config \ - libssl-dev \ - libgtk-3-dev \ - xdg-utils - - - run: npm ci - - - name: Build Tauri app (AppImage only) - env: - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - # AppImage만 빌드. .deb는 Tauri updater가 in-place 교체를 못 하므로 - # 사용자가 설치해도 auto-update를 받지 못해 혼란만 생긴다. APT repo를 - # 제대로 운영하게 되면 다시 켜기. - run: | - set -euo pipefail - npm run check:deps - npm run build:server - npm run build - npx tauri build --bundles appimage - - # Win/Mac 컨벤션에 맞춰 파일명 정규화: - # Racemo_VERSION_Linux_{arch}.AppImage - - name: Rename Linux artifacts - shell: bash - run: | - set -euo pipefail - VERSION="${{ needs.prepare-release.outputs.version }}" - ARCH="${{ matrix.arch }}" - BUNDLE_DIR="src-tauri/target/release/bundle" - shopt -s nullglob - - for f in "$BUNDLE_DIR"/appimage/*.AppImage; do - target="$BUNDLE_DIR/appimage/Racemo_${VERSION}_Linux_${ARCH}.AppImage" - [ "$f" != "$target" ] && mv -f "$f" "$target" - done - - echo "=== AppImage ===" - ls -la "$BUNDLE_DIR"/appimage/ 2>/dev/null || echo "(empty)" - - # Sign AppImage with updater key (self-updater verifies this signature) - - name: Sign AppImage with updater key - env: - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - shell: bash - run: | - set -euo pipefail - VERSION="${{ needs.prepare-release.outputs.version }}" - ARCH="${{ matrix.arch }}" - APPIMAGE="src-tauri/target/release/bundle/appimage/Racemo_${VERSION}_Linux_${ARCH}.AppImage" - npx tauri signer sign "$APPIMAGE" - SIG="${APPIMAGE}.sig" - if [ ! -s "$SIG" ]; then - echo "ERROR: signature output missing or empty: $SIG" >&2 - exit 1 - fi - - # 보안: 빌드한 .sig가 tauri.conf.json의 pubkey와 일치하는지 검증. - # 일치하지 않으면 기존 사용자가 auto-update 받을 수 없는 broken release가 됨. - - name: Verify updater key match - shell: bash - run: | - set -euo pipefail - VERSION="${{ needs.prepare-release.outputs.version }}" - ARCH="${{ matrix.arch }}" - SIG="src-tauri/target/release/bundle/appimage/Racemo_${VERSION}_Linux_${ARCH}.AppImage.sig" - if [ ! -f "$SIG" ]; then - echo "ERROR: signature file missing: $SIG" >&2 - exit 1 - fi - python3 scripts/verify-updater-key.py --sig "$SIG" || { - echo "ERROR: updater pubkey mismatch for $SIG" >&2 - exit 1 - } - - - name: Upload artifacts to GitHub Release - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - shell: bash - run: | - set -euo pipefail - TAG="${{ needs.prepare-release.outputs.tag }}" - REPO="racemo-dev/racemo" - - shopt -s nullglob - ASSETS=( - src-tauri/target/release/bundle/appimage/*.AppImage - ) - if [ ${#ASSETS[@]} -eq 0 ]; then - echo "No Linux artifacts found" >&2 - exit 1 - fi - gh release upload "$TAG" --repo "$REPO" --clobber "${ASSETS[@]}" - - # finalize-latest-json job이 두 arch의 .sig를 읽어야 하므로 artifact로 보존 - - name: Upload .sig as workflow artifact - uses: actions/upload-artifact@v4 # SHA pin via Dependabot after first run - with: - name: linux-sig-${{ matrix.arch }} - path: src-tauri/target/release/bundle/appimage/*.AppImage.sig - retention-days: 1 - if-no-files-found: error - - # matrix 빌드가 모두 끝난 후 latest.json에 linux 플랫폼 항목 merge. - # matrix 안에서 하면 두 job이 동시에 latest.json 수정 시 race condition 발생하므로 분리. - finalize-latest-json: - needs: [prepare-release, build] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Download x64 sig - uses: actions/download-artifact@v4 # SHA pin via Dependabot after first run - with: - name: linux-sig-x64 - path: sigs/x64 - - - name: Download aarch64 sig - uses: actions/download-artifact@v4 # SHA pin via Dependabot after first run - with: - name: linux-sig-aarch64 - path: sigs/aarch64 - - - name: Merge linux platforms into latest.json - env: - GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} - shell: bash - run: | - set -euo pipefail - TAG="${{ needs.prepare-release.outputs.tag }}" - VERSION="${{ needs.prepare-release.outputs.version }}" - REPO="racemo-dev/racemo" - PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - - # 기존 latest.json 가져오기 (없으면 빈 구조로 생성) - mkdir -p existing - if gh release download "$TAG" --repo "$REPO" -p latest.json -D existing 2>/dev/null; then - echo "Found existing latest.json" - else - echo "No existing latest.json — creating fresh" - fi - - # 두 arch의 .sig 파일 위치 - SIG_X64="$(ls sigs/x64/*.AppImage.sig | head -1)" - SIG_AARCH64="$(ls sigs/aarch64/*.AppImage.sig | head -1)" - - # python heredoc은 들여쓰기 보존하므로 컬럼 0에서 시작해야 함 - python3 < Result<(), St let stderr = String::from_utf8_lossy(&out.stderr); log::warn!( target: "auth::vault", - code = ?out.status.code(), - "icacls failed to tighten vault ACL; file may be world-readable: {stderr}" + "icacls failed to tighten vault ACL (exit code {:?}); file may be world-readable: {stderr}", + out.status.code(), ); } Err(e) => {